- Initiate an action that changes the current state.
- Show one primary action per view.
- Use danger tone when the action is destructive.
Button
Make the next action clear with one primary button and quieter supporting actions.
Implementation previewSee the decisions in action
Change the hierarchy, size, and state. The preview and its code use the same component.
Every example. In one place.
Explore every named example, including states, compositions, and theme variants.
Example source
packages/design-system/src/components/primitives/Button/Button.stories.tsxExport PlaygroundImplementation preview Local Storybook build 0.2.0-preview.1. Source specification status does not approve this runtime.
Stories + source JSON ↗Choose the right emphasis
Give the most important action a clear place in the hierarchy.
- Using a button to navigate to another URL; use a link.
- Treating the link variant as an anchor.
- Disabling an action without explaining what is needed.
Primary
Filled treatment for the highest-priority action in a group.
Secondary
Outlined text treatment; icon-only secondary buttons use the filled secondary container.
Tertiary
Chrome-free text treatment that retains the full text-button tap target.
Link
Inline, lowest-emphasis action without button padding.
Small details carry the experience
Anatomy, feedback, and accessibility are part of the component contract.
ContainerOwns the tap target, action hierarchy treatment, size, and state.
LabelNames the action for text buttons.
Leading visualOptional visual placed before a text label.
Loading indicatorReplaces visible content while preserving the button geometry during loading.
Make the behavior available to everyone.
- Use a descriptive visible label, or ariaLabel for icon-only buttons.
- Loading preserves the label, sets aria-busy, and blocks duplicate presses.
- Keyboard focus is visible in both themes.
Keep the important relationships.
- Text buttons use children and optional leading/trailing content.
- Icon-only buttons require icon and ariaLabel and cannot have children or leading/trailing.
- Always renders type=button. A form must explicitly request submission.
This interactive implementation is for the web. Imported native specifications do not establish native implementation or parity.
Full component specification preview · 1.0.0
This versioned design-owned specification describes the implementation preview. It remains pending design and engineering sign-off.
{
"$schema": "../../../specs/component-spec.schema.json",
"identity": {
"name": "Button",
"status": "preview",
"version": "1.0.0",
"summary": "A pressable action primitive with text, optional leading or trailing content, or an icon-only presentation."
},
"parts": [
{
"id": "container",
"label": "Container",
"description": "Owns the tap target, action hierarchy treatment, size, and state."
},
{
"id": "label",
"label": "Label",
"description": "Names the action for text buttons."
},
{
"id": "leading",
"label": "Leading visual",
"description": "Optional visual placed before a text label."
},
{
"id": "trailing",
"label": "Trailing visual",
"description": "Optional visual placed after a text label."
},
{
"id": "icon",
"label": "Icon",
"description": "The sole visible part of an icon-only button."
},
{
"id": "loading-indicator",
"label": "Loading indicator",
"description": "Replaces visible content while preserving the button geometry during loading."
}
],
"allowedDesignChoices": {
"variants": {
"primary": {
"label": "Primary"
},
"secondary": {
"label": "Secondary"
},
"tertiary": {
"label": "Tertiary",
"description": "A deliberate web-only extension."
},
"link": {
"label": "Link"
}
},
"sizes": {
"sm": {
"label": "Small"
},
"md": {
"label": "Medium"
}
}
},
"variants": [
{
"id": "primary",
"label": "Primary",
"description": "Filled treatment for the highest-priority action in a group.",
"platforms": [
"ios",
"android",
"web"
]
},
{
"id": "secondary",
"label": "Secondary",
"description": "Outlined text treatment; icon-only secondary buttons use the filled secondary container.",
"platforms": [
"ios",
"android",
"web"
]
},
{
"id": "tertiary",
"label": "Tertiary",
"description": "Chrome-free text treatment that retains the full text-button tap target.",
"platforms": [
"web"
]
},
{
"id": "link",
"label": "Link",
"description": "Inline, lowest-emphasis action without button padding.",
"platforms": [
"ios",
"android",
"web"
]
}
],
"sizes": [
{
"id": "sm",
"label": "Small",
"description": "Compact text or icon action for dense layouts."
},
{
"id": "md",
"label": "Medium",
"description": "Default standalone text or icon action."
}
],
"measurements": [
{
"id": "text-md-height",
"label": "Medium text button height",
"kind": "exact",
"token": "--cds-size-56",
"value": 56,
"unit": "px",
"appliesTo": [
"text",
"md"
]
},
{
"id": "text-md-horizontal-padding",
"label": "Medium text button horizontal padding",
"kind": "exact",
"token": "--cds-space-32",
"value": 32,
"unit": "px",
"appliesTo": [
"text",
"md"
]
},
{
"id": "text-md-vertical-spacing",
"label": "Medium text button vertical spacing",
"kind": "automatic",
"appliesTo": [
"text",
"md"
],
"notes": "The exact 56px height centers the label and owns the vertical inset."
},
{
"id": "text-sm-vertical-padding",
"label": "Small text button vertical padding",
"kind": "exact",
"token": "--cds-space-8",
"value": 8,
"unit": "px",
"appliesTo": [
"text",
"sm"
]
},
{
"id": "text-sm-horizontal-padding",
"label": "Small text button horizontal padding",
"kind": "exact",
"token": "--cds-space-20",
"value": 20,
"unit": "px",
"appliesTo": [
"text",
"sm"
]
},
{
"id": "content-gap",
"label": "Content gap",
"kind": "exact",
"token": "--cds-space-4",
"value": 4,
"unit": "px",
"appliesTo": [
"text",
"leading",
"trailing"
],
"notes": "Reconciles the design gap of 4px with the prior web implementation gap of 8px."
},
{
"id": "icon-md-size",
"label": "Medium icon-only button size",
"kind": "exact",
"token": "--cds-size-40",
"value": 40,
"unit": "px",
"appliesTo": [
"icon",
"md"
]
},
{
"id": "icon-sm-size",
"label": "Small icon-only button size",
"kind": "exact",
"token": "--cds-size-24",
"value": 24,
"unit": "px",
"appliesTo": [
"icon",
"sm"
]
},
{
"id": "large-pill-radius",
"label": "Large primary and secondary radius",
"kind": "exact",
"token": "--cds-radius-full",
"value": "full",
"unit": "none",
"appliesTo": [
"text",
"md",
"primary",
"secondary"
]
}
],
"layout": {
"alignment": "Label, slots, icon, and loading indicator are centered in both axes.",
"flow": "Text buttons lay out leading visual, label, and trailing visual in one row; icon-only buttons render one icon.",
"notes": [
"Full-width text buttons stretch to their parent while preserving centered content.",
"Icon-only secondary buttons use the filled secondary container rather than the text-button outline."
]
},
"typographyAndColor": {
"typography": [
{
"label": "Medium text",
"description": "Uses the 16px body style.",
"token": "--cds-type-body-size"
},
{
"label": "Small text",
"description": "Uses the 14px body-small style.",
"token": "--cds-type-body-small-size"
},
{
"label": "Link text",
"description": "Uses the compact link style.",
"token": "--cds-type-link-size"
}
],
"colors": [
{
"label": "Primary container",
"description": "Theme-aware primary action fill.",
"token": "--cds-button-primary-container"
},
{
"label": "Primary text",
"description": "Theme-aware foreground on the primary fill.",
"token": "--cds-button-primary-text"
},
{
"label": "Secondary container",
"description": "Theme-aware fill for icon-only secondary buttons.",
"token": "--cds-button-secondary-container"
},
{
"label": "Secondary text",
"description": "Theme-aware foreground for secondary and tertiary text.",
"token": "--cds-button-secondary-text"
}
]
},
"states": [
{
"id": "active",
"label": "Active",
"description": "Available for press and uses the selected variant treatment."
},
{
"id": "disabled",
"label": "Disabled",
"description": "Blocks presses and applies --cds-opacity-disabled."
},
{
"id": "loading",
"label": "Loading",
"description": "Blocks presses, hides the visible content, and centers a Spinner without changing geometry."
}
],
"behavior": [
{
"label": "Press",
"description": "Calls onPress once for an enabled, non-loading button."
},
{
"label": "Loading",
"description": "Treats loading as disabled and exposes the busy state to assistive technology."
},
{
"label": "Haptic",
"description": "Requests the light semantic haptic after a successful press unless haptics are disabled."
}
],
"accessibility": [
{
"label": "Icon-only label",
"description": "Every icon-only button requires an accessible ariaLabel that names the action."
},
{
"label": "Native semantics",
"description": "Renders a native button element and forwards disabled and aria-busy state."
},
{
"label": "Loading name",
"description": "Keeps the original visible label in the accessibility tree while the Spinner is shown."
}
],
"contentAdaptation": [
{
"label": "Action labels",
"description": "Use short verb-led labels; full-width layouts absorb longer localized text."
},
{
"label": "Slots",
"description": "Use leading and trailing visuals only when they clarify the action; icon-only buttons do not accept text slots."
}
],
"motionHaptics": {
"motion": [
{
"label": "Press feedback",
"description": "Uses the shared button press treatment and respects reduced-motion preferences."
}
],
"haptics": [
{
"label": "Default feedback",
"description": "Uses a light haptic for successful presses when a native bridge is available."
}
]
},
"platformDifferences": [
{
"platforms": [
"web"
],
"description": "The tertiary variant is web-only; it is not part of the shared cross-platform component set.",
"rationale": "Commit e85ed8f16 added the intentional chrome-free app treatment, so the difference remains documented instead of being removed during design reconciliation."
}
]
}Carry the decisions into code
The same component contract supports a person reading the page and an agent implementing it.
Build a Button component using the Cloaked design system. Read https://design.cloaked.team/data/components/button.json first. Use the documented component API and semantic tokens. Preserve keyboard access, clear labels, and validation behavior. Treat this implementation as a review preview; report any missing requirements.