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.pickis 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 aToast, animate).- Run it:
app.runGui(title, w, h)(native window) orapp.runTui()(terminal). The event loop is a method of your component, sothisstays your live top-level app. - JSX: it lives in
.szxfiles;sz apps/counter.szxtranslates to plain.szand runs in one step — the web syntax disappears, there is no web runtime. - Style: attach a
.szsstylesheet (CSS with reactive conditions andwidth/heightmedia queries) withapp.useStylesheet(parseCss(File.read("counter.szs")))beforerunGui. - 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.szsto mark the focused widget (:active-focusis an accepted alias). - Secondary windows:
openPanel(title, w, h)opens extra native windows whose content comes from yourrenderPanel(id)override. Since v4.4 each panel carries its own full input state — focus, caret/selection, editableInput/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") insiderender(). app.useNativeRenderer(true)(beforerunGui, 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/opacityinheritance, multi-valuepadding,widthin px/%,overflow: scrollclipping,line-height,white-space: nowrap, custom:fontfamilies andposition: absolutebadges 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→.szJSX translator, focus & keyboard navigation, OS events (file drag-drop, gestures), secondary windows (panels), retained-mode and the native renderer, and the complete API surface. -
.szsreference — CSS with logic: reactive conditions againststyleVars(),width/heightmedia queries, the supported property table, and custom fonts (:font+font-family). Conditions combine withand/or/not(media-query style;&&/||/!are accepted aliases), with the usual precedence —notbinds tighter thanand, andandtighter thanor: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: underuseNativeRenderer(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
@whenblocks — 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.szslogic (astyleVars()variable, orwidth/height), not just a media query.@elseis 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 (
@wheninside@when— conditions are AND-ed) and a rule inside a block may keep its own(cond), combined with the block's.@elsenegates the whole preceding condition, so composites like(a or b)complement correctly. Unknown at-rules (@media, …) are discarded. The same core-parity note applies underuseNativeRenderer(true). -
Build a GUI app — step-by-step tutorial from
sz installto 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