Elle catalog
The closed vocabulary Elle composes generated sheets from (packages/ui-schema). 16 components. To grow it, see docs/strata/generative-ui.md.
Grid
Side-by-side arrangement: a row of StatCards, paired charts. 2 columns unless density demands more. The ONLY container — the sheet flows top-level elements vertically with house spacing.
ui/gridContainer
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"columns": {
"default": 2,
"description": "Number of columns (1-4)",
"type": "integer",
"minimum": 1,
"maximum": 4
},
"gap": {
"default": "md",
"description": "Gap between cells",
"type": "string",
"enum": [
"sm",
"md",
"lg"
]
}
},
"required": [
"columns",
"gap"
],
"additionalProperties": false
}StatCard
A single KPI with optional period-over-period delta. Group several in a Grid for a summary row.
ui/stat-card
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Metric label (e.g. 'Total Views')"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "null"
}
],
"description": "The metric value; numbers auto-format compactly (1.2M)"
},
"subtitle": {
"description": "Context line under the value",
"type": "string"
},
"delta": {
"description": "Percent change vs the previous period (e.g. 23.5)",
"type": "number"
},
"deltaInverted": {
"description": "Set when DOWN is good (cost, churn)",
"type": "boolean"
},
"previousValue": {
"description": "The previous period's value, already formatted",
"type": "string"
},
"icon": {
"description": "Lucide icon name in kebab-case (e.g. 'eye', 'trending-up')",
"type": "string"
}
},
"required": [
"label",
"value"
],
"additionalProperties": false
}TrendChart
A time series as the house bar chart (hover shows per-point values). One metric per chart.
ui/trend-chart
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Chart title (e.g. 'Daily Views')"
},
"data": {
"description": "Inline series (small series only — prefer dataPath)",
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Point label (e.g. a date: 'Jun 4')"
},
"value": {
"type": "number",
"description": "Point value"
}
},
"required": [
"label",
"value"
],
"additionalProperties": false
}
},
"dataPath": {
"type": "string",
"pattern": "^\\/toolResults\\/[A-Za-z0-9_-]+(?:\\/[^/~]+)*$",
"description": "JSON Pointer to an array of data points inside a tool result you already received, e.g. \"/toolResults/getDailyMetrics/data\". Prefer this over inlining large arrays."
},
"xKey": {
"description": "Row field for point labels (e.g. 'date'); defaults to 'label'",
"type": "string"
},
"yKey": {
"description": "Row field for point values (e.g. 'views_count'); defaults to 'value'",
"type": "string"
},
"subtitle": {
"description": "Period line under the total (e.g. 'Last 30 days')",
"type": "string"
},
"color": {
"description": "House palette token; omit for the default",
"type": "string",
"enum": [
"chart-1",
"chart-2",
"chart-3",
"chart-4",
"chart-5"
]
}
},
"required": [
"title"
],
"additionalProperties": false
}DataTable
Ranked or comparable records: posts, campaigns, creators. Declare columns; bind rows to a tool result.
ui/data-table
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"columns": {
"minItems": 1,
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Row field this column reads; dots reach nested fields ('metrics.views')"
},
"label": {
"type": "string",
"description": "Column header"
},
"align": {
"description": "Right-align numbers",
"type": "string",
"enum": [
"left",
"right"
]
},
"format": {
"description": "Value formatting: compact = 1.2M, currency = USD, date = locale date",
"type": "string",
"enum": [
"text",
"number",
"compact",
"currency",
"percent",
"date"
]
}
},
"required": [
"key",
"label"
],
"additionalProperties": false
},
"description": "Column definitions, in display order"
},
"rows": {
"description": "Inline rows (small sets only — prefer dataPath)",
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
]
}
}
},
"dataPath": {
"type": "string",
"pattern": "^\\/toolResults\\/[A-Za-z0-9_-]+(?:\\/[^/~]+)*$",
"description": "JSON Pointer to an array of row objects inside a tool result you already received, e.g. \"/toolResults/getDailyMetrics/data\". Prefer this over inlining large arrays."
},
"maxRows": {
"description": "Cap the rendered rows",
"type": "integer",
"minimum": 1,
"maximum": 50
},
"emptyMessage": {
"description": "Shown when there are no rows",
"type": "string"
}
},
"required": [
"columns"
],
"additionalProperties": false
}Badge
A short status or category chip (Active, Failed, Organic).
ui/badge
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Badge text"
},
"variant": {
"default": "secondary",
"description": "Semantic style",
"type": "string",
"enum": [
"default",
"secondary",
"destructive",
"outline",
"success"
]
}
},
"required": [
"label",
"variant"
],
"additionalProperties": false
}Markdown
Prose: explanations, takeaways, caveats. Keep it short — the chat voice carries the narrative.
ui/markdown
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "GitHub-flavored markdown prose (headings, lists, tables)"
}
},
"required": [
"text"
],
"additionalProperties": false
}SectionLabel
A mono uppercase heading that starts a sheet section.
ui/section-label
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Uppercase section heading (e.g. 'TOP POSTS')"
}
},
"required": [
"text"
],
"additionalProperties": false
}Separator
A horizontal rule between sections.
ui/separator
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"additionalProperties": false
}MediaCard
A single piece of social media — one TikTok video or photo slideshow — with its metrics and creator. Hover plays the video / cycles the slides. Give it an action to make it tappable.
ui/live-post-card
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"media": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "A video URL, or an array of image URLs for a slideshow"
},
"mediaType": {
"description": "Override the kind; inferred from the URL when omitted (an array is always images)",
"type": "string",
"enum": [
"video",
"image"
]
},
"poster": {
"description": "Poster/cover image URL shown before a video plays",
"type": "string"
},
"author": {
"description": "Creator handle or name (e.g. \"@nike\")",
"type": "string"
},
"platform": {
"description": "Source platform for the corner icon (e.g. 'tiktok', 'instagram')",
"type": "string"
},
"primaryStat": {
"description": "Headline metric, already formatted (e.g. \"1.2M\")",
"type": "string"
},
"primaryLabel": {
"description": "Headline metric label (e.g. \"Views\")",
"type": "string"
},
"stats": {
"description": "Up to ~4 secondary metrics",
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Metric label (e.g. \"Likes\")"
},
"value": {
"type": "string",
"description": "The metric, already formatted (e.g. \"12.4K\")"
}
},
"required": [
"label",
"value"
],
"additionalProperties": false
}
},
"action": {
"description": "What tapping the card does",
"oneOf": [
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "navigate"
},
"to": {
"type": "string",
"minLength": 1,
"description": "In-app destination. Project-relative (no leading slash) is resolved under the current project — e.g. 'create/slideshow-remix', 'social/research'. A leading slash is used verbatim."
},
"search": {
"description": "Query params, e.g. { sourceVideoId: '7123...' }",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"label": {
"description": "Optional button text (defaults to a sensible verb)",
"type": "string"
}
},
"required": [
"kind",
"to"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "message"
},
"text": {
"type": "string",
"minLength": 1,
"description": "Sent to Elle as the user's next message when tapped, e.g. 'Make a slideshow from this video'"
},
"label": {
"description": "Optional button text (defaults to the message)",
"type": "string"
}
},
"required": [
"kind",
"text"
],
"additionalProperties": false
}
]
}
},
"required": [
"media"
],
"additionalProperties": false
}MediaGrid
A gallery of social posts (inspiration, trending clips, a content portfolio) bound to a tool result via dataPath. Each item renders as a media card; give it an action and tapping a card navigates or messages Elle (e.g. "remix this"). This is the surface for "suggest videos to make content from".
ui/live-post-card
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"items": {
"description": "Inline items (small sets only — prefer dataPath)",
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"dataPath": {
"type": "string",
"pattern": "^\\/toolResults\\/[A-Za-z0-9_-]+(?:\\/[^/~]+)*$",
"description": "JSON Pointer to an array of media items (e.g. /toolResults/getInspiration/items) inside a tool result you already received, e.g. \"/toolResults/getDailyMetrics/data\". Prefer this over inlining large arrays."
},
"columns": {
"description": "Fixed column count; omit for a responsive auto-fit grid",
"type": "integer",
"minimum": 1,
"maximum": 3
},
"maxItems": {
"description": "Cap the rendered cards",
"type": "integer",
"minimum": 1,
"maximum": 24
},
"mediaKey": {
"description": "Item field with the media URL or slide-URL array (default 'media')",
"type": "string"
},
"mediaTypeKey": {
"description": "Item field with 'video'|'image' (default 'mediaType')",
"type": "string"
},
"posterKey": {
"description": "Item field with the poster/cover URL (default 'poster')",
"type": "string"
},
"authorKey": {
"description": "Item field for the author handle (default 'author')",
"type": "string"
},
"platformKey": {
"description": "Item field for the platform (default 'platform')",
"type": "string"
},
"primaryStat": {
"description": "Headline metric per card (default reads 'views')",
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Item field this stat reads; dots reach nested fields"
},
"label": {
"type": "string",
"description": "Stat label"
},
"format": {
"description": "Value formatting (default compact for numbers)",
"type": "string",
"enum": [
"text",
"number",
"compact",
"currency",
"percent",
"date"
]
}
},
"required": [
"key",
"label"
],
"additionalProperties": false
},
"stats": {
"description": "Secondary metric rows (default likes/comments/shares)",
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Item field this stat reads; dots reach nested fields"
},
"label": {
"type": "string",
"description": "Stat label"
},
"format": {
"description": "Value formatting (default compact for numbers)",
"type": "string",
"enum": [
"text",
"number",
"compact",
"currency",
"percent",
"date"
]
}
},
"required": [
"key",
"label"
],
"additionalProperties": false
}
},
"action": {
"description": "What tapping a card does",
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"navigate",
"message"
],
"description": "navigate to a page, or message Elle"
},
"to": {
"description": "navigate: in-app destination, project-relative (e.g. 'create/slideshow-remix'). Required for kind=navigate.",
"type": "string"
},
"search": {
"description": "navigate: static query params shared by every card",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"searchFromKeys": {
"description": "navigate: query-param name → item field key, filled per card (e.g. { sourceVideoId: 'videoId' })",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"textTemplate": {
"description": "message: the text sent to Elle, with {field} placeholders filled from the item (e.g. 'Make a slideshow from {videoId}'). Required for kind=message.",
"type": "string"
},
"label": {
"description": "Optional affordance label",
"type": "string"
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"display": {
"description": "Card treatment: 'post' (default) renders the social-post card (9:16 media pane + stats); 'plain' renders a bare image/video card at its natural aspect ratio — use for ad previews and any media that isn't a social post",
"type": "string",
"enum": [
"post",
"plain"
]
},
"emptyMessage": {
"description": "Shown when there are no items",
"type": "string"
}
},
"additionalProperties": false
}Heading
A prose heading for a prominent sheet or section title. Use SectionLabel for a mono uppercase divider; use this for a real title.
ui/heading
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Heading text"
},
"variant": {
"default": "heading",
"description": "Type scale: display = hero, title = section, heading = sheet title, subhead = pane header",
"type": "string",
"enum": [
"display",
"title",
"heading",
"subhead"
]
}
},
"required": [
"text",
"variant"
],
"additionalProperties": false
}Callout
A bordered banner for the one thing that should not be missed — a key insight, a recommendation, or a warning (variant destructive). Not for ordinary prose (use Markdown).
ui/alert
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The message body"
},
"title": {
"description": "Optional bold lead line",
"type": "string"
},
"variant": {
"default": "default",
"description": "'destructive' for warnings/errors; 'default' otherwise",
"type": "string",
"enum": [
"default",
"destructive"
]
},
"icon": {
"description": "Lucide icon name in kebab-case (e.g. 'sparkles', 'flame')",
"type": "string"
}
},
"required": [
"text",
"variant"
],
"additionalProperties": false
}Button
A standalone call-to-action. Tapping runs its action (navigate to a page, or message you). Use for the next step at the end of a sheet ("Open the builder", "Schedule these").
ui/button
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "Button text"
},
"action": {
"oneOf": [
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "navigate"
},
"to": {
"type": "string",
"minLength": 1,
"description": "In-app destination. Project-relative (no leading slash) is resolved under the current project — e.g. 'create/slideshow-remix', 'social/research'. A leading slash is used verbatim."
},
"search": {
"description": "Query params, e.g. { sourceVideoId: '7123...' }",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"label": {
"description": "Optional button text (defaults to a sensible verb)",
"type": "string"
}
},
"required": [
"kind",
"to"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "message"
},
"text": {
"type": "string",
"minLength": 1,
"description": "Sent to Elle as the user's next message when tapped, e.g. 'Make a slideshow from this video'"
},
"label": {
"description": "Optional button text (defaults to the message)",
"type": "string"
}
},
"required": [
"kind",
"text"
],
"additionalProperties": false
}
],
"description": "What tapping the button does (navigate or message Elle)"
},
"variant": {
"default": "default",
"description": "Visual weight; use default for the primary action",
"type": "string",
"enum": [
"default",
"outline",
"secondary",
"ghost",
"destructive"
]
},
"icon": {
"description": "Lucide icon name in kebab-case",
"type": "string"
}
},
"required": [
"label",
"action",
"variant"
],
"additionalProperties": false
}Card
A titled, bordered container that groups related elements into one block (e.g. a strategy summary, a single record's details). A container — nest its children by id.
ui/cardContainer
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"description": "Card title",
"type": "string"
},
"subtitle": {
"description": "Supporting line under the title",
"type": "string"
}
},
"additionalProperties": false
}List
A compact, non-tabular list of records (accounts, campaigns, steps, options) bound to a tool result via dataPath — title + optional subtitle + status chip per row. Give it an action to make rows tappable. Prefer DataTable when columns of metrics matter; use List for lighter, scannable rows.
ui/list-row
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"items": {
"description": "Inline rows (small sets only — prefer dataPath)",
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"dataPath": {
"type": "string",
"pattern": "^\\/toolResults\\/[A-Za-z0-9_-]+(?:\\/[^/~]+)*$",
"description": "JSON Pointer to an array of row objects inside a tool result you already received, e.g. \"/toolResults/getDailyMetrics/data\". Prefer this over inlining large arrays."
},
"titleKey": {
"description": "Item field for the row title (default 'title')",
"type": "string"
},
"subtitleKey": {
"description": "Item field for the secondary line (default 'subtitle')",
"type": "string"
},
"badgeKey": {
"description": "Item field for a right-aligned status chip",
"type": "string"
},
"maxItems": {
"description": "Cap the rendered rows",
"type": "integer",
"minimum": 1,
"maximum": 50
},
"action": {
"description": "What tapping a row does",
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"navigate",
"message"
],
"description": "navigate to a page, or message Elle"
},
"to": {
"description": "navigate: in-app destination, project-relative (e.g. 'create/slideshow-remix'). Required for kind=navigate.",
"type": "string"
},
"search": {
"description": "navigate: static query params shared by every card",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"searchFromKeys": {
"description": "navigate: query-param name → item field key, filled per card (e.g. { sourceVideoId: 'videoId' })",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"textTemplate": {
"description": "message: the text sent to Elle, with {field} placeholders filled from the item (e.g. 'Make a slideshow from {videoId}'). Required for kind=message.",
"type": "string"
},
"label": {
"description": "Optional affordance label",
"type": "string"
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"emptyMessage": {
"description": "Shown when there are no rows",
"type": "string"
}
},
"additionalProperties": false
}Progress
A horizontal completion bar (0–100) — goal progress, pipeline completion, budget used. Pair with a label.
ui/progress
Props schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"value": {
"type": "number",
"description": "Completion 0–100"
},
"label": {
"description": "Label shown above the bar (with the % on the right)",
"type": "string"
}
},
"required": [
"value"
],
"additionalProperties": false
}