Elements

Cuttlefish UI supports a focused set of HTML elements. Every UI has one or more <screen> roots; inside them you compose containers, text, controls, and media. Multiple screens can coexist in one file — see Screen navigation.

The <screen> root

<screen>
  ...children...
</screen>

Required — at least one per UI; add more to define additional screens. Each screen’s box fills the display viewport (e.g. 320×240 landscape for an ILI9341, a common TFT display controller). All other elements live inside a screen.

Screen navigation

Any tap target can navigate to another screen with an href="#screenId":

<screen id="home">
  <button href="#settings">Settings</button>
</screen>

<screen id="settings">
  <a href="#home">Back</a>
</screen>

<a href="#id"> and <button href="#id"> auto-wire the tap to a screen switch at build time — no script code needed.

Containers and text

<view> — generic container

A block container. Supports display: flex for layout. Exposes .value plus onToggle/onChange for pin-driven state.

<view id="row" style="display: flex; flex-direction: row; gap: 8px;">
  <text>Hello</text>
  <text>World</text>
</view>

<text> — static or dynamic text

<text id="label">Temperature</text>

Has a numeric .value property for state. To change the displayed string reactively, use ui.bind(node, 'text', compute) (see Authoring API).

Interactive elements

ElementPurpose.value
<button>Tappable button (with :pressed state)0 / 1
<check>Checkbox — tap to toggle0 / 1
<radio name="g">Radio — mutually exclusive within a name group0 / 1
<select>Tap to cycle options0..N-1
<progress>Progress bar0-100 (fill %)
<range>Draggable sliderbetween min and max
<input>Text input (opens on-screen keyboard)— (use .text)
<list>Virtualized (only the visible items are drawn), data-bound list
<canvas>User-drawn graphics via ui.drawCanvas
<drawer>Edge panel that slides in from a screen edge
<dialog>Centered modal overlay
<toast>Auto-dismissing notification

<button>

<button id="ok">OK</button>

Supports the :pressed pseudo-state and transition animations (see CSS). Events: onClick (short tap, up within 600ms), onHold (press ≥ 600ms), onRelease.

<check>

<check id="enable">Enable feature</check>

Content text is the label. .value is 0 (unchecked) or 1 (checked). onToggle(pin, onChange?) watches a GPIO pin for falling edges and flips .value automatically.

<radio>

<radio name="mode">Auto</radio>
<radio name="mode">Manual</radio>

Radios sharing the same name are mutually exclusive — selecting one deselects the others. .value is 0 or 1.

<select>

<select id="mode">
  <option>Auto</option>
  <option>Manual</option>
  <option>Off</option>
</select>

Tapping cycles through the <option> children. .value is the index of the current option (0..N-1); the element’s displayed text automatically reflects the current option.

<progress>

<progress id="load" value="40"></progress>

Read-only progress bar. .value is 0-100 (percentage filled).

<range>

<range id="brightness" min="0" max="100"></range>

A draggable slider. .value sits between min and max (defaults 0-100). onChange(callback?) fires whenever .value changes during a drag. Drag the thumb or write .value from code.

<input>

<input id="ssid" type="text" placeholder="Network name" maxlength="32">

Text input. Tapping opens an on-screen keyboard. The string value lives in .text (not .value). onChange(callback?) fires after the keyboard commits. Attributes: id, type, placeholder, maxlength, keyboard.

Use type="number" to open a numeric keypad instead of the alpha keyboard. To open a custom keyboard, set keyboard="mykeys" to the id of a <keyboard> template defined in the same file (see below).

<list>

<list id="networks" item-height="28px"></list>

A virtualized (only the visible items are drawn), data-bound list. Bind it to dynamic data with ui.bindList(node, countFn, itemFn, onTap?). Attribute: item-height.

<canvas>

<canvas id="spark" width="120" height="40"></canvas>

A user-drawn region. Register a per-frame draw callback with ui.drawCanvas(node, callback); the callback receives a CanvasCtx (see Authoring API). Attributes: width, height (drawing buffer size in pixels).

On-screen keyboards

Two built-in keyboards ship with the runtime — an alpha keyboard (QWERTY-style, themeable via the ui-key* CSS classes) and a number keypad. <input> opens the alpha one by default, or the number keypad with type="number".

