Introducing svelterm: Svelte in the terminal, with real CSS
svelterm renders Svelte 5 components to the terminal:
standard components with <style> blocks, laid out by a CSS engine
that implements the cascade, flexbox, grid, animations and form controls on a grid of
character cells. The same source
renders in a browser. The playground shows both targets
side by side, and every demo also runs in a real terminal:
curl -fsSL https://svelterm.dev/run/counter.mjs | node --input-type=module - One disclosure before the story: svelterm is largely LLM-written. I designed and directed it, and most of the code was produced in pair-programming sessions with Claude, test-driven and reviewed as it landed. If that is a dealbreaker for you, there are plenty of artisanal, hand-typed frameworks out there. Or if you're really not happy with the way computers are going, maybe try going outside and getting some fresh air.
Why
Like a lot of people I have been enjoying the renaissance in terminal applications that tools like Claude Code kicked off. I was surprised to learn how many of them are built with React, via the Ink library. Things have moved on from curses. But the experience has rough edges: I kept hitting flicker and full re-renders in longer Claude Code sessions, and I wanted to understand whether that pain was inherent to the approach or incidental to it.
I also had a hunch about styling. Every TUI framework invents its own styling system: Ink has JSX props, Bubble Tea is imperative, Ratatui is Rust structs. CSS is the styling system people already know, and (not incidentally these days) the one LLMs have absorbed from training on the entire web. A terminal framework that spoke real CSS would need no new vocabulary from either.
The Go detour
My first attempt was not Svelte. While building Hubcap I wanted an interactive terminal UI, and Svelte had no custom renderer support at the time, so I prototyped Sumi: a Go framework with Svelte-like templates and fine-grained reactivity, compiled to plain Go. Sumi is unreleased and I plan to come back to it, but it lives on inside Hubcap, and it proved the interesting part of the idea. Real CSS, adapted where the terminal demands it (cell units, box-drawing border styles), works well on a cell grid and is pleasant to write.
What Sumi could not give me was portability. Its templates compile to Go, so the terminal is where they live, and running them on the web would mean WASM for no good reason. By the time I had convinced myself of that, Paolo Ricciuti's experimental custom renderer API for Svelte had appeared. That changed the proposition: one component source, rendered to the DOM in a browser and to a cell grid in a terminal.
What it does
svelterm supplies a renderer that builds a tree of plain node objects instead of DOM nodes,
then runs a browser-shaped pipeline over it: style resolution, layout, paint to a cell
buffer, diff, and minimal ANSI output. There is no DOM anywhere, emulated or otherwise
(how it works).
The compatibility rule: any HTML or CSS feature with a sensible meaning on a
character grid works the way you would expect from a browser, and anything pixel-derived
parses and is silently dropped. In practice that covers the cascade and selectors
(including attribute matchers and ::before/::after), flexbox,
grid with named areas, tables, keyframe animations and transitions, form controls with
browser-like keyboard and mouse behaviour, and dark/light schemes via prefers-color-scheme. Where the two targets genuinely differ you write @media (display-mode: terminal) and @media (display-mode: browser) blocks in the same stylesheet. The full support matrix is in the docs.
The playground is the quickest way to get a feel for it:
an editor with live previews of the terminal and browser renders side by side. The terminal
pane is itself svelterm output, fed into a VT100 emulator in the browser. If you would
rather trust a real terminal (I would), the curl one-liners under svelterm.dev/run.txt pipe each demo into Node.
Piping a script into node - normally costs you interactivity because stdin is
the script; svelterm reopens the controlling terminal for input, so Tab, Enter and the
mouse keep working.
How it was built
The working method was consistent throughout: I own the design, direction and taste, and the agent does most of the typing. Each feature started as a design doc with the work sliced thin, each slice landed test-first (the suite is over 1,100 tests now), and verification went beyond the unit tests: driving the playground in a browser, and driving the bundles in tmux with real key events. That last habit paid for itself. Testing only in the browser emulator had me nervous, and the first time I piped a bundle into a real terminal it caught an API break that no browser test could see.
Status
svelterm is an early preview and depends on an unmerged Svelte branch, so you cannot use it with mainline Svelte yet. Feedback on the terminal rendering is very welcome: github.com/tomyan/svelterm.