HOC SiteDocumentation

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-pressed and data-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 has aria-checked and data-selected (not aria-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 in text (not a separate icon prop). Icon-only controls get an accessible name from ariaLabel (localizable), else a derived label (social brands, Menu for bars-, Theme for theme action). Locale actions default to showing the locale code; icon-only text keeps the authored chips, and localeLabelMode: "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) sets aria-current="page" and data-selected. Selection paints nothing on its own: style it by authoring a selected decoration overlay (or an active text color). Variable radiogroup buttons are left unchanged. Optional iconSize / 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: 48

fontWeight

Numeric font weight (CSS font-weight).

fontWeight: 600

fontFamily

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>