Serez Packages

serez-ui

v4.21.1

React-style UI library for Serez-Code: build native GUI and terminal (TUI) apps from one JSX codebase. Virtual DOM with dirty-skip re-rendering, hooks (Window/State/Effects), unified focus and keyboard navigation, clipboard, file drag-and-drop with gestures, responsive layout (autosize, breakpoints, Row auto-wrap), and native file dialogs. 24 built-in components — Button, Input, Textarea, Select, Dropdown, Checkbox, RadioGroup, Slider, ProgressBar, Label, Link, Row/Col, Image, Table, Modal, Tooltip, Toast, Chart, Switch, Tabs, FileInput, DropZone, Collapsible. Styling via .szs, a CSS-with-logic dialect covering typography (real px font-size, weights, letter-spacing, text-transform), the box model (padding, margin, borders, radius, shadows, background images), flex and grid layout, transforms (translate/rotate/scale), z-index and positioning, pseudo-states (:hover/:focus/:active/:disabled), plus reactive conditions and @when/@else blocks driven by app state. Also ships a retained-mode scene renderer and an experimental native renderer (useNativeRenderer).

by serezdev · published 2026-05-30

serez-ui

React-style UI library for Serez-Code. 24 built-in components, a transparent Virtual DOM, and hooks — the same component runs in the terminal (TUI) or in a real native window (GUI). Written in pure .sz; the JSX layer (.szx) compiles away entirely (no web runtime). Requires Serez-Code ≥ 7.2.0.

import "serez-ui"

class Counter:Window {
    public Counter() { super(); this.count = 0 }

    public render() {
        return (
            <div>
                <h1>Counter</h1>
                <hr />
                <h2>{this.count}</h2>
                <Button onClick={() => { this.count = this.count + 1 }}>Increment</Button>
                <Button onClick={() => { this.count = 0 }}>Reset</Button>
            </div>
        )
    }
}

let app = new Counter()
app.runGui("Counter", 520, 420)   // or: app.runTui() for the terminal

Install

sz install serez-ui

Quick use