You can also define custom keyboards with <keyboard>, <row>, and <key>:

<keyboard id="hexkeys">
  <row><key>0</key><key>1</key><key>2</key><key>3</key></row>
  <row><key>4</key><key>5</key><key>6</key><key>7</key></row>
  <row><key>8</key><key>9</key><key>A</key><key>B</key></row>
  <row><key>C</key><key>D</key><key>E</key><key>F</key></row>
</keyboard>

<input id="addr" keyboard="hexkeys" placeholder="Hex address">

Keyboard templates are parsed from the top level of the .ui file and referenced by id from any <input>.

Overlays

<drawer> — edge panel

<drawer id="nav" side="left">
  <view class="dialog-scrim" on:click={closeNav}></view>
  <view class="dialog-card"> ...menu content... </view>
</drawer>

A panel that slides in from an edge of the screen. side is left, right, top, or bottom (default bottom). Open and close it from script with ui.drawer.open(id) / ui.drawer.close(id); a tap on the scrim (an overlay child covering the rest of the screen) closes it. Slide frames are band-composited, so the animation doesn’t tear on SPI panels.

<dialog> — centered modal

<dialog id="confirm">
  <view class="dialog-scrim" on:click={closeConfirm}></view>
  <view class="dialog-card">
    <text>Delete this network?</text>
    <view class="dialog-footer">
      <button class="btn btn-outline">Cancel</button>
      <button class="btn btn-destructive">Delete</button>
    </view>
  </view>
</dialog>

A centered modal overlay. Open and close it with ui.dialog.open(id) / ui.dialog.close(id); tapping the scrim closes it. Internally it uses the same slot machinery as the drawer. The <dialog> element itself lays out at zero height with absolutely-positioned children — style the .dialog-card child, not the <dialog> tag.

<toast> — transient notification

<toast id="saved" side="bottom" duration="2500" class="toast">
  <text class="toast-title">Saved</text>
  <text class="toast-description">Settings written to flash</text>
</toast>

Shows with ui.toast(id) — a single call, no open/close pair. side is top or bottom; after the panel is fully open it auto-closes duration milliseconds later (re-showing it restarts the timer, and a manual close wins over the auto-close). Multiple toasts stack as authored siblings. When closed it slides fully off the display edge — no remnant strip.

Tables

<table>

<table>
  <tr><th>Sensor</th><th>Value</th></tr>
  <tr><td>Temp</td><td>22.4 °C</td></tr>
</table>

An approximation of an HTML table via the UA stylesheet (it is not a full table layout engine) — useful with the kit’s table styling. See CSS & Styling.

Template directives

Elements support Svelte-style declarative wiring in the template itself, so most UIs need little or no script code:

DirectiveExampleMeaning
on:click / on:hold / on:release / on:change<button on:click={save}>Call the script function save on the event
bind:text<input bind:text="name">Two-way bind the input’s text to the signal name
bind:value<range bind:value="level">Two-way bind .value to the signal level
ref<text ref="temp">Give the node a TypeScript handle name independent of its CSS id
{expr} interpolation<text>Level: {level()}</text>Live-updating text bound to a signal — no id needed

Images

<img>

<img id="logo" src="assets/logo.img" width="64" height="64">

Embeds an image as a static const uint16_t[] (RGB565 — a compact color format). Common formats — png, jpg, webp, gif, svg, ico, tiff, avif — are detected by magic bytes, downscaled to fit the display, and converted automatically at build time; the raw .img format (flat row-major, width × height × 2 bytes) also still works. Use object-fit: contain | cover | fill to control scaling. Attributes: id, src, width, height.

HTML tag aliases

Common HTML tags map to Cuttlefish primitives:

HTML tagMaps toNotes
body, div, header, footer, nav, main, section, article, aside<view>Block container
span, p, h1h6<text>Inline/heading text
b, strong, i, em, u<text> inline runsMixed bold/italic/underline spans within one text node
label, a, brfirst-class tagslabel groups with a control, a is a link (supports href), br breaks a line
table, tr, th, td, hrfirst-class tagsUA-styled table approximation; hr draws a rule (see the kit’s .separator / .vseparator)

Global attributes

All elements support hidden — a hidden element (and its descendants) is kept in memory but takes no space on screen and ignores taps.

Next: CSS & Styling for how to style these elements.