llms.txt

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" }
}
#ComponentCategoryDescription
1badgeDisplayInline label with color and icon
2buttonDisplayAction button with variants and icon
3iconDisplayStandalone icon from curated set
4imageDisplayHTTPS image with aspect ratio
5itemDisplayContent row with actions slot
6item_groupContainerGroups items into a styled list
7progressDisplayHorizontal progress bar
8separatorDisplayVisual divider
9stackContainerVertical or horizontal layout
10textDisplayText block with size and weight
11bar_chartDataHorizontal bar chart with labeled bars
12cell_gridDataColored cell grid, optionally interactive
13inputFieldText or number input
14sliderFieldNumeric range slider
15switchFieldBoolean toggle
16toggle_groupFieldSingle 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.

NewLiveERC-20
PropTypeRequiredDefaultDescription
labelstringYesDisplay text. Max 30 chars
variantstringNo"default""default" (filled) or "outline" (bordered)
colorPaletteColorNo"accent"Badge color
iconIconNameNoLeading 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

Fires actions via on.press — the standard way to commit user input. Default variant is "secondary" (bordered); use "primary" (filled) for the main CTA, typically one per page. (cell_grid also fires actions, via on.press per cell.) See Actions for the full list of action types.

PropTypeRequiredDefaultDescription
labelstringYesButton text. Max 30 chars
variantstringNo"secondary"Visual style
iconIconNameNoLeading icon

Variants

VariantDescription
primarySolid accent background, white text — primary CTA
secondaryAccent-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.

arrow-right
check
heart
star
zap
share
play
chevron-right
PropTypeRequiredDefaultDescription
nameIconNameYesIcon identifier
colorPaletteColorNo"accent"Icon color
sizestringNo"md""sm" (16px) or "md" (20px)
{ "type": "icon", "props": { "name": "star", "color": "amber" } }

Available Icons

CategoryIcons
Navigationarrow-right arrow-left external-link chevron-right
Statuscheck x alert-triangle info clock
Socialheart message-circle repeat share user users
Contentstar trophy zap flame gift
Mediaimage play pause
Commercewallet coins
Actionsplus minus refresh-cw bookmark
Feedbackthumbs-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.

PropTypeRequiredDescription
urlstringYesHTTPS URL. GIFs autoplay and loop.
aspectstringYes"1:1" "16:9" "4:3" "9:16"
altstringNoAlt 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. Mirrors shadcn Item: optional media renders on the left, title and description render in the content area, and children render in the actions slot on the right. Items are not interactive — don't use navigation-style affordances like chevron-right, arrow-right, or external-link that imply the row itself is clickable.

Engagement Score
Based on 24h activity
92
Share this Snap
Pre-fill the composer
PropTypeRequiredDefaultDescription
titlestringYesPrimary text. Max 100 chars
descriptionstringNoSecondary text below title. Max 160 chars
variantstringNo"default"Visual style
mediaobjectNoLeft-side media, like shadcn ItemMedia

Variants

VariantDescription
defaultNo background, no border

Media

Rendered on the left, before the item content, matching shadcn ItemMedia.

Media variantRequired fieldsOptional fields
iconnamecolor
imageurlalt, round

Children

Rendered in the actions slot (right side). Badges, buttons, and icons are all fair game — the item itself is not clickable, so pick trailing content that reads as content (status icon, badge, button), not navigation. Avoid chevron-right, arrow-right, or external-link on a plain item; they imply the row itself navigates.

"score": {
  "type": "item",
  "props": {
    "title": "Engagement Score",
    "description": "Based on 24h activity",
    "media": { "variant": "icon", "name": "trending-up", "color": "green" }
  },
  "children": ["score-badge"]
},
"score-badge": { "type": "badge", "props": { "label": "92", "color": "green" } }
"share": {
  "type": "item",
  "props": { "title": "Share this Snap", "description": "Pre-fill the composer" },
  "children": ["share-btn"]
},
"share-btn": {
  "type": "button",
  "props": { "label": "Share", "icon": "share" },
  "on": { "press": { "action": "compose_cast", "params": { "text": "Check out Snaps!" } } }
}