A component is a class that extends Window and returns JSX from render(). State lives in this fields; mutating it inside an event handler re-renders through the Virtual DOM (diff + patch).

  • render() — override; returns the VNode tree (JSX).
  • styleVars() — override; exposes state to the reactive CSS (.szs).
  • Child → parent callbacks: pass a method without parentheses and the child invokes it — <TaskRow onPick={this.pick} /> in the parent, onClick={this.props.onPick} in the child. this.pick is a reference bound to the parent, so calling it mutates the parent's state. Wrapping it (onPick={() => this.pick()}) works too, and is what you need when the child supplies no arguments but your method takes some. Requires a core with method references (older cores ran the method on read instead of referencing it).
  • onKey(evt) / onMouse(evt) / onFrame() — optional overrides for raw input and per-frame work (poll progress, auto-dismiss a Toast, animate).
  • Run it: app.runGui(title, w, h) (native window) or app.runTui() (terminal). The event loop is a method of your component, so this stays your live top-level app.
  • JSX: it lives in .szx files; sz apps/counter.szx translates to plain .sz and runs in one step — the web syntax disappears, there is no web runtime.
  • Style: attach a .szs stylesheet (CSS with reactive conditions and width/height media queries) with app.useStylesheet(parseCss(File.read("counter.szs"))) before runGui.
  • Focus marks are opt-in (v4.4): clicking a widget leaves no ring by default. Declare Input:focus { border-color: #22d3ee } per widget or a global *:focus { border: 2px solid #f43f5e } in the .szs to mark the focused widget (:active-focus is an accepted alias).
  • Secondary windows: openPanel(title, w, h) opens extra native windows whose content comes from your renderPanel(id) override. Since v4.4 each panel carries its own full input state — focus, caret/selection, editable Input/Textarea, Dropdown, undo — isolated from the main window; the keyboard follows whichever window has OS focus. closePanel(id) is safe to call from a panel's own callbacks.
  • Responsive by default: the GUI reflows on resize, block text word-wraps, and content taller than the window scrolls with the mouse wheel. For structural changes read app.breakpoint() ("sm"/"md"/"lg") inside render().
  • app.useNativeRenderer(true) (before runGui, core ≥ 9.2) opts into the core's native layout/CSS/paint engine — same components, same .szs, much faster. With core ≥ 9.3 both renderers are at visual parity: class selectors, color/font-scale/opacity inheritance, multi-value padding, width in px/%, overflow: scroll clipping, line-height, white-space: nowrap, custom :font families and position: absolute badges render the same on both paths.

Documentation

The full reference lives on the Serez-Code site:

  • Component catalog — the 24 built-in components (Button, Input, Textarea, Select, Dropdown, Checkbox, RadioGroup, Slider, ProgressBar, Label, Link, Row/Col, Image, Table, Modal, Tooltip, Toast, Chart, Switch/Toggle, Tabs, Collapsible/Accordion, FileInput, DropZone) with props, keyboard behavior and examples.

  • serez-ui guide — the component model, the .szx.sz JSX translator, focus & keyboard navigation, OS events (file drag-drop, gestures), secondary windows (panels), retained-mode and the native renderer, and the complete API surface.

  • .szs reference — CSS with logic: reactive conditions against styleVars(), width/height media queries, the supported property table, and custom fonts (:font + font-family). Conditions combine with and / or / not (media-query style; && / || / ! are accepted aliases), with the usual precedence — not binds tighter than and, and and tighter than or:

    body  (width > 600 and flag == true)  { background-color: #c12; }
    .item (selected or hovered)           { border-color: #3b82f6; }
    .row  (not hidden)                    { display: flex; }
    

    There are no grouping parentheses — the sheet scanner closes the condition at the first ). Note that composite conditions need a core that also understands them: under useNativeRenderer(true) the stylesheet is handed to the core's own CSS engine, and on an older core those rules simply don't apply. The default (interpreted) renderer works on any supported core.

    Group many rules under one shared condition with @when blocks — a single logic gate for several selectors (tags, .classes, #ids), so you don't repeat the condition rule by rule. The query is the same .szs logic (a styleVars() variable, or width/height), not just a media query. @else is the complement of the preceding @when, and @else (cond) chains else-if; the branches are mutually exclusive (first match wins), so there's no need to negate ranges by hand:

    @when (width < 300 and darkMode) {
        body   { color: #fff }
        .card  { padding: 8 }
        #main  { gap: 4 }
    }
    
    @when (width < 200) { body { color: #100 } }
    @else (width < 400) { body { color: #200 } }
    @else               { body { color: #300 } }
    

    Blocks nest (@when inside @when — conditions are AND-ed) and a rule inside a block may keep its own (cond), combined with the block's. @else negates the whole preceding condition, so composites like (a or b) complement correctly. Unknown at-rules (@media, …) are discarded. The same core-parity note applies under useNativeRenderer(true).

  • Build a GUI app — step-by-step tutorial from sz install to a working desktop app.

Permissions

serez.json declares the permissions the library needs:

{ "permissions": ["Env", "Terminal", "Gui"] }

Terminal for the TUI loop (raw mode, keyboard, mouse), Gui for the native window.

Packaging

Ship a serez-ui app as a self-contained .exe / .msi (the runtime travels inside, no Serez-Code needed on the target machine) with serez-pack.

Repo structure

serez-ui/
  index.sz             public entry — re-exports the whole API
  src/
    vnode.sz h.sz      Virtual DOM node + hyperscript
    diff.sz patch.sz   diffing + patching
    state.sz effect.sz memo.sz   hooks
    window.sz          Window base class + the GUI/TUI event loops (runGui/runTui)
    components.sz      Button, Input, Textarea, Select, Dropdown, Checkbox,
                       RadioGroup, Slider, ProgressBar, Label, Link, Row, Col,
                       Image, Table, Modal, Tooltip, Toast, Chart, Switch/Toggle,
                       Tabs, FileInput, DropZone
    renderer.sz events.sz   TUI renderer + event helpers
    renderer_gui.sz    GUI renderer (pixels via the core Gui backend)
    event_loop.sz gui_event_loop.sz   deprecated loop shims → app.runTui/runGui
    css.sz             .szs parser (CSS with logic)
    layout.sz          flexbox layout engine
  tools/
    translate.sz       .szx → .sz translator (run a .szx directly with `sz file.szx`)
  apps/                demos (counter, form, todo, gui_form, …)
  Propuesta.md         design contract