Elements
Snaps are built from 16 components organized into four categories. Every component lives
in ui.elements as a named entry. The type field names the component; props carries
its configuration; children names child element IDs; on binds event handlers.
"my-element": {
"type": "text",
"props": { "content": "Hello" }
}
| # | Component | Category | Description |
|---|---|---|---|
| 1 | badge | Display | Inline label with color and icon |
| 2 | button | Display | Action button with variants and icon |
| 3 | icon | Display | Standalone icon from curated set |
| 4 | image | Display | HTTPS image with aspect ratio |
| 5 | item | Display | Content row with actions slot |
| 6 | item_group | Container | Groups items into a styled list |
| 7 | progress | Display | Horizontal progress bar |
| 8 | separator | Display | Visual divider |
| 9 | stack | Container | Vertical or horizontal layout |
| 10 | text | Display | Text block with size and weight |
| 11 | bar_chart | Data | Horizontal bar chart with labeled bars |
| 12 | cell_grid | Data | Colored cell grid, optionally interactive |
| 13 | input | Field | Text or number input |
| 14 | slider | Field | Numeric range slider |
| 15 | switch | Field | Boolean toggle |
| 16 | toggle_group | Field | Single or multi-select choice group |
Field components (input, slider, switch, toggle_group) collect user input.
Their values are sent in the POST payload under inputs[name] when a submit action
fires.
badge
Inline label with color and optional icon. Use for metadata, status indicators, and counts alongside content. "default" (filled) draws attention; "outline" is subtler. Pair with an icon for scannability.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | Yes | Display text. Max 30 chars | |
variant | string | No | "default" | "default" (filled) or "outline" (bordered) |
color | PaletteColor | No | "accent" | Badge color |
icon | IconName | No | Leading icon |
{ "type": "badge", "props": { "label": "New" } }
{ "type": "badge", "props": { "label": "Live", "color": "green", "icon": "zap" } }
{ "type": "badge", "props": { "label": "ERC-20", "variant": "outline", "color": "blue" } }
button
The only component that fires actions — bind them via on.press. Default variant is "secondary" (bordered); use "primary" (filled) for the main CTA, typically one per page. See Actions for the full list of action types.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | Yes | Button text. Max 30 chars | |
variant | string | No | "secondary" | Visual style |
icon | IconName | No | Leading icon |
Variants
| Variant | Description |
|---|---|
primary | Solid accent background, white text — primary CTA |
secondary | Accent-colored border, transparent fill |
{
"type": "button",
"props": { "label": "Submit", "variant": "primary" },
"on": { "press": { "action": "submit", "params": { "target": "https://my-snap.com/" } } }
}
{
"type": "button",
"props": { "label": "Open" },
"on": { "press": { "action": "open_url", "params": { "target": "https://example.com" } } }
}
icon
Standalone icon from the curated set. Best as a visual accent inside item action slots or horizontal stacks. Avoid using icons as standalone content — pair with text or use inside a badge.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | IconName | Yes | Icon identifier | |
color | PaletteColor | No | "accent" | Icon color |
size | string | No | "md" | "sm" (16px) or "md" (20px) |
{ "type": "icon", "props": { "name": "star", "color": "amber" } }
Available Icons
| Category | Icons |
|---|---|
| Navigation | arrow-right arrow-left external-link chevron-right |
| Status | check x alert-triangle info clock |
| Social | heart message-circle repeat share user users |
| Content | star trophy zap flame gift |
| Media | image play pause |
| Commerce | wallet coins |
| Actions | plus minus refresh-cw bookmark |
| Feedback | thumbs-up thumbs-down trending-up trending-down |
image
HTTPS image with fixed aspect ratio. Use "16:9" for hero and banner images, "1:1" for avatars or thumbnails, "4:3" for general photos, and "9:16" for tall portrait content.
| Prop | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL. Supports jpg, png, gif, webp. GIFs autoplay and loop. |
aspect | string | Yes | "1:1" "16:9" "4:3" "9:16" |
alt | string | No | Alt text for accessibility |
{ "type": "image", "props": { "url": "https://example.com/photo.jpg", "aspect": "16:9" } }
item
The go-to component for structured content rows: leaderboards, settings, key-value info. Has a title, optional description, and an actions slot on the right side. Put badges, icons, or buttons in children for the action slot.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | Yes | Primary text. Max 100 chars | |
description | string | No | Secondary text below title. Max 160 chars | |
variant | string | No | "default" | Visual style |
Variants
| Variant | Description |
|---|---|
default | No background, no border |
Children
Rendered in the actions slot (right side). Use for badges, icons, buttons, or any trailing content.
"score": {
"type": "item",
"props": { "title": "Engagement Score", "description": "Based on 24h activity" },
"children": ["score-badge"]
},
"score-badge": { "type": "badge", "props": { "label": "92", "color": "green" } }
"nav": {
"type": "item",
"props": { "title": "Settings" },
"children": ["nav-arrow"]
},
"nav-arrow": { "type": "icon", "props": { "name": "chevron-right", "color": "gray" } }
item_group
Wraps related items for visual grouping. Use separator: true for settings-style lists and border: true for card-like sections.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
border | boolean | No | false | Show border around the group |
separator | boolean | No | false | Show divider lines between items |
gap | string | No | Spacing between items: "none" "sm" "md" "lg" |
Children: item elements only.
"results": {
"type": "item_group",
"props": {},
"children": ["r1", "r2", "r3"]
},
"r1": { "type": "item", "props": { "title": "First place", "description": "Alice" } },
"r2": { "type": "item", "props": { "title": "Second place", "description": "Bob" } },
"r3": { "type": "item", "props": { "title": "Third place", "description": "Charlie" } }
progress
Horizontal progress bar for completion, scores, or any bounded numeric value. Always uses the theme accent color. The label appears above the bar — use it for context like "78%" or "Level 3 of 5".
| Prop | Type | Required | Description |
|---|---|---|---|
value | number | Yes | Current value (0 to max, finite) |
max | number | Yes | Maximum value (must be > 0, finite) |
label | string | No | Label text above the bar. Max 60 chars |
{ "type": "progress", "props": { "value": 65, "max": 100, "label": "Upload progress" } }
separator
Visual divider between logical sections of a page. Most snaps use 2-4 separators. Overusing them creates visual clutter.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
orientation | string | No | "horizontal" | "horizontal" or "vertical" |
{ "type": "separator", "props": {} }
stack
Layout container for arranging children. Every page starts with a vertical stack as root. Use horizontal stacks for button rows, badge groups, or side-by-side cards. justify: "between" is useful for navigation bars.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
direction | string | No | "vertical" | "vertical" or "horizontal" |
gap | string | No | "md" | Spacing between children: "none" "sm" "md" "lg" |
justify | string | No | Content alignment: "start" "center" "end" "between" "around" |
"page": {
"type": "stack",
"props": {},
"children": ["header", "content", "actions"]
}
"row": {
"type": "stack",
"props": { "direction": "horizontal", "gap": "sm" },
"children": ["b1", "b2", "b3"]
}
text
The primary content element. Use weight: "bold" for headings and emphasis. Use size: "sm" for captions, timestamps, and secondary info.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | Yes | Text content. Max 320 chars | |
size | string | No | "md" | "md" (body), "sm" (caption) |
weight | string | No | "normal" | "bold" "normal" |
align | string | No | "left" | "left" "center" "right" |
{ "type": "text", "props": { "content": "Welcome to Snaps", "weight": "bold" } }
{ "type": "text", "props": { "content": "Last updated 2 hours ago", "size": "sm", "align": "center" } }
bar_chart
Horizontal bar chart for displaying ranked or comparative data. Each bar shows a label on the left, a colored fill bar, and a numeric value on the right. Use for poll results, leaderboards, or any ranked values.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
bars | object[] | Yes | 1–6 bar entries (see below) | |
max | number | No | max value | Upper bound for bar scale |
color | PaletteColor | No | "accent" | Default bar color |
Bar Object
| Prop | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Bar label. Max 40 chars |
value | number | Yes | Bar value (≥ 0) |
color | PaletteColor | No | Per-bar color override |
{
"type": "bar_chart",
"props": {
"bars": [
{ "label": "Poblano", "value": 42 },
{ "label": "Negro", "value": 38 },
{ "label": "Verde", "value": 15, "color": "green" }
]
}
}
cell_grid
Grid of colored cells for pixel art, game boards, color pickers, and small data matrices. Cells are defined sparsely — only specify cells that have color or content. Use select to enable tap-to-select; taps write to POST inputs under name.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | No | "grid_tap" | POST inputs key for selection data |
cols | number | Yes | Column count (2–32) | |
rows | number | Yes | Row count (2–16) | |
cells | object[] | Yes | Sparse cell definitions (see below) | |
gap | string | No | "sm" | Cell spacing: "none" (0px) "sm" (1px) "md" (2px) "lg" (4px) |
rowHeight | number | No | 28 | Pixel height per row (8–64). Grid height = rows × rowHeight |
select | string | No | "off" | Selection mode: "off" "single" "multiple" |
Cell Object
| Prop | Type | Required | Description |
|---|---|---|---|
row | number | Yes | Row index (0-based) |
col | number | Yes | Column index (0-based) |
color | PaletteColor | No | Cell fill color |
content | string | No | Cell text content |
{
"type": "cell_grid",
"props": {
"cols": 4,
"rows": 4,
"cells": [
{ "row": 0, "col": 0, "color": "red" },
{ "row": 0, "col": 3, "color": "blue" },
{ "row": 1, "col": 1, "color": "green", "content": "X" },
{ "row": 3, "col": 3, "color": "purple" }
],
"select": "multiple"
}
}
input
Text or number input field for short free-text entry. Set type: "number" for numeric input (changes the mobile keyboard). Always provide a label for accessibility. Value is collected in POST inputs under name.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | Input name (POST inputs key) | |
type | string | No | "text" | "text" or "number" |
label | string | No | Label text above input. Max 60 chars | |
placeholder | string | No | Placeholder text. Max 60 chars | |
defaultValue | string | No | Pre-filled value | |
maxLength | number | No | Max character count (1–280) |
POST value: string.
{ "type": "input", "props": { "name": "email", "label": "Email", "placeholder": "you@example.com" } }
slider
Numeric range slider for bounded choices like ratings, quantities, or percentages. Always set meaningful min/max values and add a label so users know what they're adjusting. Value is collected in POST inputs under name.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | Slider name (POST inputs key) | |
min | number | Yes | Minimum value (must be ≤ max) | |
max | number | Yes | Maximum value (must be ≥ min) | |
step | number | No | 1 | Increment step (must be > 0, finite) |
defaultValue | number | No | midpoint | Initial value (must be between min and max) |
label | string | No | Label text above slider. Max 60 chars |
POST value: number.
{ "type": "slider", "props": { "name": "rating", "label": "Rating (1–10)", "min": 1, "max": 10 } }
switch
Boolean toggle for binary preferences (on/off, yes/no). Good for settings-style pages — the label should describe the enabled state ("Enable notifications", not "Notifications toggle"). Value is collected in POST inputs under name.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | Switch name (POST inputs key) | |
label | string | No | Label text beside the switch. Max 60 chars | |
defaultChecked | boolean | No | false | Initial checked state |
POST value: boolean.
{ "type": "switch", "props": { "name": "notifications", "label": "Enable notifications" } }
toggle_group
Choice group for selecting between 2-6 discrete options. Prefer this over multiple buttons when the choices are parallel and exclusive. Use multiple: true for multi-select scenarios like tags or interests. Value is collected in POST inputs under name.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | Group name (POST inputs key) | |
options | string[] | Yes | Choice labels. Min 2, max 6. Each max 30 chars | |
multiple | boolean | No | false | Allow multiple selections |
orientation | string | No | "horizontal" | "horizontal" or "vertical" |
defaultValue | string or string[] | No | Pre-selected option(s) | |
variant | string | No | "default" | "default" (solid) or "outline" (bordered) |
label | string | No | Label text above the group. Max 60 chars |
POST value: the selected option string (or string[] when multiple is true).
{ "type": "toggle_group", "props": { "name": "plan", "label": "Choose a plan", "options": ["Free", "Pro", "Team"] } }
{
"type": "toggle_group",
"props": {
"name": "interests",
"label": "Select interests",
"multiple": true,
"orientation": "vertical",
"options": ["Dev", "Design", "Data", "Product"]
}
}