@f0ursqu4r3/term0
| Install | |
|---|---|
npm install @f0ursqu4r3/term0 |
|
| Latest Version: | 0.4.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 30, 2026 |
| Links: | npm |
term0
Showcase: https://f0ursqu4r3.github.io/term0/ (Vue) and https://f0ursqu4r3.github.io/term0/elements.html (HTML and htmx)
Vue 3 components for a monochrome terminal look on a character-cell grid.
Every length is a cell (ch) or a line (lh). Every colour comes from a
Ghostty palette.
Coding agents: read AGENT_GUIDE.md. It ships in the package at
node_modules/@f0ursqu4r3/term0/AGENT_GUIDE.md.
Install
bun add @f0ursqu4r3/term0
Use
import '@f0ursqu4r3/term0/style.css'
<script setup lang="ts">
import { TermRoot, TPane, TLine, TMeter, usePalette } from '@f0ursqu4r3/term0'
usePalette() // apply the stored palette at start
</script>
<template>
<TermRoot>
<TPane title="Status">
<TLine
:runs="[
{ text: 'LINK ', tone: 'dim' },
{ text: 'UP', tone: 'ok' },
]" />
<TMeter :value="5" :total="8" :width="24" />
</TPane>
</TermRoot>
</template>
TermRoot sets the font, the cell grid, the palette and a reset. These
apply only inside the root, so a root can sit in a normal page. For a
full-page app, also import @f0ursqu4r3/term0/base.css. It applies the same styles
to html.
For an app that takes the whole window, set absolute. The root fills
the window in whole cells and lines, so the grid stays exact at every
window size. The remainder, less than one cell or line, sits after the
root by default. align moves it: 'start', 'center' or 'end' for
both axes, or [x, y] for each axis.
<TermRoot absolute align="center">...</TermRoot>
<TermRoot absolute :align="['center', 'start']">...</TermRoot>
All package CSS is in @layer term0. Your own unlayered CSS always wins.
Components
| Component | Purpose |
|---|---|
TermRoot |
Root. Prop palette (a Palette or Ghostty text) applies to this root only. absolute, align. |
TPane |
Box with the title in the top rule. |
TLine, TRun |
One console line of toned runs. |
TKey |
LABEL value link or button. |
TCursor |
Blinking block cursor. |
TButton |
inverse or plain (hover-inverse). |
TInput, TSelect, TCheckbox |
Form controls with v-model. |
TDialog |
Modal pane on the native <dialog>, v-model:open. |
TList |
Selectable rows, v-model:selected. Arrow keys, Home, End and letters move. height scrolls. |
TTable |
Fixed-width columns in cells. Sort, select and virtual rows. See Tables. |
TMeter, TSparkline, TTimeline |
Text charts. |
TChart, TBars |
Multi-row live charts. See Live charts. |
TPaletteEditor |
Ghostty palette editor. |
TTabs |
Tab bar and panel, v-model. Arrow keys, Home and End move between tabs. |
TRadioGroup |
(•) / ( ) over native radios, v-model. |
TTextarea |
Multi-line input. rows in lines, width in cells. |
TTree |
├─ / └─ tree. v-model:expanded, v-model:selected. Right and Left arrows expand and collapse. |
TLog |
Scrolling log with time and level. It follows new lines while scrolled to the bottom. ansi parses colour codes. Virtual rows. |
TPrompt |
Shell prompt with history and Tab completion. See Shell prompt. |
TSpinner |
One-cell activity glyph: braille, line, block or your own frames. Still under reduced motion. |
TBadge |
Inline inverse label. |
TKbd |
Key cap. |
TRule |
── LABEL ──── divider. |
TAlert |
Box in a palette role: info, ok, warn, alarm. |
TStatusBar |
Inverse bar with left and #right slots. |
TFields |
Aligned label and value pairs, with an optional · leader. |
TPanelGroup, TPanel |
Panels in a row or column, sized in cells. border="shared" joins panes; resizable drags. |
TCommandPalette, TMenu |
Fuzzy command list and menu button. See Keys and commands. |
TKeyHints |
Footer of key caps and labels: q quit / find. |
TToasts |
Stack of TAlert toasts in a corner. See Toasts. |
THeatmap |
Grid of ░▒▓█ shades, with row labels and a legend. |
TProgress |
Bar, percent, rate and time left: [████ ] 50% 9.6MB/s ETA 0:26. |
TDiff |
+ ok, - alarm, @@ info. A unified patch, or before and after. |
TScrollbar |
One-cell scrollbar beside a scrolling element. See Scrollbars. |
TCanvas |
Draws a CellBuffer (text, boxes, lines, braille dots). See Drawing. |
TGauge |
A half-circle gauge drawn with braille dots. |
TSplash |
Big letters for a heading, in the block (5 lines) or small (3 lines) font. Named by its text for screen readers. |
TStepper |
Step indicator, [✓] Build ── [2] Test ── [3] Ship, in a row or a column. It shows progress and takes no input. |
TForm, TField |
A <form novalidate> with labelled rows. TField puts a label, a control and an error or hint on the cell grid. TForm shows an error summary and emits submit or invalid. |
TCalendar |
Month calendar on the cell grid. v-model is a YYYY-MM-DD string. min and max limit the days. Full keyboard control. |
TMarkdown |
Markdown as text on the cell grid. Headings, lists, code blocks, quotes, rules, code, strong, em and links. HTML shows as text. |
Tones: dim, faint, alarm, ok, warn, info, inverse,
inverse-alarm, inverse-warn.
The glyph functions are also exported: meter, sparkline,
occupancyGlyph, fit, right, axis, the line() builder, the
timeline helpers Axis, counts, coverage and density, and the
chart helpers plot, bars, chartAxis, chartSummary,
barsSummary, resolveScale and ticks, and heatmap, ansiRuns, AnsiParser, diffLines,
parsePatch, duration, percent and eta.
Live charts
TChart draws one or more series as a line or an area, height lines
tall. TBars draws one bar per value. Both redraw when their values
change and hold no history. For live data, keep the samples in
useSeries: a ring buffer that holds the newest capacity values.
<script setup lang="ts">
import { TChart, TBars, useSeries } from '@f0ursqu4r3/term0'
const cpu = useSeries(120)
setInterval(() => cpu.push(readCpu()), 1000)
</script>
<template>
<TChart
:series="[{ values: cpu.values.value, tone: 'ok', label: 'cpu %' }]"
:height="8"
:width="60"
:min="0"
:max="100"
y-axis
legend
:thresholds="[{ value: 80, tone: 'alarm', rule: true }]" />
<TBars :values="cpu.values.value.slice(-12)" :height="4" :bar-width="3" :gap="1" />
</template>
TChart options:
| Prop | Default | Purpose |
|---|---|---|
values or series |
One series, or { values, tone, label } for each series. A later series draws on top. |
|
glyphs |
'quadrant' |
quadrant (2×2 per cell), block (1 sample per cell, 1/8 line) or braille (2×4). |
fill |
'line' |
line or area. |
height, width |
4, samples |
Lines and cells. width defaults to the longest series. |
align |
'right' |
Where the newest sample sits. |
min, max |
'auto' |
Fix both for a live chart, so the scale does not jump. |
yAxis, ticks |
off, ≤ 5 | Label column with ┤ at each tick. formatTick formats the labels. |
xAxis |
[cell, label] ticks under the plot. |
|
thresholds |
[] |
{ value, tone, rule }: cells at or above value take tone. rule draws ┄ at value. |
legend |
off | A row with each series label in its tone. |
label |
summary | aria-label. The default gives each series' last value, min and max. |
TBars takes values, labels, height, barWidth, gap, min,
max, baseline, tone (a tone or a function of the value and
index), thresholds, showValues, yAxis, ticks, formatTick and
label. Values below baseline grow down.
Every row of a chart has the same width in cells. A value that is not a finite number is a gap.
The bundled font has no braille glyphs, so glyphs="braille" uses a
fallback font. Each braille glyph sits in a one-cell box, so the grid
holds, but the glyphs look different from the rest of the page. Use
quadrant to stay in the bundled font.
Panel groups
<TPanelGroup border="shared" resizable>
<TPanel v-model:size="side" :min="12"><TPane title="Files">...</TPane></TPanel>
<TPanel fill><TPane title="Editor">...</TPane></TPanel>
</TPanelGroup>
TPanelGroup puts any number of panels next to each other. It fills
its parent, so give the parent a size. size, min and max are
cells in a row and lines in a column. The first fill panel takes
the remaining space. Without fill, the last panel takes it.
A pane border on a seam is the divider. With border="separate", two
panes keep a one-cell gap between their borders. With
border="shared", they use one border.
The dividers are fixed by default. With resizable, a drag or an arrow
key moves a divider by whole cells, and Home and End move it to its
limits.
A TPane in a panel draws its top and bottom rules through the middle
of the first and last line. Its body is the whole lines between them,
so give a pane panel :min="3" or more. The lower pane of a shared
column has no top line of its own, so it needs :min="2".
Keys and commands
useHotkeys calls a handler when its combo is pressed, while the
component is mounted. A combo is a key name with modifiers: q, /,
?, escape, ctrl+k, shift+tab. mod is Meta on Apple platforms
and Ctrl elsewhere. Keys typed into a text field are skipped, unless
the combo is in inInputs.
<script setup lang="ts">
import { TCommandPalette, TKeyHints, TMenu, useHotkeys, type Command } from '@f0ursqu4r3/term0'
const commands: Command[] = [
{ id: 'open', label: 'Open file', group: 'File', hint: 'mod+o' },
{ id: 'quit', label: 'Quit', hint: 'q' },
]
useHotkeys({ q: quit, 'mod+o': openFile }, { inInputs: ['mod+o'] })
</script>
<template>
<TMenu label="File" :items="commands" @select="run" />
<TCommandPalette :commands="commands" @run="(command) => run(command.id)" />
<TKeyHints
:hints="[
{ key: 'mod+k', label: 'commands' },
{ key: 'q', label: 'quit' },
]" />
</template>
TCommandPalette opens with its hotkey (default mod+k) or
v-model:open. It matches the query against group: label with
characters in order; runs and word starts rank first. Up and Down move,
Enter runs, Escape closes. fuzzyMatch, fuzzyFilter and highlight
are exported for your own filters.
TMenu opens under its button. Up, Down, Home and End move and skip
disabled items. Enter or a click selects; Escape, Tab or a click
outside closes it. hint shows a combo on the right. comboLabel
gives the text for a combo: ⌘K on Apple, Ctrl+K elsewhere.
Shell prompt
<TLog :entries="output" :height="8" />
<TPrompt v-model:history="history" :complete="complete" aria-label="Shell" @submit="run" />
Enter emits submit and adds the line to history (the newest limit
lines, default 100). Up and Down walk the history and come back to the
draft. Tab calls complete with the text before the caret. It returns
whole-line candidates: one replaces the line, several fill their common
prefix and show under the prompt. Attributes go to the <input>.
ANSI output
TLog shows CLI output with its colours when you set ansi. The style
carries from one entry to the next, as in a terminal. A bare \r
rewrites the line, so progress bars show their last state. Every other
escape sequence is removed. The text never becomes HTML.
<TLog :entries="output" ansi />
<TLog :entries="output" ansi ansi-colours="palette" />
ansi-colours picks how colours render:
tone(default): red isalarm, greenok, yellowwarn, blueinfo, bright blackfaint. Other colours are plain. The look stays monochrome.palette: the 16 ANSI colours of the palette, ast0-fg-Nandt0-bg-N. Bold makes colours 0 to 7 bright. 256-colour and truecolor codes use the nearest of the 16.
For a raw stream, such as fetch or a WebSocket, give the chunks to an
AnsiParser. It returns the lines each chunk completes and keeps a
partial line or escape for the next chunk.
import { ref } from 'vue'
import { AnsiParser, type LogEntry } from '@f0ursqu4r3/term0'
const parser = new AnsiParser({ colours: 'tone' })
const output = ref<LogEntry[]>([])
socket.onmessage = ({ data }) => {
const lines = parser.push(data).map((runs) => ({ text: '', runs }))
output.value = [...output.value, ...lines].slice(-1000)
}
ansiRuns(text) parses one string into Run[] for TLine.
LogEntry.runs shows runs in place of text.
With ansi, TLog caches the parse for each entry object. Do not change
an entry in place: replace it with a new object. Keep the entry objects
you already have when you append, so only new entries parse. Each entry
is one row: an escape sequence never continues into the next entry.
Long logs
TLog wraps each entry on the cell grid and draws only the rows in
view, plus overscan rows (default 5) above and below. A log of 100 000
entries scrolls and appends without delay. Two limits follow from this:
- Browser find (
Ctrl+F) and text selection reach only the drawn rows. - The wrap counts one cell for each code point, so wide characters (CJK, most emoji) take two cells on screen but count as one.
Toasts
import { toast } from '@f0ursqu4r3/term0'
toast('Saved.', { tone: 'ok' })
toast('Deploy failed.', { tone: 'alarm', title: 'Deploy', timeout: 0 })
Put one <TToasts /> in the app. It shows the shared stack in a
corner (default bottom-end), newest nearest the edge. A toast
leaves after timeout ms (default 4000); 0 stays until its ×
button. The stack keeps the newest 5. createToasts(limit) makes a
separate stack for <TToasts :stack="stack" />.
Tables
<TTable
v-model:sort="sort"
v-model:selected="pid"
:columns="[{ key: 'pid', label: 'PID', width: 6, sortable: true }]"
:rows="rows"
row-key="pid"
:height="12" />
A sortable column sorts on a header click: ascending, descending,
then unsorted. The header shows ▲ or ▼ and sets aria-sort.
Numbers sort as numbers and text sorts with numeric parts; compare
on the column overrides this.
rowKey makes rows selectable: a click, or Up, Down, Home, End, Page
Up and Page Down with the table focused. The selected row is inverse.
height (lines, header included) scrolls the body under a sticky
header and draws only the rows in view, plus overscan rows each side
(default 5). Each row is then one line, so cell slots must not wrap.
Scrollbars
The package CSS draws native scrollbars one cell wide in palette
greys: a void track set off by a rule, and a faint thumb. Some browsers
ignore this styling, for example Safari overlay scrollbars. For the
same scrollbar in every browser, set scrollbar="cell" on TLog, or
on TList and TTable with a height. The thumb is at least one line
tall. Drag it, click the track to page, or use the wheel over it. In a
table the track starts under the sticky header.
For your own scrolling element, put <TScrollbar :target="element" :rows="10" /> next to it. offset skips lines at the top. useScroll
gives the scroll position as refs.
Drawing
CellBuffer is a grid of character cells. You draw on it, then pass
buffer.rows() to TCanvas.
<script setup lang="ts">
import { computed, ref } from 'vue'
import { CellBuffer, TCanvas } from '@f0ursqu4r3/term0'
const tick = ref(0)
const rows = computed(() => {
tick.value // Read the counter: draw again when it changes.
return new CellBuffer(24, 6)
.box(0, 0, 24, 6, 'round', 'dim')
.put(2, 0, ' Plot ')
.line(4, 4, 40, 30, 'info')
.rows()
})
</script>
<template>
<TCanvas :rows="rows" label="Plot" />
</template>
TCanvas does not watch the buffer. After you draw, call buffer.rows()
again and pass the new array.
Big headings
TSplash draws text in big letters. Set font="small" for three lines. Set tone to colour it.
<TSplash text="term0" />
<TSplash text="Ready" font="small" tone="ok" :level="2" />
splash(text, { font }) returns the lines as string[], for your own layout.
Steps
TStepper shows where a process stands. current is the 0-based index of the current step.
Done steps are green with ✓. The current step is inverse. Upcoming steps are dim.
<TStepper :steps="['Build', 'Test', 'Ship']" :current="1" />
<TStepper :steps="['Build', 'Test', 'Ship']" :current="1" direction="column" />
Forms
TField lays out one row: the label in a column of cells, the control
beside it, and a message under the control. An error shows in the alarm
tone with a ! mark. A hint shows in the dim tone. The label points at
the first input, select or textarea in the slot. That control also gets
aria-describedby for the message, and aria-invalid for an error.
TForm collects the errors. Pass errors (field name to message). Each
TField with a matching name shows its message. An alarm summary
above the fields shows the count and all messages.
<TForm :errors="errors" @submit="save($event.values)" @invalid="errors = $event.errors">
<TField name="email" label="Email" hint="We never share it.">
<TInput type="text" required />
</TField>
<TField name="team" label="Team">
<TSelect v-model="team" :options="teams" />
</TField>
<TButton type="submit">Save</TButton>
</TForm>
submit carries { values, form }. The values come from FormData, and
the last value of a name wins. invalid carries { errors, form } when
required, pattern or another native rule fails. The form never
submits itself.
Calendar
TCalendar shows one month. Its v-model is a YYYY-MM-DD string or
null. All dates are local calendar days, not UTC instants. Today is
info. The selected day is inverse. Days outside min and max are
faint and cannot be selected.
<TCalendar v-model="due" min="2026-09-01" :week-start="0" label="Due date" />
Keys on the grid: arrows move by a day or a week, Page Up and Page Down
by a month, Home and End to the start and end of the week, Enter and
Space select. Month names and weekday initials come from Intl in the
browser locale. Set locale to choose another.
Markdown
TMarkdown shows a small subset of CommonMark. It needs no dependency.
Give it a string in source.
<TMarkdown :source="notes" label="Release notes" />
The component never uses v-html. HTML in the source shows as text. A
link gets an href only when the URL is http:, https:, mailto: or
relative. Other links, such as javascript: and data:, show their
text and nothing else.
Palette
usePalette() returns shared state: text, palette, custom, error,
set(text) and reset(). Valid Ghostty text applies to every root and
saves to localStorage (key term0.palette) as it is typed. If the text
is not valid, error shows why and the last valid palette stays.
| Role | Source |
|---|---|
| Ink, void | foreground, background |
| Dim, faint, rule | foreground mixed into background at 64, 42 and 30 % |
| Alarm, ok, warn, info | palette = 1, 2, 3, 4 |
| Cursor | cursor-color, cursor-text |
| Selection | selection-background, selection-foreground |
Tailwind 4
@import 'tailwindcss';
@import '@f0ursqu4r3/term0/tailwind.css';
This adds the colours (text-dim, bg-void, ...), font-pixel,
text-cell, text-cell-2x and the utilities pane, pane-title,
inverse, alarm, warn, cells, tty-line, cursor-blink and
w-fill. It keeps your theme, but it sets --spacing: 1ch, so p-2 is
two cells. Use these utilities inside a TermRoot, and keep the
@f0ursqu4r3/term0/style.css import.
HTML and htmx
Vue is the main target. term0 also works without Vue, for server-rendered pages and htmx. It has two parts:
- CSS classes. The server renders plain HTML with the classes the Vue components use.
- Custom elements for the parts that need JavaScript, from
@f0ursqu4r3/term0/elements.
With a bundler:
import '@f0ursqu4r3/term0/style.css'
import '@f0ursqu4r3/term0/elements'
Without a bundler, serve the whole dist/ directory of the package. elements.js imports files from dist/chunks/.
<link rel="stylesheet" href="/term0/style.css" />
<script type="module" src="/term0/elements.js"></script>
The elements use light DOM, so your CSS and htmx swaps reach their contents. The elements keep their state in attributes, so the server can render any state.
CSS-only components
Put class="t0-root" on a container, or on <body> with @f0ursqu4r3/term0/base.css.
The spaces inside these tags render: t0-line and t0-cursor keep white space.
<div class="t0-root">x</div>
<section class="t0-pane"><h2 class="t0-pane-title">Status</h2>x</section>
<div class="t0-line"><span class="t0-tone-dim">LINK </span><span class="t0-tone-ok">UP</span></div>
<button type="button" class="t0-button t0-button-inverse">RUN</button>
<button type="button" class="t0-button t0-hover-inverse">RUN</button>
<span class="t0-badge t0-tone-ok">NEW</span>
<kbd class="t0-kbd">Esc</kbd>
<div class="t0-rule" role="separator" aria-label="ALERTS"><span class="t0-rule-label">ALERTS</span></div>
<div class="t0-alert t0-alert-warn" role="status"><span class="t0-alert-title">warn</span>DISK 91%</div>
<span class="t0-cursor"> </span>
Tones: t0-tone-dim, -faint, -ok, -warn, -alarm, -info, -inverse, -inverse-alarm,
-inverse-warn.
Elements
| Element | Purpose |
|---|---|
<t0-meter value total width bracketed> |
TMeter. bracketed="false" drops the brackets. |
<t0-sparkline values> |
TSparkline. values is numbers separated by spaces or commas. |
<t0-spinner frames interval label> |
TSpinner. frames is braille, line, block or a string of frame characters. Put a visible label after the element. |
<t0-tabs value> |
Behaviour for server-rendered tabs. Fires a bubbling t0-change with detail.value. |
<t0-chart values sample ...> |
TChart with a live buffer. Props in kebab case. values separates series with ;. tones and labels give each series a tone and a label. thresholds is 80:warn:rule, 95:alarm. x-axis is 0:-60s, 59:now. |
<t0-bars values sample ...> |
TBars with a live buffer. Props in kebab case. tone is one tone for every bar. |
<t0-log height ansi ...> |
TLog: wrap on the cell grid, virtual rows, follow mode. See Log with htmx. |
<t0-progress value total width rate unit label> |
TProgress: a bar with its percent. rate (units per second) adds the rate and the ETA. |
<t0-heatmap values min max labels shades legend> |
THeatmap: a grid of shade glyphs. values="1 2 3; 4 5 6". |
<t0-toasts corner width limit> |
TToasts: a stack of TAlert toasts in a corner. Add toasts from script, child rows or the t0-toast event. |
<t0-dialog open label> |
Behaviour for a server-rendered TDialog: opens and closes the native <dialog>, fires t0-close. |
<t0-tree label expanded selected> |
TTree from a nested list: guides, markers, keys, t0-toggle and t0-select events. |
<t0-table selected> |
Sort and row selection for a server-rendered TTable: t0-sort and t0-select events. |
A swap that clears the children of t0-meter, t0-sparkline, t0-spinner, t0-chart,
t0-bars, t0-progress or t0-heatmap makes them draw again.
t0-chart and t0-bars keep their samples on the element, so a morph keeps the history.
el.push(...values) appends to the first series and el.pushTo(index, ...values) to another.
The sample attribute appends one value to each series: sample="42;61". A morph sets an
attribute only when its value changes, so put a sequence number before an @ to repeat a
value: sample="17@42;61". capacity sets the buffer size. The default is width times the
samples per cell, or 120.
Log with htmx
<t0-log> reads each child element as a new entry and removes it, so htmx can append rows.
data-time and data-level (debug, info, warn, error) set the columns. A
t0-tone-* class inside a row sets the tone of its text. Other markup becomes plain text.
Row text collapses white space as HTML does. A <pre> row keeps its text as it is.
<t0-log height="10" hx-get="/log/next" hx-trigger="every 2s" hx-swap="beforeend"></t0-log>
The server returns rows such as
<div data-time="12:00:01" data-level="warn">disk <span class="t0-tone-alarm">91%</span></div>.
Attributes: height (lines, default 10), ansi, ansi-colours (tone or palette),
overscan (default 5) and limit (keep the newest entries; 0, the default, keeps all). With
ansi, the text of each row is parsed for ANSI codes. From script, el.push(...entries) adds
LogEntry objects, el.write(chunk) adds a raw stream, and el.clear() empties the log.
The element owns its children. Append with beforeend. An innerHTML swap (the htmx
default) replaces every entry with the new rows, so a poll can return the whole tail. A
morph or an outerHTML swap replaces the element and its entries.
Tabs with htmx
The examples use htmx 4. The server renders the tabs. Give each tab a value, its own hx-get
and its own hx-target: htmx 4 does not inherit attributes by default. Arrow keys click the
tab, so the request fires for the keyboard too.
<t0-tabs value="disk">
<div class="t0-tabs-list" role="tablist" aria-label="Views">
<button role="tab" value="disk" hx-get="/panel/disk" hx-target="#panel">Disk</button>
<button role="tab" value="log" hx-get="/panel/log" hx-target="#panel">Log</button>
</div>
<div id="panel" class="t0-tabs-panel" role="tabpanel" tabindex="0">…</div>
</t0-tabs>
t0-change bubbles, so a t0-tabs inside another tab panel also reaches the outer one. Filter
with hx-trigger="t0-change[target === this]" if you listen on the outer element.
Palette without Vue
applyStoredPalette() applies the palette usePalette stores, so Vue and htmx pages on one
origin share it. For one root, render the --term-* properties from termProperties(palette)
in its style attribute.
<script type="module">
import { applyStoredPalette } from '/term0/elements.js'
applyStoredPalette()
</script>
Develop
bun install
bun run dev # showcase at #/, scoped demo at #/embedded, grid-snapped root at #/absolute
bun run build:playground # static showcase in playground/dist
bun run test
bun run typecheck
bun run lint
bun run fmt:check
bun run build # writes dist/
The showcase takes ?preset=phosphor (or monotheme, amber,
paper) to open with a palette, ?palette to open the editor, and
?absolute to open snapped to the window grid.
Release
dist/ is not in git. The release workflow builds it and publishes to npm.
- Set
versioninpackage.jsonand commit. - Tag
vX.Y.Zand push the tag. The tag must matchversion.
The workflow runs lint, format check, typecheck and tests, then
npm publish. It uses npm trusted publishing, so the repo stores no npm
token.
Licence
MIT. The Departure Mono font is by Helena Zhang, under the SIL Open Font
License 1.1 (src/fonts/LICENSE, also shipped in dist/fonts/).
Related Packages
Application-level component library for vui-rs terminal UIs — dialogs, fuzzy sel...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.4.0 | ^3.5 | MIT | |
| 0.3.0 | ^3.5 | MIT | |
| 0.2.0 | ^3.5 | MIT |