item_group

Wraps related items for visual grouping. Use separator: true for settings-style lists and border: true for card-like sections.

First place
Alice
Second place
Bob
Third place
Charlie
PropTypeRequiredDefaultDescription
borderbooleanNofalseShow border around the group
separatorbooleanNofalseShow divider lines between items
gapstringNoSpacing 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".

Upload progress
PropTypeRequiredDescription
valuenumberYesCurrent value (0 to max, finite)
maxnumberYesMaximum value (must be > 0, finite)
labelstringNoLabel 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.

Section A

Section B
PropTypeRequiredDefaultDescription
orientationstringNo"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. Nested stack children of a horizontal stack flex as peers (equal share of the row by default), so you usually do not need columns just to place two sections side by side. justify: "between" is useful for navigation bars.

Child 1
Child 2
Child 3
PropTypeRequiredDefaultDescription
directionstringNo"vertical""vertical" or "horizontal"
gapstringNocolumn-awareSpacing between children: "none" "sm" "md" "lg" (see below)
justifystringNoContent alignment: "start" "center" "end" "between" "around"
columnsnumberNoHorizontal stacks only: 26 for an explicit equal column grid. Omit when children are stacks — nested stacks flex as row peers automatically.

Gap

gap resolves to a different pixel value depending on direction. Horizontal stacks use a tighter scale because their children sit side-by-side and tend to be wider:

SizeVerticalHorizontal
none0px0px
sm8px4px
md16px8px
lg24px16px

When gap is omitted, vertical stacks default to "md". Horizontal stacks pick a default based on column count — denser layouts get tighter gaps:

ColumnsDefault
2"lg" (16px)
3"md" (8px)
4–6"sm" (4px)
unknown"md" (8px)

Column count is taken from columns when set, or inferred from button-row children. An explicit gap always wins — override the default when you have a deliberate visual reason (extra breathing room around a hero row, a visually compact toolbar, a hero+supporting-button pair where the loose lg default would feel disconnected).

"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.

Welcome to Snaps
Last updated 2 hours ago
PropTypeRequiredDefaultDescription
contentstringYesText content. Max 320 chars
sizestringNo"md""md" (body), "sm" (caption)
weightstringNo"normal""bold" "normal"
alignstringNo"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.

PropTypeRequiredDefaultDescription
barsobject[]Yes1–6 bar entries (see below)
maxnumberNomax valueUpper bound for bar scale
colorPaletteColorNo"accent"Default bar color

Bar Object

PropTypeRequiredDescription
labelstringYesBar label. Max 40 chars
valuenumberYesBar value (≥ 0)
colorPaletteColorNoPer-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.

There are two interaction modes, and they are mutually exclusive:

  1. Press to act — leave select at "off" and bind on.press. Each press writes the pressed cell's value (or "row,col" if no value is set) to inputs[name] and fires the bound action (e.g. submit). No selection ring; cells behave like a grid of buttons.
  2. Press to select — set select: "single" or "multiple". Each press accumulates selection state with a visual ring; nothing is posted. Pair with a separate button that submits the accumulated selection. Multi-select joins values with |.

Don't combine on.press with a non-"off" select mode — the auto-fire would defeat the accumulating selection. The component ignores on.press whenever select is on.

PropTypeRequiredDefaultDescription
namestringNo"grid_tap"POST inputs key for the tapped or selected cells
colsnumberYesColumn count (2–32)
rowsnumberYesRow count (2–16)
cellsobject[]YesSparse cell definitions (see below)
gapstringNo"sm"Cell spacing: "none" (0px) "sm" (1px) "md" (2px) "lg" (4px)
cellAspectRatiostringNo"auto"Cell shape: "auto" uses rowHeight; "square" keeps each cell square based on column width
rowHeightnumberNo28Pixel height per row (8–64). Grid height = rows × rowHeight
selectstringNo"off"Selection mode: "off" (use with on.press) "single" "multiple"

