crysterm
Version, currently 1.0.215 versions
- 1.6.2latestOct 18, 2026
- 1.6.1Oct 18, 2026
- 1.6.0not indexedOct 18, 2026
- 1.5.0Oct 13, 2026
- 1.4.3not indexedOct 13, 2026
- 1.4.2not indexedOct 13, 2026
- 1.4.1not indexedOct 13, 2026
- 1.4.0not indexedOct 13, 2026
- 1.3.0not indexedOct 13, 2026
- 1.1.1not indexedOct 13, 2026
- 1.1.0not indexedOct 13, 2026
- 1.0.2not indexedOct 13, 2026
- 1.0.1not indexedOct 13, 2026
- 1.0.0Aug 17, 2026
- master branchAug 15, 2026
github.com/crystallabs/crysterm
Console / terminal GUI toolkit for Crystal.
Nothing has been indexed for 1.0.2 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
crysterm:
github: crystallabs/crysterm
version: ~> 1.0.2Then run:
shards installshard.yml
No shard.yml has been indexed for 1.0.2. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
Crysterm is a console/terminal toolkit (TUI), inspired by Qt, Blessed, and Blessed-contrib.
It is implemented in Crystal. Apps using Crysterm can be written using AI.
Apple silicon hardware is recommended for development, for quickest iterations.
Main features:
Widgets
More than 90 concrete widgets with Qt-modeled APIs and behavior. Some demoed below:
Sources: tests/misc/widgets.cr and tests/misc/widgets2.cr. See also the full MainWindow chrome (menus, toolbars, docks, tabs) in tests/misc/qt_widgets.cr, per-widget demos under tests/widget/, and the directory with widgets in src/widget/.
Layouts
As in Qt, widgets are best positioned by layout engines rather than by absolute coordinates. There are 12 layouts supported.


