llms.txt

Elements

Every snap page body is a tree whose root is a stack. The client renders child elements in order, top to bottom. The client controls sizing, spacing, fonts, and padding -- snaps do not specify pixel dimensions, margins, or CSS.

Sample: Open the Element Showcase in the emulator to interact with every element type.

Page Root: stack

page.elements must be a stack node: a vertical list of child elements.

  • page.elements.type MUST be "stack"
  • page.elements.children is an array of elements (min 1, max 5)
  • Max 1 media element (image or grid) per page
{
  "type": "stack",
  "children": [{ "type": "text", "style": "title", "content": "Hello" }]
}

text

Renders text content in a predefined style.

Title style
Body style — for main descriptive text

Caption style — secondary info

Label style
{ "type": "text", "style": "title", "content": "Rate these movies" }
PropertyRequiredValues
styleYes"title" (max 80 chars), "body" (max 160 chars), "caption" (max 100 chars), "label" (max 40 chars)
contentYesThe text string
alignNo"left" (default), "center", "right"

image

Renders an image from a URL.

Sample landscape
{ "type": "image", "url": "https://example.com/photo.jpg", "aspect": "16:9" }
PropertyRequiredValues
urlYesHTTPS image URL. Supports jpg, png, gif, webp. GIFs autoplay and loop
aspectYes"1:1", "16:9", "4:3", "3:4", "9:16"
altNoAlt text for accessibility

divider

A horizontal line to visually separate content sections.

Poll Results

42 votes


Closes in 2 hours

{ "type": "divider" }

No additional properties.

spacer

Vertical breathing room. The client determines actual height.

{ "type": "spacer", "size": "medium" }
PropertyRequiredValues
sizeNo"small", "medium" (default), "large"

progress

A horizontal progress bar.

72% Yes

{ "type": "progress", "value": 72, "max": 100, "label": "72% Yes" }
PropertyRequiredValues
valueYesNumber, current value
maxYesNumber, maximum value
labelNoText label displayed alongside. Max 60 chars
colorNo"accent" (default), "green", "red", "amber", "gray"

list

An ordered or unordered list of items.

  1. @dwr.eth8/10 (80%)
  2. @jessepollak7/10 (70%)
{
  "type": "list",
  "style": "ordered",
  "items": [
    { "content": "@dwr.eth", "trailing": "8/10 (80%)" },
    { "content": "@jessepollak", "trailing": "7/10 (70%)" }
  ]
}
PropertyRequiredValues
styleNo"ordered" (default), "unordered", "plain"
itemsYesArray of list items. Max 4 items
items[].contentYesItem text. Max 100 chars
items[].trailingNoRight-aligned text. Max 40 chars

grid

A rows-by-columns grid of cells. Each cell has a background color and optional text content. For game boards, pixel canvases, and tile-based UIs.

