étui v0.1.0 TUI framework for Gleam
overview demo widgets themes install docs
⌃C quit
NAVIGATE
overview demo widgets themes install docs github hex.pm
WIDGETS
Block
Paragraph
List
Table
Tabs
Gauge
LineGauge
HBar
+24 more
gleam ─ erlang/otp ─ node.js

étui

/eɪˈtwiː/ · n. · a case for holding small precious things

Build beautiful terminal interfaces in pure Gleam.

Buffer-diff rendering, composable widgets, Unicode-correct grapheme clusters, crash-safe terminal restore. On the BEAM.

Pure functions. Any fn(Buffer, Rect) → Buffer is a widget.
Unicode-correct. Cell width, not codepoints.
Crash-restore. Terminal always left clean.
Testable without a terminal. Geometry is pure math.
Erlang + Node.js targets.
6 built-in themes. RGB and 256-color.
─ App ─ 80×24
> item 1
item 2
item 3
item 4
item 5
Count: 42
あいうえお CJK = 2 cells each
👨‍👩‍👧‍👦 ZWJ family = 2 cells
progress
68%
downloads / day
NORMAL étui 0.1.0 │ gleam │ erlang Ln 1, Col 1
── LIVE DEMO ─────────────────────
étui-dino · interactive demo · click to focus q quit
DEMO étui · gleam · erlang space jump · q quit

Spacing and alignment in this web preview depend on the browser font renderer and the monospace fallback in use. In a real terminal with a proper monospace font, box-drawing characters and cell widths align pixel-perfect.

── WIDGETS ──────────────────────
Block
widgets/block
Borders, title, padding — foundation for panels.
Paragraph
widgets/paragraph
Word-wrapping text with alignment.
List
widgets/list
Scrollable list with selection.
Table
widgets/table
Grid with optional header and row selection.
Tabs
widgets/tabs
Horizontal tab bar with active highlight.
Gauge
widgets/gauge
Progress bar with optional label.
LineGauge
widgets/line_gauge
Thin single-line progress gauge.
HBar
widgets/hbar
Horizontal bar chart for labeled values.
Chart
widgets/chart
Line chart from numeric series.
Sparkline
widgets/sparkline
Inline trend from a short data series.
Canvas
widgets/canvas
Braille pixel drawing surface.
Input
widgets/input
Single-line text field, wide-char cursor.
TextArea
widgets/textarea
Multi-line text editor.
Scrollbar
widgets/scrollbar
Scroll position indicator on an edge.
Spinner
widgets/spinner
Animated loading indicator (use with run_animated).
Marquee
widgets/marquee
Scrolling text ticker.
Popup
widgets/popup
Centered modal overlay.
StatusBar
widgets/statusbar
Left / center / right status line.
Line
widgets/line
Horizontal or vertical divider.
Progress
widgets/progress
Multi-step progress tracker.
GradientBar
widgets/gradient_bar
Color-gradient progress bar.
Clear
widgets/clear
Erase a rectangular region.
Scene
widgets/scene
Static composition of child widgets.
Tree
widgets/tree
Expand/collapse hierarchy, optional counts.
Dialog
widgets/dialog
Modal with buttons.
Form
widgets/form
Multi-field labeled form.
Notification
widgets/notification
Toast or banner overlay.
ScrollView
widgets/scroll_view
Scrollable viewport wrapper.
Paginator
widgets/paginator
Page indicator (dots or numbers).
Help
widgets/help
Key binding help (short or full).
Fieldset
widgets/fieldset
Horizontal rule with inline title.
MultiSelect
widgets/multi_select
Checkbox list with optional cap.
── WIDGET SYSTEM ───
// any fn(Buffer, Rect) → Buffer is a widget — no traits, no registration
import gatui/widget

// compose: border wrapping inner content
let w = widget.compose(border_w, block.inner(area, blk), content_w)

// layer: draw top over bottom
let w = widget.layer(background_w, overlay_w)

// animated widget: receives frame counter
let aw: widget.AnimatedWidget = fn(buf, area, frame) { ... }
widget.freeze_frame(aw, current_frame)(buf, area)
── THEMES ───────────────────────
dracula
Normal text
Accent / selection
┌───────┐ border
nord
Normal text
Accent / selection
┌───────┐ border
catppuccin_mocha
Normal text
Accent / selection
┌───────┐ border
gruvbox_dark
Normal text
Accent / selection
┌───────┐ border
tokyo_night
Normal text
Accent / selection
┌───────┐ border
dark
Normal text
Accent / selection
┌───────┐ border
import gatui/theme

let t = theme.catppuccin_mocha()
let t = theme.nord()
let t = theme.tokyo_night()

// customize from a base
let custom = theme.Theme(..theme.nord(), accent: style.Rgb(255, 165, 0))
── INSTALL ──────────────────────
1. add to gleam.toml
[dependencies]
etui = ">= 0.1.0 and < 1.0.0"
2. quickstart
import gatui/app
import gatui/backend/erlang
import gatui/buffer
import gatui/geometry.{Fill, Horizontal, Percentage}
import gatui/widgets/block
import gatui/widgets/paragraph

pub type Model { Model(count: Int, width: Int, height: Int) }

pub fn main() {
  app.run(erlang.new(), Model(0, 80, 24), view, update, fn(m) { m.count >= 10 }, 16)
}

fn view(model: Model) -> List(app.RenderOp) {
  let area  = geometry.rect_new(0, 0, model.width, model.height)
  let chunks = geometry.split(Horizontal, area, [Percentage(30), Fill])
  let buf =
    buffer.buffer_new(area)
    |> block.render(left, block.block_new() |> block.with_border(block.Single))
    |> paragraph.render(right, paragraph.paragraph_new("Count: " <> int.to_string(model.count)))
  [app.Render(buf)]
}
étui gleam erlang + js 32 widgets 6 themes
apache-2.0 altumdream.com