Sources: tests/misc/layouts.cr and tests/misc/layouts2.cr. See also per-layout demos under tests/layout/, and the directory with layouts in src/layout/.
CSS and QSS styling
A complete CSS engine is used for styling -- paddings, margins, borders (including per-side and radius), colors, shadows, opacity, transitions, and pixel measures (when possible) translated to cells via the terminal's real cell geometry.
It also reads Qt QSS dialect directly — unmodified desktop Qt themes can be used to style apps.
Six independent windows below run the same scene, each loaded with a different theme from data/css/.
Source: tests/misc/themes.cr
For the minimal author-your-own-stylesheet form — one CSS string styling a
whole app, including :focus restyling — see
examples/css/css.cr.
Borders, Shadow, and Light
The full presentation of borders, shadows, and light can be seen in README-borders.md.
Source: tests/misc/styling.cr, tests/misc/styling2.cr, and other numbered styling files in tests/misc/
Rich text — Markdown with GFM
TextDocument (a QTextDocument work-alike) supports Markdown
(CommonMark + GitHub-Featured Markdown), HTML, and Crysterm's native tags, and
TextEdit/TextBrowser render it: headings, bold/italic/strikethrough,
inline code and links, fenced code blocks, blockquotes, GFM tables laid out
as real box-drawing tables, GFM task lists, and GFM alert admonitions —
colored through the stylesheet (TextEdit::heading { color: … }), editable
with full undo, navigable links.
Below is an example of a Claude-style session that is streaming a reply in Markdown (note: this is not Claude, it is Crysterm's Claude-like example):
Source: examples/claude/claude.cr
Syntax highlighting
SyntaxHighlighter mirrors Qt's QSyntaxHighlighter. Syntax formats overlay
the text without touching content, undo or export; multi-line constructs carry
state between blocks, and multiple highlighters can stack on one document.

Source: tests/misc/syntax.cr
Full Unicode
Unicode-native end to end: grapheme clusters (ZWJ emoji, combining marks), wide CJK cells, ambiguous-width resolution probed from the live terminal, East Asian width, and glyph chrome that auto-upgrades on modern-font terminals.
Icons and glyph chrome exist in up to three renditions, and detection is automatic:
- ASCII — plain 7-bit fallback characters, for terminals/fonts that would otherwise show tofu.
- Monochrome (GNU Unifont) — the deterministic bitmap face used by the built-in capture facility: captures are rasterized in-process with Unifont, so they look the same everywhere, independent of any real terminal or font.
- Full-color — in a live terminal, emoji glyphs are drawn by the terminal's own color emoji font.
All Crysterm captures shown in this document are the middle variant; run
the examples in a modern terminal to see the full-color renditions. For
example, the "Save" icon (Glyphs::Role::IconSave) in all three:
| ASCII | Capture (Unifont) | Terminal |
|---|---|---|
s | 💾 |
Source: examples/text/editor/editor.cr
Native TrueColor
Colors are natively stored as full 24-bit RGB and reduced to the terminal's real capability only at output time (16.7M → 256 → 16 → 8 colors, automatically). Alpha compositing blends colors per channel in RGB space; translucent widgets, soft shadows, and smooth gradients are supported.
Source: tests/misc/truecolor.cr
Hardware and artificial cursors
Cursor shape (block / underline / bar), blink, and color are settable
per window and per widget — each focused widget can present its own cursor.
When the terminal can't style its hardware cursor (or a custom glyph/style is requested), Crysterm transparently composites an artificial cursor into the cell buffer instead.
Source: tests/misc/cursors.cr
Border junctions with color blending
Adjacent and overlapping borders can merge into shared junction glyphs/chars
(├ ┬ ┼ …). When the touching borders differ in color, the difference can
be ignored or blended (for the smoothest seam), or the merge can be skipped.
Source: tests/misc/junctions.cr
Parseable tags in strings
Any widget content can carry inline tags (when parse_tags: true), for in-band
colors ({red-fg}, {#57c7ff-bg}), attributes ({bold}, {underline},
{inverse}, {blink}), and alignment
({center}, {right}, and {|} for left/right split).
Source: tests/misc/tags.cr
Loadable fonts
BitmapFont loads GNU Unifont .hex and ttystudio .json fonts —
BigText renders text with them in the terminal, and the capture pipeline
rasterizes screenshots through them. Terminus and Unifont faces ship in
data/font/.

Source: tests/misc/fonts.cr
Rich media — images, animated images, and video
One Media widget, twenty-one backends, selected automatically per terminal
and per content (--media-backend=auto) or forced by config/CLI/env/code:
- In-band pixels — Kitty graphics, iTerm2 inline images, Sixel, ReGIS
- Sub-cell glyphs — octant (2×4), braille (2×4), sextant (2×3), quadrant (2×2), half (1×2), block, ASCII
- ANSI cells — TrueColor, 256, 16, 8 colors
- External — w3m overlay, überzug, Tektronix 4014
Formats: PNG, APNG, GIF (stills and animations), JPEG (iTerm pass-through),
ANSI/BBS art (.ans, .nfo, …), and http(s):// sources. Fit modes:
stretch, contain (aspect-preserving), cover, none.



Sources: tests/misc/media_graphics.cr, tests/misc/media_glyph.cr, tests/misc/media_ansi.cr
APNG and GIF animations play in every backend, with per-frame delays honored and multiple widgets optionally driven in lockstep from one shared timer — here the same GIF in four backends at once:
Source: tests/misc/animated.cr — see also tests/misc/netscape.cr
Videos play through the same widget via ffmpeg (Widget::Video): mp4,
mkv, webm, mov, avi, mpeg, ts, 3gp and more, with eager or constant-memory
streaming decode (media.video_decode=auto|eager|stream):
Source: tests/misc/video.cr
Terminal feature auto-detection
At startup Crysterm probes the live terminal — truecolor (DECRQSS), graphics protocols (Kitty/iTerm/Sixel), Unicode and ambiguous width, palette and default colors, cursor styling, kitty keyboard protocol, in-band resize, pixel mouse, cell pixel geometry — and automatically picks the best supported settings.
E.g. the same gauges render as real pixels on a Kitty-graphics terminal and downgrade to braille sub-cells on a plain terminal. All autodetected, no user action required.

Source: tests/misc/detect.cr — probe internals:
Tput#probe!, Tput::Features, Tput::Emulator.
Everything in the toolkit tunable
Every internal setting in the toolkit has a default value and can be overriden.
Unless left at (pre-set or auto-detected) defaults, values can be specified in config file
(~/.config/crysterm/config.yml), env (CRYSTERM_*), CLI flag, or code —
currently ~70 total low-level settings available in any Crysterm program
out of the box.
crystal run app.cr -- --crysterm-dump-config=pretty # every option + provenance
crystal run app.cr -- --media-backend=sixel # force a graphics backend
CRYSTERM_MEDIA_BACKEND=kitty crystal run app.cr
Crysterm reads these from a copy of ARGV without consuming anything, and its
two framework flags are namespaced --crysterm-config /
--crysterm-dump-config, so an app's own --config stays its own.
Multiple screens
One process can drive several completely separate terminal screens.
Below, a value assigned on the left screen updates the right one, running inside a single program but on different TTYs.
Source: examples/screen/multiple/multiple.cr
Full mouse support
All the protocols — X10, SGR (1006), URxvt (1015), SGR-Pixels (1016, sub-cell pixel coordinates), and GPM on the Linux console — with hover, enter/leave, capture, double/triple-click counting, wheel scrolling, focus reporting (1004), and a GUI pointer shape over hovered widgets (OSC 22).
Real drag & drop with MIME-typed payloads (text/uri-list, …),
Move/Copy/Link actions negotiated by modifiers, and the same sessions driven
by keyboard. The editor demo above opens its menu and scrolls by mouse.
Paste buffer, kill-ring, undo/redo
Text widgets share a process-wide Emacs kill-ring (C-k/C-u/C-w
kill, C-y yank, consecutive kills merge — kill in one field, yank in
another), a full undo/redo stack on the document model (C-z/M-z,
grouped edit blocks, format-preserving), GUI clipboard keys (C-c/C-x/
C-v), bracketed paste (DEC 2004), and the system clipboard over
OSC 52 — copy/paste that works through SSH and tmux.
Reactive programming
An opt-in app-state layer — the toolkit itself never requires it, and
plain property assignment always works. When you want it: a fine-grained
reactive system (SolidJS-style): Reactive::Property,
Reactive.computed, effects with automatic dependency tracking,
Reactive.bind scoped to any event handler's lifetime, ObservableList for
collection views, batching, and a reactive_property macro for widget classes.
Assign property.value = x — every bound widget repaints itself:
Source: tests/misc/reactive.cr — full tour: tests/reactive/reactive.cr
High-performance rendering
The renderer supports compositing and damage tracking, and automatically uses whichever is faster for the workload.
Output is a minimal cell-level diff against what the
terminal already shows, with CSR/BCE scroll optimizations and optional DEC
2026 synchronized output. The FPS widget reports render/draw/flush times
and terminal bandwidth live:
Source: tests/misc/quicktro.cr — see also tests/misc/concurrent_rendering.cr
Screen capture
Any window or single widget can record itself and produce a still PNG
(encoded in-process, no external tools), any other format — APNG, GIF,
MP4, WebM, JPEG, … — by piping frames to ffmpeg, and a textual .dump
format, including live recording of a running UI at a chosen fps and
compositing of in-band graphics (sixel/kitty/iterm) into the capture.
Setting
CRYSTERM_SHOT / CRYSTERM_ANIM / CRYSTERM_DUMP makes any Crysterm
program capture itself headlessly — every image on this page was produced
that way.
Source: tests/misc/minesweeper.cr — the game from examples/games/minesweeper/minesweeper.cr, playing itself through the scripted input paths below
Scripting and automation
Scripted input goes through the exact code paths that real input uses —
window.press 'x' / window.press Key::Down / window.type "hello" for
keys, window.click x, y / window.wheel :up, x, y for the mouse (with
window.emit some_event and window.dispatch_mouse ... as the general
escape hatches).
The
bundled harness (tests/widget/example.cr) builds
on that with a scripting driver — d.key, d.type, d.click, d.act,
dwell timing — used by ~200 widget demos to film themselves, and equally
usable for in-app automation and testing.
Direct (inline) mode
Apps can also run on the command line without taking over the screen. Two
flavors: Crysterm::Direct for styled printing into the normal scrollback
and inline windows (Window.new inline: true, optionally
auto_grow: true, max_height:).
The complete widget stack, popups and
all, anchored at the shell cursor like fzf. For example, a completer on a command line:
Source: examples/direct/completer/completer.cr
(Note: the above feature is currently experimental.)
Remote control
Built with -Dremote, an app exposes its widget tree as a DOM over
HTTP: JSON-RPC commands (setContent, addClass, focus, append,
query, snapshot, …) addressed by full CSS selectors.
UI events are streamed out over Server-Sent Events — so app behavior can live in another process, written in any language.
The layout side works without the network,
too: Window#load_layout builds the UI from HTML + CSS, queryable and
updatable in-process:
Source: tests/misc/dom.cr — the bundled crysterm run app.html --handler "python3 app.py" CLI is in src/remote/bin/.
(Note: the above feature is not browser output in HTML; it is control of a native program/TUI from an external program, and is currently experimental.)
Ready-to-use example apps
Programs under examples/ are complete, usable applications, meant as templates for your own.
And more
- Terminal widget — a real VT-emulating terminal inside your UI (ptys, colors, mouse), enabling multiplexers and embedded shells.
- Charts — line/bar/pie/donut/sparkline graph widgets on a backend-agnostic canvas that upgrades from braille to real pixels.
- Effects — animated widget effects (e.g. Matrix rain), marquees, gradients, shadows with true alpha.
- Hyperlinks — OSC 8 clickable links tracked per cell.
- Keyboard — kitty keyboard protocol and modifyOtherKeys, probed and enabled automatically.
- GPM — mouse on the bare Linux console.
- ANSI art — CP437
.ans/.nfoart decoded and rendered with sub-cell detail. - Docs with pictures —
crystal docsembeds each widget's capture into its API documentation automatically.
Other contributed shards
Crysterm is supported by the event model in event_handler, color routines in term_colors, terminal handling in tput.cr, GPM mouse in gpm.cr, a terminfo library in unibilium.cr, and an animated PNG/GIF parser in pnggif.
tput.cr implements all the terminal routines, and does not use ncurses. For terminfo bindings it uses unibilium, but it also supports a built-in, standard mode which does not use terminfo at all. (A lot of modern software just hardcodes the sequences.) The other important module at Crysterm's core is event_handler. through which all app events and input are routed.
Hello world
require "crysterm"
# The two conventional short aliases — nothing is `include`d into your namespace.
alias CT = Crysterm
alias CW = CT::Widgets
# A `Window` is the surface your widgets live on.
window = CT::Window.new title: "hello"
# `tagged:` is `content:` with the {tags} parsed.
CW::Box.new \
parent: window,
top: :center, left: :center, width: 20, height: 5,
tagged: "{center}'Hello {bold}world{/bold}!'\nPress q to quit.{/center}",
style: CT::Style.new(fg: "yellow", bg: "blue", border: true)
# `q` / Ctrl-Q quit by default. Run the main loop:
window.exec
A step-by-step tutorial building up from this program is in GETTING-STARTED.md, and a ready-to-copy project skeleton is in examples/template/.
Examples
git clone https://github.com/crystallabs/crysterm
cd crysterm
shards
crystal tests/hellos/hello.cr # the program above
crystal tests/hellos/hello2.cr # the Qt shape: MainWindow + a layout
crystal tests/misc/qt_widgets.cr # tour of the Qt-inspired widget set
crystal tests/misc/widgets.cr # 6 representative widgets, animated (1/2)
crystal tests/misc/widgets2.cr # 6 more representative widgets (2/2)
Larger, complete applications:
crystal examples/mutt/mutt.cr # a Mutt-style mail client
crystal examples/pine/pine.cr # a Pine/Alpine-style mail client
crystal examples/claude/claude.cr # a Claude-style rich-text chat session
crystal examples/text/editor/editor.cr # a working Unicode text editor
crystal examples/terminal/emulator/emulator.cr # a minimal real terminal emulator
crystal examples/terminal/tid/tid.cr # terminal identification, live
crystal examples/screen/multiple/multiple.cr # two windows, one app
crystal examples/direct/completer/completer.cr # inline (fzf-style) mode
crystal examples/games/minesweeper/minesweeper.cr
crystal examples/games/pong/pong.cr
crystal examples/games/commando/commando.cr
crystal examples/games/wumpus/wumpus.cr
(And many more under examples/ and tests/.)
Dependencies and versioning
Crysterm and associated shards (tput, event_handler,
term_colors, etc.) follow semantic versioning, with releases tagged
vX.Y.Z. Crysterm's shard.yml pins each of those dependencies to its major
version (e.g. version: ~> 2.0), so a plain clone plus shards install
resolves to released versions, with minor and patch updates
picked up.
To develop against the current master HEADs instead, place
shard.override.yml next to shard.yml:
# shard.override.yml — track master HEADs instead of releases
dependencies:
tput:
github: crystallabs/tput
branch: master
crystallabs-helpers:
github: crystallabs/crystallabs-helpers.cr
branch: master
term_colors:
github: crystallabs/term_colors
branch: master
event_handler:
github: crystallabs/event_handler
branch: master
gpm:
github: crystallabs/gpm.cr
branch: master
pnggif:
github: crystallabs/pnggif
branch: master
superconf:
github: crystallabs/superconf.cr
branch: master
unibilium:
github: crystallabs/unibilium.cr
branch: master
unibilium-shim:
github: crystallabs/unibilium-shim.cr
branch: master
For working with local checkouts, path: ../tput.cr entries instead of branch: ...
work the same way.
Testing
Run crystal spec as usual.
Documentation
For a hands-on, chapter-by-chapter tutorial — from an empty directory to a styled, interactive application — see GETTING-STARTED.md.
Run crystal docs as usual for plain API docs.
For the full docs — with every widget's screenshot or animation embedded into its class documentation — run the docs pipeline instead:
crystal tools/test.cr
Invoked as shown, it (re)captures any stale example outputs, embeds each widget's
capture into its doc comment, and builds crystal docs with the images copied
into the docs/ tree. Such improved documentation is available as usual in docs/index.html.
To narrow the run, name a
target and/or an action, e.g. re-make just one widget's captures with
crystal tools/test.cr -- tests/widget/button.cr, or only re-insert image
links and rebuild docs with crystal tools/test.cr -- --doc-comments --docs.
Thanks
- All the fine folks in the Crystal community.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
1.0.2- Tagged
- Oct 13, 2026
- Commit
77223c0d3acd- Indexed
- not yet
Dependents
Repository
github.com/crystallabs/crysterm
Metadata
- Created
- Aug 12, 2026
- Updated
- Nov 8, 2026
- Synced
- Nov 8, 2026
- Versions
- 15