Button
Field-level catalog tables below stay in English — they match the machine design schema used by the editor, API, and MCP.
Decoration and visibility use Decoration & visibility (BlockShell).
Call-to-action or named dataRequest trigger. Prefer sitemap routes for navigate. For forms/search use action runRequest|runMutation with requestName — request behavior (filters, fieldValues, resultWrites) lives on root.props.dataRequests. loadingText/successText/errorText update the visible button label and a visually hidden
role="status"live region (not a second visible status line). resetOnSuccess (default true for runMutation) clears request field variables after a short success display. Client validation blocks empty required fields via native validity before the request runs (does not set request error / errorText). A single set-variable button whose value equals the current variable is pressed (aria-pressedanddata-selected). The value may be a literal or{{var:item.field}}. Two or more buttons that set the same variable are a radiogroup; the selected one hasaria-checkedanddata-selected(notaria-pressed), and arrow keys move the selection. Fill and outline chrome come from appearance.variant plus decoration background and border. Compact nav/footer links use appearance.variant"text"(default) — there is no stored"link"variant. Labeled text buttons have 0 intrinsic padding and no ButtonBlock min-height or touch-target floor; vertical size follows typography.size (floor 12px). Filled/outline add 12px vertical padding. Keep shell padding off for compact links; shell padding or heightStatic enlarges the control. Icons live as{{icon:…}}chips intext(not a separate icon prop). Icon-only controls get an accessible name fromariaLabel(localizable), else a derived label (social brands, Menu for bars-, Theme for theme action). Locale actions default to showing the locale code; icon-onlytextkeeps the authored chips, andlocaleLabelMode: "custom"keeps any authored label. Modal toggles set aria-expanded. A navigate button whose href matches the open page (ignoring a trailing slash and the locale prefix) setsaria-current="page"anddata-selected. Selection paints nothing on its own: style it by authoring aselecteddecoration overlay (or anactivetext color). Variable radiogroup buttons are left unchanged. OptionaliconSize/iconGap(px) override auto icon scale (~0.85× fontSize) and gap. For non-stretch CTAs keep `shell.box.sizeByBreakpoint..widthMode: "fitContent"` (ButtonBlock defaults include this).
Examples
Primary CTA
{
"content": {
"text": "Create thread"
},
"action": {
"url": "/categories/[id]/new"
}
}Run search request
{
"content": {
"text": "Search"
},
"action": {
"kind": "runRequest",
"requestName": "searchX",
"loadingText": "Searching…"
}
}Set a variable from the current row
{
"action": {
"kind": "updateVariables",
"variableUpdates": [
{
"variableId": "size",
"op": "set",
"value": "{{var:item}}"
}
]
},
"content": {
"text": "{{var:item}}"
}
}Icon-only menu toggle
{
"content": {
"text": "{{icon:bars-3}}"
},
"action": {
"kind": "modal",
"modalId": "ModalBlock-header-menu"
},
"ariaLabel": "Menu"
}Fields
content
text
Button label. Mix literal text with {{var:id}}, {{bind:id}}, and {{icon:iconId}} chips. The icon picker appends an icon chip at the end; drag chips in the label to reposition. An older icon prop is migrated into a leading {{icon:…}} chip on design write / editor mount.
content: {
text: "Browse {{icon:arrow-right}}"
}ariaLabel
Accessible name for icon-only buttons/links. Overrides icon-derived labels. Ignored when visible text is present.
ariaLabel: "Menu"typography
textAlign
Horizontal text alignment. On a heading, an explicit left is stored and pulls the block to the start of a centered flex parent. Omitting it keeps the parent alignment.
Options: left, center, right
textAlign: "center"fontSize
Unit: px
Font size in pixels. Send a JSON number (48), not a string. On HeadingBlock this is a single size for all breakpoints — not a responsive map; HeadingBlock has no lineHeight (use RichTextBlock for that).
fontSize: 48fontWeight
Numeric font weight (CSS font-weight).
fontWeight: 600fontFamily
Font family. A theme token (theme-heading, theme-body, theme-mono), a rich-text font id, a CSS generic family (sans-serif, serif, monospace, system-ui, ui-sans-serif, cursive, fantasy), or a comma-separated stack.
fontFamily: "theme-heading"italic
When true, renders the text in italic style.
typography: {
italic: false
}underline
Underline.
appearance
appearance
Visual style: "filled", "outline", or "text" (link-style chrome; there is no "link" variant). Stored at appearance.variant. Text + label uses 0 padding and no min-height; filled/outline add 12px vertical padding. Fill and outline also use decoration background and border.
appearance: "text"iconSize
Optional icon size in px. When omitted, icons auto-scale to ~0.85× fontSize.
appearance: {
iconSize: 20
}iconGap
Optional gap between icon chips and text in px. When omitted, uses a font-based gap.
appearance: {
iconGap: 8
}color
text.textColorLight
Text color used in light theme.
textColorLight: "#111827"text.textColorDark
text.hover
textColorHoverLight
textColorHoverDark
text.active
textColorActiveLight
textColorActiveDark
action
action
See shared option catalog action.
Options: navigate, copyEntryLink, shareSocial, websiteUserSignOut, notifications, updateVariables, runRequest, runMutation, modal, locale, theme, requestNextPage, requestPrevPage, sliderPrev, sliderNext, sliderGoTo, consentCookies, consentPush, consentAll
action: "navigate"url
Link value stored at action.url. A string, or { url, params?, entryId?, routePageId? }. See get_docs design/shared-concepts/links.
entryId plus routePageId build the public URL from the route pattern and the entry. The last [param] is the entry. Earlier [relation.field] segments come from that entry, including {{var:item.id}} in a repeated list.
action: {
url: { url: "catalogue/[slug]", params: { slug: "baby-wipes" }, entryId, routePageId }
}modalId
When action is modal, the target ModalBlock’s stable props.id. Renaming the modal does not change this id. Missing targets no-op at runtime.
action: {
modalId: "ModalBlock-abc123"
}variableUpdates
Stored under the action group next to action updateVariables. Each entry is { variableId, op, value }. op is set, clear, add, or subtract. Number add and subtract clamp to 1…pageCount when that variable pages a list. value may be a literal or a whole token such as {{var:item}}; the click resolves tokens before writing the variable.
action: {
variableUpdates: [{ variableId: "size", op: "set", value: "{{var:item}}" }, { variableId: "page", op: "add", value: 1 }]
}requestName
Named dataRequest for runRequest/runMutation.
loadingText
Label while the named request is loading.
successText
Label after a successful run.
errorText
Label when the named request fails.
resetOnSuccess
When true (default for runMutation), clear request field variables after a short delay. Success text still displays for several seconds either way. When false, keep field values.
action: {
resetOnSuccess: false
}<details><summary>Default props</summary>
{
"shell": {
"visibility": {
"authVisibility": "all",
"routeLockVisibility": "all",
"roleVisibility": [],
"entryActionVisibility": "all",
"queryPageVisibility": "all",
"hideWhenEmpty": ""
},
"anchor": {
"anchorId": "",
"anchorKey": ""
},
"spacing": {
"padding": {
"value": "none",
"enabled": false
},
"margin": {
"enabled": false
}
},
"box": {
"sizeByBreakpoint": {
"mobile": {
"widthMode": "fitContent"
},
"desktop": {
"widthMode": "fitContent"
}
},
"enabled": false
},
"background": {
"enabled": false,
"width": "page",
"kind": "solid",
"solid": {
"color": {
"light": "",
"dark": ""
}
},
"gradient": {
"angle": 180,
"type": "linear",
"colors": [
{
"light": "",
"dark": "",
"offset": 50
},
{
"light": "",
"dark": "",
"offset": 50
}
],
"color1Light": "",
"color1Dark": "",
"color2Light": "",
"color2Dark": ""
},
"image": {
"src": "",
"mode": "cover",
"repeatSize": 100,
"position": "center"
},
"motion": {
"enabled": false,
"directionDeg": 0,
"speed": 20,
"rotateDegPerSec": 0,
"animationMode": "particle"
},
"video": {
"src": "",
"source": "file",
"youtubeUrl": "",
"fit": "cover",
"loop": true,
"repeatSize": 100
},
"particles": {
"shape": "circle",
"svgSrc": "",
"colorMode": "solid",
"colorLight": "#ffffff88",
"colorDark": "#ffffff66",
"size": 12,
"jitter": 0.2,
"count": 40,
"lifetimeSec": 4,
"speed": 40,
"animType": "fade",
"fromSide": "left",
"toSide": "right"
},
"grid": {
"style": "cells",
"motionDirectionDeg": 0,
"motionSpeed": 0,
"rotateDegPerSec": 0,
"hoverGrowLimitPct": 50,
"hoverRotateLimitDeg": 0,
"reactionTimeSec": 1,
"hoverGrow": true,
"hoverRotate": false,
"hoverColor": false,
"hoverColorLight": "#ffffff55",
"hoverColorDark": "#ffffff33",
"reactionSpeed": 1
},
"backdrop": {
"enabled": false,
"blurPx": 0,
"brightness": 1,
"contrast": 1,
"saturate": 1,
"grayscale": 0,
"sepia": 0,
"hueRotateDeg": 0
}
},
"pointerEvents": "auto",
"effects": {
"enabled": false,
"opacity": 1,
"blurPx": 0,
"brightness": 1,
"contrast": 1,
"saturate": 1,
"grayscale": 0,
"sepia": 0,
"hueRotateDeg": 0,
"dropShadowOffsetX": 0,
"dropShadowOffsetY": 0,
"dropShadowBlur": 0,
"dropShadowColorLight": "",
"dropShadowColorDark": "",
"mixBlendMode": "normal"
},
"animation": {
"scroll": {
"enabled": false,
"type": "fadeIn",
"duration": 600,
"delay": 0
},
"tilt": {
"enabled": false,
"intensity": 12
},
"wander": {
"enabled": false,
"speed": 40,
"jitter": 40,
"turnRadius": 50,
"turnSmoothness": 40,
"limits": "parent"
},
"transform": {
"enabled": false,
"rotateX": 0,
"rotateY": 0,
"rotateZ": 0,
"translateX": 0,
"translateY": 0,
"translateZ": 0,
"scale": 1
}
}
},
"appearance": {
"border": {
"enabled": false,
"style": "solid",
"colorLight": "",
"colorDark": ""
},
"shadow": {
"enabled": false,
"valueLight": "",
"valueDark": ""
},
"variant": "text",
"color": {
"text": {
"light": "",
"dark": "",
"hover": {
"light": "",
"dark": ""
},
"active": {
"light": "",
"dark": ""
}
}
}
},
"content": {
"text": "Button"
},
"typography": {
"align": "left",
"size": 18,
"weight": 600,
"family": "theme-heading",
"italic": false,
"underline": false
},
"action": {
"kind": "navigate",
"openInNewTab": false,
"copiedText": "Copied",
"socialNetwork": "whatsapp",
"returnUrl": "/",
"unreadBadge": true
}
}</details>