Components
Every serez-ui built-in component, explained the way you will use it: what it is for, all of its props with types and defaults, its keyboard behavior, and a working example. Use the sidebar to jump to any component — they are grouped by type.
onChange, onToggle, onDrop…). Assigning that value to a field of this triggers a re-render, and the UI reflects the new state. All components render in the GUI and in the TUI from the same JSX; focus and keyboard navigation are built in (Tab moves, Enter/Space activates). Colors, spacing and fonts come from .szs.Actions
Button
The basic action trigger. Its text is the children, so anything dynamic can go inside the tag. Use it for every "do something now" interaction — submit, add, delete, open. A disabled Button is greyed out, ignores clicks and is skipped by Tab.
| Prop | Type | Default | Description |
|---|---|---|---|
onClick | fn() | — | Called on click, or on Enter/Space while focused |
disabled | bool | false | Grey out, ignore clicks and skip in the focus order |
<Button onClick={save} disabled={!this.canSave}>Save</Button>
<Button onClick={() => { this.count = this.count + 1 }}>Clicked {this.count} times</Button>Style it from .szs with Button { background-color: …; color: …; border-radius: …; }.
Link
An underlined, accent-colored action for navigation-flavored clicks — open a URL, jump to another view, reveal something. Visually lighter than a Button; same activation rules.
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Destination shown/used by the link |
onClick | fn() | — | Called on click or Enter while focused |
disabled | bool | false | Grey out and skip in the focus order |
<Link href="https://serez.dev" onClick={openDocs}>Open the docs</Link>
<Link onClick={() => { this.view = "settings" }}>Go to settings</Link>Text input
Input
One-line text field. The caret is fully positionable: click places it under the cursor, dragging selects, Home/End jump, Backspace/Delete auto-repeat while held. onChange fires on every edit with the whole new value — store it back or the field snaps to the old value (that is the controlled pattern working as intended).
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | "" | Current text — the single source of truth |
placeholder | string | — | Muted hint shown while the value is empty |
type | string | text | "password" masks every character |
onChange | fn(value) | — | Fires on every edit with the full new text |
onSubmit | fn() | — | Fires when Enter is pressed in the field |
disabled | bool | false | Read-only look, no focus, no edits |
<Input value={this.name} placeholder="your name" onChange={(v) => { this.name = v }} />
<Input value={this.pass} type="password" placeholder="password"
onChange={(v) => { this.pass = v }} onSubmit={() => { this.login() }} />Textarea
Multi-line editor with caret and its own vertical scrollbar (mouse wheel works inside). Enter inserts a newline — submit with a Button next to it. rows fixes the visible height; longer content scrolls.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | "" | Current text (may contain newlines) |
placeholder | string | — | Muted hint shown while empty |
rows | int | 6 | Visible height in text lines |
onChange | fn(value) | — | Fires on every edit with the full new text |
disabled | bool | false | Read-only look, no focus, no edits |
<Textarea value={this.bio} rows={4} placeholder="about you"
onChange={(v) => { this.bio = v }} />Selection
Checkbox
A boolean box with a label. Click it or press Space while focused; onChange receives the new state. The label can be passed as a prop or as the children — both work.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | bool | false | Current state |
label | string | children text | Text next to the box |
onChange | fn(bool) | — | Fires with the new state on toggle |
disabled | bool | false | Grey out and skip in the focus order |
<Checkbox checked={this.agreed} label="I agree" onChange={(b) => { this.agreed = b }} />Switch / Toggle
On/off pill switch — exactly the same props and semantics as Checkbox, different look: reach for it for settings ("dark mode", "notifications") and keep Checkbox for confirmations and lists. Toggle is an alias for the same component.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | bool | false | Current state |
label | string | children text | Text next to the pill |
onChange | fn(bool) | — | Fires with the new state on toggle |
disabled | bool | false | Grey out and skip in the focus order |
<Switch checked={this.dark} label="Dark mode" onChange={(b) => { this.dark = b }} />RadioGroup
One choice among a few always-visible options (dots). Click an option or move the selection with ↑/↓. Prefer it over Select/Dropdown when there are 2–5 options and seeing all of them at once helps the user decide.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The currently selected option |
options | [string] | — | The choices, one dot per entry |
onChange | fn(value) | — | Fires with the picked option |
disabled | bool | false | Grey out and skip in the focus order |
<RadioGroup value={this.size} options={["S", "M", "L"]} onChange={(v) => { this.size = v }} />Select
The compact picker: shows one value and cycles through the options on click or ←/→. No overlay — good for short lists (2–4 values) and tight layouts. For longer lists where the user should see everything, use Dropdown.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The currently selected option |
options | [string] | — | The values it cycles through, in order |
onChange | fn(value) | — | Fires with the next option on each cycle |
disabled | bool | false | Grey out and skip in the focus order |
<Select value={this.mode} options={["fast", "normal", "slow"]} onChange={(v) => { this.mode = v }} />Dropdown
A real drop list with an overlay: click or Enter opens it over the widgets below, ↑/↓ move through the options, Enter picks and closes. The overlay draws above everything (z-order handled for you).
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | The currently selected option |
options | [string] | — | The list shown when open |
onChange | fn(value) | — | Fires with the picked option |
disabled | bool | false | Grey out and skip in the focus order |
<Dropdown value={this.lang} options={["es", "en", "fr"]} onChange={(v) => { this.lang = v }} />Slider
Numeric picker over a horizontal track. Click anywhere on the track to jump, drag the knob for continuous change, or nudge by step with ←/→. The value is clamped to [min, max].
| Prop | Type | Default | Description |
|---|---|---|---|
value | int | min | Current value |
min | int | 0 | Lower bound |
max | int | 100 | Upper bound |
step | int | 1 | Keyboard/quantization increment |
onChange | fn(value) | — | Fires with the new value while it changes |
disabled | bool | false | Grey out and skip in the focus order |
<Slider value={this.vol} min={0} max={100} step={5} onChange={(v) => { this.vol = v }} />
<Label>Volume: {this.vol}%</Label>Structure & navigation
Row / Col
The layout pair. Row places its children side by side — each at its content width, with a gap — and stacks them vertically when they no longer fit (that is the responsive autosize; force a single line with wrap={false}). Col stacks children vertically. Tune spacing from .szs (gap, padding, direction: column).
| Prop | Type | Default | Description |
|---|---|---|---|
wrap | bool | true | Row only — stack vertically when children no longer fit |
<Row>
<Button onClick={save}>Save</Button>
<Button onClick={clear}>Clear</Button>
</Row>
<Col>
<h2>Stacked</h2>
<p>One under the other.</p>
</Col>Tabs
Controlled tab strip: it draws the tabs (active one underlined) and you render the panel for the active index — the component does not hold or switch content by itself. Click a tab or switch with ←/→ while focused.
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | [string] | — | One tab per entry, in order |
active | int | 0 | Index of the selected tab |
onChange | fn(index) | — | Fires with the clicked/keyed tab index |
disabled | bool | false | Grey out and skip in the focus order |
<Tabs tabs={["Info", "Config", "Logs"]} active={this.tab} onChange={(i) => { this.tab = i }} />
{ this.tab == 0 ? <Info /> : this.tab == 1 ? <Config /> : <Logs /> }Collapsible / Accordion
A header with a chevron that shows or hides its children. Click the header or press Enter/Space while focused. Stack several (each with its own open flag) to build an accordion — close the others in the onToggle if you want only one open at a time. Accordion is an alias.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Header text next to the chevron |
open | bool | false | Whether the children are visible |
onToggle | fn(bool) | — | Fires with the new open state |
disabled | bool | false | Grey out and skip in the focus order |
<Collapsible title="Advanced" open={this.adv} onToggle={(o) => { this.adv = o }}>
<Checkbox checked={this.verbose} label="Verbose logs" onChange={(b) => { this.verbose = b }} />
<Slider value={this.retries} min={0} max={10} onChange={(v) => { this.retries = v }} />
</Collapsible>Files
File permission in serez.json to read the contents of what the user picked or dropped.FileInput
A "choose file" button that opens the native OS file dialog (also with Enter/Space while focused) and then shows the picked file name next to the button. With save={true} it opens a save dialog instead — combine with defaultName for the suggested file name.
| Prop | Type | Default | Description |
|---|---|---|---|
onChange | fn(path) | — | Fires with the absolute path of the picked file |
value | string | "" | Currently picked path (controls the shown name) |
label | string | Elegir archivo... | The button text |
placeholder | string | Ningun archivo | Text shown while nothing is picked |
filterName | string | — | Human name of the dialog filter (e.g. "JSON") |
exts | string | — | Comma-separated extensions the dialog filters ("png,jpg") |
save | bool | false | Open a save dialog instead of an open dialog |
defaultName | string | — | Suggested file name in the save dialog |
<FileInput exts="json" filterName="JSON" value={this.path}
onChange={(p) => { this.path = p }} />
<FileInput save={true} defaultName="report.json" label="Export…"
onChange={(p) => { this.exportTo(p) }} />DropZone
A drag-and-drop target for OS files: it highlights while files hover over the window and onDrop receives every dropped path at once. For drops anywhere in the window (not just over a zone), override the onFilesDropped(paths) hook on your Window instead.
| Prop | Type | Default | Description |
|---|---|---|---|
onDrop | fn(paths) | — | Fires with the array of dropped absolute paths |
label | string | Arrastra archivos aqui | Text shown inside the zone |
height | int | 120 | Height of the drop area in px |
<DropZone label="Drop images here" height={140} onDrop={(paths) => { this.images = paths }} />Presentation & data
Label
Muted caption text — the children are the content. Non-interactive (skipped by focus). For headings and body text use the HTML-like tags (h1–h3, p, span); Label is for the small print next to controls.
<Label>Press Save when you are done</Label>Image
Raster image (PNG/JPG) from a file path — or from bytes in memory via bytes, e.g. an image you just downloaded with fetch. Give one or both dimensions; with only one, the aspect ratio is preserved.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Path to a PNG/JPG on disk |
bytes | bytes | — | In-memory image data (alternative to src) |
width | int | natural | Target width in px |
height | int | natural | Target height in px |
alpha | int | opaque | Blend the image (lower = more transparent) |
alt | string | — | Text shown if the image cannot load |
<Image src="assets/logo.png" width={64} alt="logo" />
<Image bytes={this.png} height={120} alt="fetched preview" />ProgressBar
Shows progress from value toward max, with an optional caption. Non-interactive. For a live bar during long work, update the value from the onFrame() hook and return true to redraw.
| Prop | Type | Default | Description |
|---|---|---|---|
value | int | 0 | Current progress |
max | int | 100 | The value that means 100% |
label | string | — | Caption drawn with the bar |
<ProgressBar value={this.done} max={this.total} label="downloading" />Table
Read-only grid: a header row from columns and aligned cells from rows — an array of rows, each an array of cell strings. Good for response headers, metrics, key/value listings. It does not sort or select; render your own controls around it for that.
| Prop | Type | Default | Description |
|---|---|---|---|
columns | [string] | [] | Header cells, one per column |
rows | [[string]] | [] | The data — one inner array per row |
<Table columns={["Name", "Qty", "Price"]}
rows={[["Apples", "4", "$2.00"], ["Pears", "2", "$1.50"]]} />Chart
Plots a numeric series with the core's vector primitives (antialiased lines and fills). Three shapes: line, area (filled) and bar. By default the scale fits the data — pin it with min/max when comparing charts or animating. In the TUI it degrades to a sparkline. Non-interactive.
| Prop | Type | Default | Description |
|---|---|---|---|
data | [number] | [] | The series to plot, in order |
type | string | line | line | area | bar |
height | int | 160 | Height of the plot in px |
color | string | .szs accent | Series color (also settable via .szs background-color) |
min | number | auto | Pin the lower bound of the scale |
max | number | auto | Pin the upper bound of the scale |
dots | bool | false | Mark each data point |
<Chart data={this.latencies} type="area" height={140} dots={true} />
<Chart data={[3, 7, 4, 9]} type="bar" min={0} max={10} color="#22c55e" />Overlays & feedback
Tooltip
Wraps a single child and shows a small box with the tip next to the cursor while it hovers over the child. Purely presentational — the child keeps its own behavior and focus.
| Prop | Type | Default | Description |
|---|---|---|---|
tip | string | — | The text shown on hover |
<Tooltip tip="Sends the request"><Button onClick={send}>Send</Button></Tooltip>Modal
When open is true, it dims the whole window behind a translucent scrim and centers a box with the title and your children on top, capturing clicks and focus. Closing is state like everything else: the header's × button fires onClose, and your own buttons just set the flag back to false.
| Prop | Type | Default | Description |
|---|---|---|---|
open | bool | false | Show or hide the modal |
title | string | "" | Header text of the centered box |
onClose | fn() | — | Fires when the user clicks the × close button |
<Modal open={this.confirm} title="Delete item?" onClose={() => { this.confirm = false }}>
<p>This cannot be undone.</p>
<Row>
<Button onClick={doDelete}>Delete</Button>
<Button onClick={() => { this.confirm = false }}>Cancel</Button>
</Row>
</Modal>Toast
Transient feedback banner. kind picks the color: neutral info, green success, yellow warn, red error. Render it conditionally; for the auto-dismiss, count down in the per-frame onFrame() hook and clear the message:
| Prop | Type | Default | Description |
|---|---|---|---|
message | string | children text | The banner text |
kind | string | info | info | success | warn | error |
{ this.toast != "" ? <Toast message={this.toast} kind="success" /> : null }// show it…
this.toast = "Saved!"
this.toastTicks = 120 // ~2 s at 60 fps
// …and auto-dismiss from the per-frame hook of your Window
public onFrame() {
if (this.toastTicks > 0) {
this.toastTicks = this.toastTicks - 1
if (this.toastTicks == 0) { this.toast = ""; return true }
}
return false
}See also
- serez-ui reference— the component model, focus & keyboard, hooks, panels and the native renderer.
- .szs styling — colors, fonts, spacing, reactive conditions and media queries for everything above.
- Build a UI guide — a task-list app built step by step, TUI and GUI.