Events

EventFires when
pressA cell is pressed, only when select is "off". inputs[name] is set to the pressed cell's value (or "row,col" if no value is set) before the action runs, so a bound submit POST includes the pressed cell.

Cell Object

PropTypeRequiredDescription
rownumberYesRow index (0-based)
colnumberYesColumn index (0-based)
colorPaletteColor or #rrggbbNoCell fill color (hex is independent of page accent)
textColorPaletteColor or #rrggbbNoCell text color. When omitted, clients choose an auto-contrast color from the cell fill.
contentstringNoCell text content
valuestringNoPOST value when this cell is pressed/selected (1–30 chars). When omitted, "row,col" is used. Use this for grids whose cells carry meaningful labels (calendar days, alphabet pickers, region maps) so action handlers don't need to reverse-lookup row/col.

Press to actselect: "off" (default) + on.press. Each press fires immediately:

{
  "type": "cell_grid",
  "props": {
    "name": "color_grid",
    "cols": 4,
    "rows": 4,
    "cells": [
      { "row": 0, "col": 0, "color": "red" },
      { "row": 0, "col": 3, "color": "blue" }
    ]
  },
  "on": {
    "press": { "action": "submit", "params": { "target": "https://my-snap.com/" } }
  }
}

Press to selectselect: "single" or "multiple". Presses update the selection without posting; pair with a button that submits when the user is done:

{
  "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" }
    ],
    "cellAspectRatio": "square",
    "select": "multiple"
  }
}

Value-bearing cells — when each cell represents a meaningful label (a calendar day, an alphabet letter, a region code), set value to keep the action handler clean. The handler receives the value directly instead of having to translate "row,col" back into a label:

{
  "type": "cell_grid",
  "props": {
    "name": "day",
    "cols": 7,
    "rows": 5,
    "cells": [
      { "row": 0, "col": 0, "value": "1", "content": "1" },
      { "row": 0, "col": 1, "value": "2", "content": "2" },
      { "row": 0, "col": 2, "value": "3", "content": "3" }
    ]
  },
  "on": {
    "press": { "action": "submit", "params": { "target": "https://my-snap.com/" } }
  }
}

The POST handler reads inputs.day as "3" directly — no row/col math.


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.

PropTypeRequiredDefaultDescription
namestringYesInput name (POST inputs key)
typestringNo"text""text" or "number"
labelstringNoLabel text above input. Max 60 chars
placeholderstringNoPlaceholder text. Max 60 chars
defaultValuestringNoPre-filled value
maxLengthnumberNoMax 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.

Rating (1-10)7
PropTypeRequiredDefaultDescription
namestringYesSlider name (POST inputs key)
minnumberYesMinimum value (must be ≤ max)
maxnumberYesMaximum value (must be ≥ min)
stepnumberNo1Increment step (must be > 0, finite)
defaultValuenumberNomidpointInitial value (must be between min and max)
labelstringNoLabel text above slider. Max 60 chars
showValuebooleanNofalseDisplay the current value next to the label

POST value: number.

{
  "type": "slider",
  "props": {
    "name": "rating",
    "label": "Rating (1–10)",
    "min": 1,
    "max": 10,
    "showValue": true
  }
}

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.

Enable notifications
PropTypeRequiredDefaultDescription
namestringYesSwitch name (POST inputs key)
labelstringNoLabel text beside the switch. Max 60 chars
defaultCheckedbooleanNofalseInitial 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.

PropTypeRequiredDefaultDescription
namestringYesGroup name (POST inputs key)
optionsstring[]YesChoice labels. Min 2, max 6. Each max 30 chars
multiplebooleanNofalseAllow multiple selections
orientationstringNo"horizontal""horizontal" or "vertical"
defaultValuestring or string[]NoPre-selected option(s)
variantstringNo"default""default" (solid) or "outline" (bordered)
labelstringNoLabel 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"]
  }
}