C
R
A
N
E
S
N
A
P
S
{
  "type": "grid",
  "cols": 5,
  "rows": 6,
  "cells": [
    { "row": 0, "col": 0, "color": "#22C55E", "content": "C" },
    { "row": 0, "col": 1, "color": "#6B7280", "content": "R" }
  ]
}
PropertyRequiredValues
colsYesNumber of columns. Min 2, max 64
rowsYesNumber of rows. Min 2, max 8
cellsYesArray of cell definitions. Only non-empty cells need to be specified
cells[].rowYesRow index (0-based)
cells[].colYesColumn index (0-based)
cells[].colorNo6-digit hex color (#RRGGBB) for background. Omit for transparent
cells[].contentNoText content to display in the cell
cellSizeNo"auto" (default, fills available width), "square" (cells are square)
gapNo"none", "small" (default), "medium"
interactiveNoIf true, cells with no entry in cells array are tappable. Tap coordinates are included in the next POST

text_input

A single-line text input field.

{
  "type": "text_input",
  "name": "guess",
  "placeholder": "Type 5-letter word...",
  "maxLength": 5
}
PropertyRequiredValues
nameYesField identifier, included in POST data
placeholderNoPlaceholder text. Max 60 chars
maxLengthNoMax input length. Max 280 chars

slider

A horizontal slider for numeric input.

Your estimate
{
  "type": "slider",
  "name": "estimate",
  "min": 0,
  "max": 100,
  "step": 1,
  "label": "Your estimate"
}
PropertyRequiredValues
nameYesField identifier, included in POST data
minYesMinimum value (number)
maxYesMaximum value (number)
stepNoStep increment. Default: 1
valueNoInitial value. Default: midpoint
labelNoLabel text. Max 60 chars
minLabelNoLabel at left end. Max 20 chars
maxLabelNoLabel at right end. Max 20 chars

button_group

A set of tappable options. User selects one.

{
  "type": "button_group",
  "name": "vote",
  "options": ["Tabs", "Spaces"],
  "style": "row"
}
PropertyRequiredValues
nameYesField identifier, included in POST data
optionsYesArray of option strings. Min 2, max 4. Each max 40 chars
styleNo"row" (side by side, default for 2-3), "stack" (vertical, default for 4+), "grid" (2-col grid)

toggle

A single on/off toggle.

Enable reminders
{
  "type": "toggle",
  "name": "notifications",
  "label": "Enable reminders",
  "value": false
}
PropertyRequiredValues
nameYesField identifier, included in POST data
labelYesLabel text. Max 60 chars
valueNoInitial state. Default: false

bar_chart

A vertical bar chart for displaying labeled values. For poll results, rankings, and distributions.

21

18

10

Anthropic

Databricks

OpenAI

{
  "type": "bar_chart",
  "bars": [
    { "label": "Anthropic", "value": 21 },
    { "label": "Databricks", "value": 18 },
    { "label": "OpenAI", "value": 10, "color": "teal" }
  ],
  "max": 100
}
PropertyRequiredValues
barsYesArray of bar objects. Min 1, max 6
bars[].labelYesBar label text. Max 40 chars
bars[].valueYesNumeric value (>= 0)
bars[].colorNoPalette name (e.g. "green", "red"). Overrides chart color
maxNoScale maximum. If omitted, derived from largest bar value
colorNoDefault bar color: "accent" (default), "green", "red", "amber", "blue", "gray"

group

Arranges child elements horizontally in a row. For displaying related stats, inputs, or content side by side.

42
score
7
streak
92%
win rate
{
  "type": "group",
  "layout": "row",
  "children": [
    { "type": "text", "style": "title", "content": "42" },
    { "type": "text", "style": "caption", "content": "score" }
  ]
}
PropertyRequiredValues
layoutYes"row" (horizontal arrangement)
childrenYesArray of child elements. Min 2, max 3

Group Rules

  • A group counts as 1 element toward the page max of 5
  • No media elements inside groups (image, grid are not allowed)
  • No nesting: groups cannot contain other groups
  • Children are rendered with equal width, side by side
  • Any non-media element is valid as a child: text, progress, list, slider, button_group, toggle, text_input, divider, spacer, bar_chart

First Page Requirements

The first page returned from the snap URL is rendered as the feed card. In addition to normal page rules, the first page MUST include:

  • At least one text element with style: "title" or style: "body"
  • At least one interactive element (button_group, slider, text_input, toggle) OR at least one media element (image or grid)

This ensures the feed card always has readable content plus engagement or visual context.

Example: Valid First Page

Best sci-fi movies

Pick your favorite, then tap Vote

Vote

{
  "version": "1.0",
  "page": {
    "theme": { "accent": "purple" },
    "button_layout": "stack",
    "elements": {
      "type": "stack",
      "children": [
        { "type": "text", "style": "title", "content": "Best sci-fi movies" },
        {
          "type": "button_group",
          "name": "pick",
          "options": ["Arrival", "Dune", "Interstellar"]
        },
        {
          "type": "text",
          "style": "caption",
          "content": "Pick your favorite, then tap Vote"
        }
      ]
    },
    "buttons": [
      {
        "label": "Vote",
        "action": "post",
        "target": "https://example.com/vote"
      }
    ]
  }
}

This is valid because it has a title text element and an interactive element (button_group).

Common Validation Errors

These examples show snap responses that fail validation and why.

No title or body text

VALIDATION ERROR
{
  "version": "1.0",
  "page": {
    "elements": {
      "type": "stack",
      "children": [
        {
          "type": "image",
          "url": "https://example.com/photo.jpg",
          "aspect": "16:9"
        }
      ]
    }
  }
}

Fails: First page must have at least one text element with style "title" or "body". An image alone isn't enough.

No interactive or media element

Hello
Welcome to my snap
VALIDATION ERROR
{
  "version": "1.0",
  "page": {
    "elements": {
      "type": "stack",
      "children": [
        { "type": "text", "style": "title", "content": "Hello" },
        { "type": "text", "style": "body", "content": "Welcome to my snap" }
      ]
    }
  }
}

Fails: First page must have at least one interactive element (button_group, slider, text_input, toggle) or media element (image, grid). Text-only pages are invalid.

Too many elements

Title
Body
1. Item 1

Too many!

6 elements — max is 5

VALIDATION ERROR
{
  "version": "1.0",
  "page": {
    "elements": {
      "type": "stack",
      "children": [
        { "type": "text", "style": "title", "content": "Title" },
        { "type": "text", "style": "body", "content": "Body" },
        { "type": "progress", "value": 50, "max": 100 },
        { "type": "list", "items": [{ "content": "Item 1" }] },
        { "type": "slider", "name": "val", "min": 0, "max": 100 },
        { "type": "text", "style": "caption", "content": "Too many!" }
      ]
    }
  }
}

Fails: Max 5 elements per page. This has 6.

Two media elements

Gallery

2 images — max is 1 media element

VALIDATION ERROR
{
  "version": "1.0",
  "page": {
    "elements": {
      "type": "stack",
      "children": [
        { "type": "text", "style": "title", "content": "Gallery" },
        {
          "type": "image",
          "url": "https://example.com/a.jpg",
          "aspect": "16:9"
        },
        {
          "type": "image",
          "url": "https://example.com/b.jpg",
          "aspect": "16:9"
        }
      ]
    }
  }
}

Fails: Max 1 media element (image or grid) per page.

Hex accent (must be palette name)

Hello
Agree

accent: "#8B5CF6" — must be "purple"

VALIDATION ERROR
{
  "version": "1.0",
  "page": {
    "theme": { "accent": "#8B5CF6" },
    "elements": {
      "type": "stack",
      "children": [
        { "type": "text", "style": "title", "content": "Hello" },
        { "type": "toggle", "name": "ok", "label": "Agree" }
      ]
    }
  }
}

Fails: accent must be a palette name (gray, blue, red, amber, green, teal, purple, pink), not a hex value. Use "purple" instead of "#8B5CF6".