svtui

v0.2.0

An open-source, minimal Svelte component repository for building web applications and static sites with terminal aesthetic. Inspired by srcl.

Components are self-contained source files you copy into your own project. Use the CLI npx svtui add <name> or browse from the sidebar.

Install

Run the CLI from your project root. It writes a token preset and a font into your app.

init

001 npx svtui init

That drops src/app.css and a woff2 font file under static/fonts/. Import the css in your layout so the tokens load. The css file is the only shared bit; no runtime package to add to your dependencies.

Add a component

add

001 npx svtui add button

A button.svelte file lands in src/lib/components/. Import it like any other Svelte file. Multi-file components (the table, for instance) go into their own folder.

Each file stands alone. It reads the tokens from app.css and imports nothing from outside svelte. You can read one and understand the whole thing. Edit it however you like.

Theme

All colors are css custom properties in app.css. Override them at the root, or on a wrapper, to re-skin everything at once.

app.css

001 :root {
002 --surface-base: rgb(0, 0, 0);
003 --text-primary: rgb(235, 235, 235);
004 --focus-ring: rgb(239, 99, 0);
005 }

Put a .dark block next to :root for a dark variant, then toggle the class on <html> or <body>. That is the whole theme system. There is no javascript in it.

The grid

Every svtui component sits on a character grid. Two tokens define it — a cell is 1ch wide and one line tall (a real terminal cell is taller than it is wide):

tokens

001 --cell: 1ch; /* one character wide */
002 --line: calc(var(--font-size) * var(--base-line-height)); /* one line tall */

Three rules keep composed UIs aligned. Flip the GRID toggle in the sidebar to see them hold.

1 · Integer lines only

Vertical spacing and heights are whole lines. A half-line seam lands mid-row and breaks alignment — step up to a full line instead.

do

001 padding: var(--line) 2ch;
002 height: calc(var(--line) * 3);

don't

001 padding-top: calc(var(--line) * 0.5); /* half-line */
002 margin-top: 12px; /* px */

2 · Strokes are box-shadows

A border: 1px shifts the content below it off the grid and cascades to every child. Draw strokes with box-shadow — inset to paint inside, outset to paint beside. Card and Table both do this.

do

001 box-shadow: inset 0 0 0 1px var(--border-default); /* frame */
002 box-shadow: 0 1px 0 0 var(--border-muted); /* divider */

don't

001 border-bottom: 1px solid var(--border-muted); /* shifts content 1px */

3 · One text cell

Text is one cell: font-size: var(--font-size), line-height: var(--line). A larger font-size has a taller line-box, putting that element off the grid. Build hierarchy with uppercase, weight, and spacing — not size.

Composing

Stack with a var(--line) gap (or a multiple); keep horizontal spacing in ch. Reach for the structural primitives — Row, RowSpaceBetween, Block, Indent — before writing new layout CSS.