svelterm feature reference

svelterm renders Svelte components to a terminal cell grid with HTML/CSS semantics. This is the authoritative list of what is supported, what is approximated, and what is deliberately ignored. Standard features link to MDN rather than re-explaining them; terminal-specific behaviour is documented in full here.

The rule: any feature with a sensible meaning on a grid of character cells works the way a browser author expects. Features with no cell-grid meaning (pixels, fonts, sub-cell geometry) parse and are silently dropped — never a crash. Target modes separately with @media (display-mode: terminal) / @media (display-mode: browser).


Terminal-specific extensions

These do not exist in browsers and are svelterm’s own surface.

The cell unit

One terminal cell — the atomic unit of layout. width: 20cell, padding: 1cell 2cell. ch is accepted as an exact alias (1ch = 1cell), which lets stylesheets work in both modes. All lengths round to whole cells. Unitless 0 is valid.

@media (display-mode: terminal | browser)

Reuses the standard display-mode media feature with two custom values. The terminal renderer evaluates terminal as true; real browsers never match either custom value, but a browser block simply applies as normal CSS there. This is the documented pattern for anything in the ignored bucket:

.card {
    @media (display-mode: browser)  { box-shadow: 0 2px 8px #0004; }
    @media (display-mode: terminal) { border: single; }
}

Border styles

border-style takes terminal-native values drawn with box-drawing and block glyphs (browser values like solid/dashed are ignored — use a display-mode block):

ValueLook
single┌─┐ light box drawing
double╔═╗ double lines
rounded╭─╮ rounded corners
heavy┏━┓ heavy lines
ascii+-+ plain ASCII
eighth-cell-inner / eighth-cell-outerthin eighth-block edges inside/outside the cell
half-cell-inner / half-cell-outerhalf-block edges
full-cellfull-block frame

Borders are 1 cell thick. border-top/right/bottom/left: true|false enable individual sides. border-corner: h | v | none picks corner character bias. border-color works as standard (including currentColor).

Colour on a terminal

Resolved colours are either ANSI palette names (red, cyan, …) or 24-bit truecolor. CSS keywords that match the 8 basic ANSI names map to palette entries (themeable by the user’s terminal); explicit hex/rgb() etc. stay exact. transparent means “no colour set” — the parent’s background shows through. The terminal’s own dark/light scheme is detected via OSC 11 polling (overridable through run({ colorScheme })) and drives light-dark() and @media (prefers-color-scheme: …).

opacitydim

There is no alpha channel. opacity below 1 applies the terminal’s dim attribute; the non-standard value opacity: dim does the same explicitly.

<svt-region>

A paint primitive that fills its layout box from a consumer-provided cell source (used to embed full terminal emulators). It fires a resize event with { cols, rows } when its allocated size changes. Without an explicit size it fills the parent box like a replaced element.

Focus and keys

Tab/Shift+Tab cycle focusable elements (button, input, textarea, a, select, summary); disabled controls are skipped. The focused element matches :focus. Enter clicks the focused element (opens href on links, toggles <details>, cycles <select>); Space toggles checkboxes/radios and cycles selects. Mouse clicks focus, click, and activate the same defaults. Clicking a <label> activates its control — wrapping or for="id" association both work. Ctrl+C exits.

Text editing

Focused input/textarea fields have readline-style editing (the same keymap as sumi’s editable elements):

KeysAction
/, Ctrl+B/Ctrl+Fmove by character
Alt+B/Alt+F, Ctrl+←/Ctrl+→, Alt+←/Alt+→move by word
Home/End, Ctrl+A/Ctrl+Estart / end
Backspace/Ctrl+H, Delete/Ctrl+Ddelete char (Ctrl+D exits instead when exitOn includes it)
Ctrl+K / Ctrl+Ukill to end / to start
Ctrl+W, Alt+Backspacecut the selection if one is active, else kill the word before the cursor
Alt+Dkill the word after the cursor
Ctrl+Y / Alt+Yyank the last kill / cycle older kills right after a yank
Ctrl+Ttranspose the characters around the cursor
Ctrl+_undo (Ctrl+Z suspends, as in a shell)
Shift+movementextend the selection (word variants included)
Alt+Wcopy the selection

Kills feed a kill ring; cut/copy also write the system clipboard (OSC 52 plus the platform tool). Typing, Enter, or paste replaces an active selection; Backspace/Delete remove it. Clicking an input places the caret; double-click selects the clicked word and copies it, like the screen-space selection. Words are whitespace-delimited.

In a textarea the same keymap gains lines: Enter inserts a newline, / move by line (column clamped to the target line, as in sumi), Shift+↑/↓ extends the selection, the view scrolls to follow the caret, and the selection highlight paints across rows. input fires on value changes; caret-only movement fires selectionchange with { value, cursor } for status bars.

Interaction model for form controls

  • Checkbox renders [x] / [ ]; radio renders (•) / ( ) (3×1 cells). Radios group by name across the tree.
  • <select> is a popup-less cycling control: it renders the selected option’s label plus , sized to the longest option. ArrowUp/ ArrowDown move the selection (wrapping); Space/Enter/click advance. There is no dropdown popup — it has no good cell-grid answer.
  • <progress>/<meter> render as 20×1 block-glyph bars ( fill with eighth-block partials, track), stylable via color/ background, sized via width/height.

Events

W3C-style capture/bubble dispatch on the component tree: click, keydown, input, change, paste, toggle (details), resize (svt-region), plus mouse events with cell coordinates. Event payloads ride on event.data (e.g. { value, cursor }, { checked }).


HTML elements

Standard semantics unless noted. Anything unlisted renders as a plain block/inline box per its display default.

ElementNotesReference
headings, p, div, span, lists, blockquote, pre, code, hrUA-styled like a browser (margins in cells, hr as rule, list markers)HTML elements
strong/b, em/i, u, s/del, mark, kbd, abbr, samp, vartext attributes (bold/italic/underline/strikethrough/colour)
aunderlined, focusable; Enter/click opens href in the local browser<a>
table and friendsfull table layout: colspan/rowspan, header/footer groups, caption, colgroup/col width hints, collapse/separate borders, empty-cells<table>
input (text)single-line editor with cursor, value, input events; maxlength, readonly<input>
textareamultiline editor: Enter inserts, ↑/↓ move by line (column-clamped), scroll follows the caret, selection paints across rows; shares the whole editing keymap; selectionchange fires on caret-only moves with { value, cursor }<textarea>
input type="password"value masked as bullets; editing as textpassword
input type="checkbox" / "radio"glyph toggles; checked attribute/property; change+input eventscheckbox, radio
textareamulti-line editing<textarea>
buttonfocusable, centred text, click on Enter/click<button>
select/option/optgroupcycling control (see above); change+input with the option value<select>
progress, meterblock-glyph bars; value/max (+min for meter); no-value progress renders track only<progress>, <meter>
details/summary▶/▼ disclosure, open attribute, toggle event, focusable summary<details>
imghalf-block pixels (▀) from PNG file paths / data:image/png URIs; real pixels via the kitty graphics protocol where supported; sized by CSS width/height<img>
video, canvas, iframenot rendered

CSS selectors

All standard matching semantics. Reference: MDN selectors.

  • Type, .class, #id, *, selector lists (a, b)
  • Combinators: descendant, >, +, ~
  • Attributes: [a], [a=v], [a^=v], [a$=v], [a*=v], [a~=v], [a|=v] (quoted or unquoted values)
  • Pseudo-classes: :root, :focus, :hover (mouse), :first-child, :last-child, :only-child, :empty, :first-of-type, :last-of-type, :only-of-type, :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() (full An+B), :not(), :is(), :where(), :checked, :disabled, :enabled
  • Pseudo-elements: ::before, ::after (single-colon legacy accepted) with content: strings, attr(x), counter(name) (with counter-reset / counter-increment, including explicit amounts), space-separated concatenation, and none/"". Counters use a flat namespace — no per-scope nesting or counters() joining — and update on full style resolution, so an incremental restyle can serve stale numbers until the next full pass. Pseudo boxes are inline and invisible to :empty/:nth-*. In table-internal boxes they render per §17.2.1: a pseudo on a row or table box becomes an anonymous cell/row.
  • Specificity, source order, and inline-style precedence follow the cascade.

CSS properties

Grouped; all standard behaviour (per MDN) except the noted grid adaptations. Lengths are cells (cell/ch, %, or calc()).

GroupProperties
Colour & textcolor, background/background-color, font-weight (≥700 = bold), font-style (italic), text-decoration (underline/line-through), text-transform, text-align, text-overflow (ellipsis, plus non-standard ellipsis-middle), white-space (normal/nowrap/pre), opacity (≈dim), visibility
Box modelwidth, height, min/max-width, min/max-height, padding(-*), margin(-*) (incl. auto centring and margin collapse), box-sizing, overflow (hidden/scroll/auto with real scrolling + fading scrollbars; wheel and arrow/PageUp/PageDown keys scroll)
Display & flowdisplay: block, inline, inline-block, flex, grid, none, contents, all table display types
Flexboxflex-direction (all four), flex-wrap, flex/flex-grow/flex-shrink/flex-basis, gap, justify-content (incl. space-*), align-items, align-self, order
Gridgrid-template-columns/rows (cell/ch/%/fr, repeat(), minmax()), grid-template-areas + grid-area (named and numeric), grid-column, grid-row (start / start‑end / span n), gap, grid-auto-flow: row \| column (column flow wraps at the explicit row count; implicit columns take the last explicit column’s width). Fractional minmax() minimums redistribute: a track clamped to its minimum leaves the pool and the freed space re-splits among the rest
Positioningposition: static/relative/absolute/fixed/sticky with top/right/bottom/left, z-index. Relative offsets shift visually without moving flow; sticky is top-edge only inside scroll containers (no push-out at the containing block end)
Tablesborder-collapse, border-spacing, caption-side, table-layout, empty-cells, vertical-align (baselinetop)
Bordersborder/border-style/border-color/border-corner + per-side toggles (terminal values above). border-collapse: collapse on a container (inherited — :root works) makes adjacent bordered siblings in block flow, flex, and grid share a single border line with junction glyphs ( ) — a cell-grid extension of the table property
Animationanimation shorthand, animation-name/-duration/-iteration-count (incl. infinite)/-timing-function, @keyframes (from/to/percentages, values resolve var()/light-dark())
Transitionstransition shorthand with per-property comma groups; transition-property/-duration/-timing-function longhand lists paired per spec; interruptions continue from the current value
Easinglinear, ease (default), ease-in, ease-out, ease-in-out, cubic-bezier(), steps(), step-start, step-end

Animation & transition semantics on the grid

Colours interpolate in RGB at ~30fps; single cell/ch lengths interpolate to whole cells (movement steps cell by cell); every other supported property applies discretely, switching at the segment midpoint (the CSS rule for non-interpolable values). Layout-affecting animations re-flow each frame. Easing applies per keyframe segment (a timing function declared inside a keyframe overrides the element’s for that segment); non-interpolable values switch when eased progress crosses the midpoint. Transitions run per property with their own duration/timing; interruptions continue from the current blended value. Keyframe var()/light-dark() re-resolves on scheme/custom-property changes without restarting the animation. Deviation: no transition-delay / animation-delay.

Values, functions and at-rules

  • Units: cell/ch, %, unitless 0, fr (grid). Everything pixel-derived is dropped (see below).
  • var() custom properties with inheritance and fallbacks.
  • calc(), min(), max(), clamp() over cells and %.
  • Colours: hex (3/6/8 digit), rgb()/rgba(), hsl()/hsla(), hwb(), lab(), lch(), oklab(), oklch() (legacy and modern syntax), all 148 named colours, transparent, currentColor, light-dark(). Alpha composites at paint time (blends over the cell beneath), and numeric opacity acts as a blend factor.
  • Keywords: inherit, initial, unset.
  • At-rules: @media (prefers-color-scheme, display-mode, min/max-width, min/max-height — in cells), @container (size queries against the nearest laid-out ancestor), @supports (property-name checks), @keyframes. Nested rules inside declarations (& {}-less media nesting) are supported.

Ignored (no cell-grid meaning)

These parse and are dropped silently. Use @media (display-mode: browser) for the browser-side styling and a terminal-native equivalent if needed.

px/em/rem/ex/vw/vh lengths · font-size, font-family, line-height, letter-spacing, word-spacing · border-radius (use border: rounded), box-shadow, outline, filter, backdrop-filter · transform and friends · background-image, gradients · float · @font-face, @page.