Identifies this entity as a component. Machine-readable identifier (e.g., 'button', 'dialog', 'form-field', 'data-table'). MUST be unique within the documentation group.
Pattern: ^[a-z][a-z0-9-]*$
Human-readable name for display in docs (e.g., 'Button', 'Dialog', 'Form Field', 'Data Table'). Modular metadata entries. Each entry is a typed object with a `kind` discriminator. Types include status, since, tags, category, aliases, summary, thumbnail, preview, extends, and links. All structured docs for this component as an ordered array of typed document block objects. Accepts component-specific types (anatomy, api, variants, states, design-specifications) and general types (guideline, purpose, accessibility, examples). Each document block entry has a `type` property that determines its shape. Ordering is significant: tools SHOULD preserve it for display. Agent-optimized context for this component — intent, constraints, disambiguation, anti-patterns, examples, and keywords for efficient agentic consumption.
References: entityMetadata, componentDocumentBlock, agents, extensions { "kind": "component", "name": "button", "displayName": "Button", "metadata": [ { "kind": "description", "value": "An interactive element that triggers an action when activated. Buttons communicate what will happen when the user interacts with them and are the primary mechanism for initiating actions within a surface." }, { "kind": "status", "status": "stable", "platformStatus": { "react": { "status": "stable", "since": "1.0.0" }, "web-component": { "status": "experimental", "since": "3.2.0", "description": "Available as a Web Component wrapper. Native shadow DOM implementation planned for v4." }, "ios": { "status": "stable", "since": "2.1.0" }, "android": { "status": "draft", "description": "Compose implementation in progress. Expected in v4.0." }, "figma": { "status": "stable", "since": "1.0.0" } } }, { "kind": "since", "value": "1.0.0" }, { "kind": "tags", "items": [ "action", "form", "interactive" ] }, { "kind": "category", "value": "action" }, { "kind": "aliases", "items": [ "btn", "action-button", "CTA" ] }, { "kind": "summary", "value": "An interactive element that triggers an action when activated." }, { "kind": "links", "items": [ { "kind": "source", "url": "https://code.acme.com/design-system/src/packages/components/src/button/button.tsx", "label": "React component source" }, { "kind": "source", "url": "https://code.acme.com/design-system/src/packages/components/src/button/button.test.tsx", "label": "Unit tests" }, { "kind": "design", "url": "https://design-tool.acme.com/file/abc123?node-id=1234:5678", "label": "Design file — component" }, { "kind": "design", "url": "https://design-tool.acme.com/file/abc123?node-id=1234:9999", "label": "Design file — variants" }, { "kind": "storybook", "url": "https://storybook.acme.com/?path=/docs/components-button--docs", "label": "Storybook docs" }, { "kind": "package", "url": "https://www.npmjs.com/package/@acme/components", "label": "npm package" }, { "kind": "alternative", "url": "https://design.acme.com/components/link", "label": "link (component)" }, { "kind": "child", "url": "https://design.acme.com/components/icon-button", "label": "icon-button (component)" }, { "kind": "parent", "url": "https://design.acme.com/components/button-group", "label": "button-group (component)" } ] } ], "documentBlocks": [ { "kind": "anatomy", "description": "The Button is composed of a container, a text label, and an optional leading or trailing icon.", "parts": [ { "name": "container", "displayName": "Container", "description": "The outer boundary of the button. Receives background color, border, border radius, and padding. Defines the clickable area.", "required": true, "tokens": { "background": "button-background", "border-color": "button-border-color", "border-width": "button-border-width", "border-radius": "button-border-radius", "padding-horizontal": "button-padding-horizontal", "padding-vertical": "button-padding-vertical" } }, { "name": "label", "displayName": "Label", "description": "The text content of the button. Communicates the action that will occur on activation.", "required": true, "tokens": { "font-family": "button-font-family", "font-size": "button-font-size", "font-weight": "button-font-weight", "line-height": "button-line-height", "text-color": "button-text-color" } }, { "name": "icon", "displayName": "Icon", "description": "An optional icon displayed before (leading) or after (trailing) the label. Reinforces the label's meaning visually.", "required": false, "tokens": { "size": "button-icon-size", "color": "button-icon-color", "gap": "button-icon-gap" } }, { "name": "focus-ring", "displayName": "Focus Ring", "description": "A visible outline rendered when the button receives keyboard focus. Not displayed on mouse interaction.", "required": true, "tokens": { "color": "button-focus-ring-color", "width": "button-focus-ring-width", "offset": "button-focus-ring-offset" } } ], "preview": [ { "title": "Anatomy diagram", "presentation": { "kind": "image", "url": "https://design.acme.com/assets/button-anatomy.png", "alt": "An annotated diagram of a primary button with numbered callouts: 1. Container, 2. Label, 3. Icon (optional), 4. Focus ring (shown in dashed outline)." } } ], "agents": { "intent": "Describe the visual parts that compose a Button so agents can map design tokens to the correct sub-elements.", "keywords": [ "parts", "container", "label", "icon", "focus ring", "structure" ] } }, { "kind": "api", "properties": [ { "name": "variant", "type": "'primary' | 'secondary' | 'ghost' | 'danger'", "schema": { "type": "string", "enum": [ "primary", "secondary", "ghost", "danger" ], "default": "primary" }, "description": "The visual style of the button. Determines background color, text color, and border treatment.", "required": false, "defaultValue": "primary", "since": "1.0.0" }, { "name": "size", "type": "'small' | 'medium' | 'large'", "schema": { "type": "string", "enum": [ "small", "medium", "large" ], "default": "medium" }, "description": "The size of the button. Affects padding, font size, icon size, and minimum target area.", "required": false, "defaultValue": "medium", "since": "1.0.0" }, { "name": "disabled", "type": "boolean", "schema": { "type": "boolean", "default": false }, "description": "When true, the button is non-interactive. The cursor changes to not-allowed, and the button is visually dimmed to 40% opacity.", "required": false, "defaultValue": false, "since": "1.0.0" }, { "name": "loading", "type": "boolean", "schema": { "type": "boolean", "default": false }, "description": "When true, the label is replaced with a spinner and the button is non-interactive. The button retains its dimensions to prevent layout shift.", "required": false, "defaultValue": false, "since": "2.1.0" }, { "name": "fullWidth", "type": "boolean", "schema": { "type": "boolean", "default": false }, "description": "When true, the button expands to fill the width of its parent container.", "required": false, "defaultValue": false, "since": "1.2.0" }, { "name": "iconStart", "type": "IconComponent", "description": "An icon component rendered before the label. When provided without a label, an aria-label is required.", "required": false, "since": "2.0.0" }, { "name": "iconEnd", "type": "IconComponent", "description": "An icon component rendered after the label.", "required": false, "since": "2.0.0" }, { "name": "type", "type": "'button' | 'submit' | 'reset'", "schema": { "type": "string", "enum": [ "button", "submit", "reset" ], "default": "button" }, "description": "The HTML button type attribute. Controls form submission behavior.", "required": false, "defaultValue": "button", "since": "1.0.0" } ], "events": [ { "name": "onClick", "description": "Fires when the button is activated via mouse click, touch tap, Enter key, or Space key. Does not fire when the button is disabled or loading.", "payload": "(event: MouseEvent) => void", "since": "1.0.0" }, { "name": "onFocus", "description": "Fires when the button receives focus.", "payload": "(event: FocusEvent) => void", "since": "1.0.0" }, { "name": "onBlur", "description": "Fires when the button loses focus.", "payload": "(event: FocusEvent) => void", "since": "1.0.0" } ], "slots": [ { "name": "default", "description": "The button's text label.", "acceptedContent": "Plain text or a text node. Do not nest interactive elements, headings, or block-level elements." } ], "cssCustomProperties": [ { "name": "--button-background", "description": "The background color of the button container.", "type": "color", "since": "1.0.0", "defaultValue": "var(--color-action-primary)" }, { "name": "--button-text-color", "description": "The color of the label text.", "type": "color", "since": "1.0.0", "defaultValue": "var(--color-text-on-action)" }, { "name": "--button-border-radius", "description": "The border radius of the button container.", "type": "dimension", "since": "1.0.0", "defaultValue": "var(--radius-medium)" } ], "dataAttributes": [ { "name": "data-state", "description": "Reflects the current interactive state of the button. Useful for styling with attribute selectors.", "values": [ "default", "hover", "active", "focus", "disabled", "loading" ] }, { "name": "data-variant", "description": "Reflects the current variant. Useful for parent-level conditional styling.", "values": [ "primary", "secondary", "ghost", "danger" ] } ], "agents": { "intent": "Define the code-level interface of the Button component — properties, events, slots, and CSS hooks.", "constraints": [ { "rule": "Always set the type attribute explicitly; never rely on the browser default.", "level": "should" }, { "rule": "When iconStart is used without a label, an aria-label must be provided.", "level": "must" } ], "keywords": [ "props", "events", "slots", "CSS custom properties", "interface", "API" ] } }, { "kind": "events", "items": [ { "name": "onClick", "description": "Fires when the button is activated via mouse click, touch tap, Enter key, or Space key. Does not fire when the button is disabled or loading.", "payload": "(event: MouseEvent) => void", "bubbles": true, "cancelable": false, "since": "1.0.0" }, { "name": "onFocus", "description": "Fires when the button receives focus via keyboard tab, programmatic focus, or mouse click. Use to show contextual help or tooltips.", "payload": "(event: FocusEvent) => void", "bubbles": false, "cancelable": false, "since": "1.0.0" }, { "name": "onBlur", "description": "Fires when the button loses focus. Use to dismiss contextual help or validate inline state.", "payload": "(event: FocusEvent) => void", "bubbles": false, "cancelable": false, "since": "1.0.0" } ], "agents": { "intent": "Document the events emitted by the Button so agents can wire up correct event handlers.", "constraints": [ { "rule": "onClick does not fire when disabled or loading — do not attach workaround handlers.", "level": "must-not" } ], "keywords": [ "events", "onClick", "onFocus", "onBlur", "handlers", "callbacks" ] } }, { "kind": "variants", "items": [ { "kind": "enum", "name": "emphasis", "displayName": "Emphasis", "description": "Controls the visual weight of the button. Determines background fill, border treatment, and text color to establish a visual hierarchy among actions on a surface.", "values": [ { "name": "primary", "displayName": "Primary", "description": "High-emphasis — the main action on the surface. Uses a solid, filled background. Limit to one primary button per surface.", "purpose": { "kind": "purpose", "useCases": [ { "description": "When the action is the most important on the surface — the one the user is most likely to take (e.g., Save, Submit, Confirm).", "kind": "positive" }, { "description": "When a surface already has a primary button. Adding a second dilutes visual hierarchy.", "kind": "negative", "alternative": { "name": "secondary", "rationale": "Secondary emphasis maintains importance without competing with the existing primary action." } } ] } }, { "name": "secondary", "displayName": "Secondary", "description": "Medium-emphasis — important but not the primary action. Uses a visible border and transparent background.", "purpose": { "kind": "purpose", "useCases": [ { "description": "When the action is important but secondary to a primary action on the same surface (e.g., Cancel alongside Save).", "kind": "positive" } ] } }, { "name": "ghost", "displayName": "Ghost", "description": "Low-emphasis — tertiary actions, toolbar actions, or dense layouts. No background or border in the default state.", "purpose": { "kind": "purpose", "useCases": [ { "description": "When the action is tertiary or supplementary — helpful but not essential to the user's primary task.", "kind": "positive" }, { "description": "When the action is the only action on the surface and needs to be clearly discoverable.", "kind": "negative", "alternative": { "name": "secondary", "rationale": "A ghost button on its own can be overlooked. Secondary emphasis provides enough visual presence to be discoverable." } } ] } }, { "name": "danger", "displayName": "Danger", "description": "High-emphasis destructive — signals an irreversible action. Uses the danger color. Pair with a confirmation dialog.", "purpose": { "kind": "purpose", "useCases": [ { "description": "When the action is destructive or irreversible — deleting a record, revoking access, removing a team member.", "kind": "positive" }, { "description": "When the action is not destructive, even if it feels important or urgent.", "kind": "negative", "alternative": { "name": "primary", "rationale": "The danger color is a strong signal reserved for destruction. Using it for non-destructive actions dilutes its meaning." } } ] } } ] }, { "kind": "enum", "name": "size", "displayName": "Size", "description": "Controls the physical dimensions of the button — padding, font size, icon size, and minimum touch target area.", "values": [ { "name": "sm", "displayName": "Small", "description": "Compact size for toolbars and dense layouts. 32px height." }, { "name": "md", "displayName": "Medium", "description": "Default size for most contexts. 40px height." }, { "name": "lg", "displayName": "Large", "description": "Touch-optimized size for mobile-first surfaces. 48px height." } ] } ], "agents": { "intent": "List the visual and behavioral dimensions along which the Button varies — emphasis and size.", "constraints": [ { "rule": "Limit each surface to one primary variant.", "level": "must" }, { "rule": "Use danger variant only for destructive or irreversible actions.", "level": "must" } ], "disambiguation": [ { "entity": "states", "distinction": "Variants are static design dimensions chosen at author time; states are dynamic conditions that change at runtime in response to user interaction." } ], "keywords": [ "emphasis", "primary", "secondary", "ghost", "danger", "size", "small", "medium", "large" ] } }, { "kind": "states", "items": [ { "name": "default", "displayName": "Default", "description": "The button's resting state when no interaction is occurring." }, { "name": "hover", "displayName": "Hover", "description": "Triggered when the user's pointer moves over the button. The background darkens by 8% to indicate interactivity. Not applicable on touch devices.", "tokens": { "button-background": "color-action-primary-hover" } }, { "name": "active", "displayName": "Active / Pressed", "description": "Triggered while the button is being pressed (mousedown or touch start). The background darkens by 16% from the default to indicate activation.", "tokens": { "button-background": "color-action-primary-active" } }, { "name": "focus", "displayName": "Focus", "description": "Triggered when the button receives keyboard focus. A 2px focus ring appears with a 2px offset from the container edge.", "tokens": { "button-focus-ring-color": "color-focus-ring", "button-focus-ring-width": "border-width-focus", "button-focus-ring-offset": "space-focus-offset" } }, { "name": "disabled", "displayName": "Disabled", "description": "The button is non-interactive. Opacity is reduced to 0.4. Pointer events are disabled. The button remains in the tab order when using aria-disabled instead of the HTML disabled attribute." }, { "name": "loading", "displayName": "Loading", "description": "The button label is replaced by a spinner animation. The button is non-interactive. The button maintains its dimensions from the default state to prevent layout shift." } ], "agents": { "intent": "Document the interactive states a Button can enter at runtime and the token overrides each state applies.", "disambiguation": [ { "entity": "variants", "distinction": "States are runtime conditions triggered by user interaction (hover, focus, disabled); variants are static design choices made at author time." } ], "keywords": [ "hover", "active", "focus", "disabled", "loading", "pressed", "interactive" ] } }, { "kind": "design-specifications", "properties": { "background": "button-bg", "text-color": "button-text", "border-color": "button-border", "border-width": "1px", "border-radius": "button-radius", "padding-horizontal": "space-4", "padding-vertical": "space-2", "font-family": "font-family-body", "font-size": "14px", "font-weight": "500", "line-height": "20px", "icon-size": "16px", "icon-color": "inherit", "icon-gap": "8px", "focus-ring-color": "color-focus", "focus-ring-width": "2px", "focus-ring-offset": "2px", "min-height": "40px", "min-width": "64px", "opacity": "1" }, "spacing": { "internal": { "container-horizontal": "space-4", "container-vertical": "space-2", "icon-to-label": "space-2" }, "external": { "button-to-button": "space-3", "button-group-gap": "space-3" } }, "sizing": { "minWidth": "64px", "minHeight": "40px" }, "typography": { "label": { "fontSize": "14px", "fontWeight": "500", "lineHeight": "20px", "typeToken": "$body-compact-01" } }, "responsive": [ { "breakpoint": "small", "description": "In narrow containers (below 320px), buttons expand to full width automatically to maintain a usable tap target." }, { "breakpoint": "medium", "description": "Buttons display at their intrinsic width. Button groups display inline." } ], "variants": [ { "name": "primary", "description": "High-emphasis filled button. Solid background with light text.", "properties": { "background": "button-primary-bg", "text-color": "button-primary-text", "border-color": "transparent", "border-width": "0px", "icon-color": "button-primary-text" } }, { "name": "secondary", "description": "Medium-emphasis outlined button. Transparent background with visible border.", "properties": { "background": "transparent", "text-color": "button-secondary-text", "border-color": "button-secondary-border", "border-width": "1px", "icon-color": "button-secondary-text" } }, { "name": "ghost", "description": "Low-emphasis button with no background or border.", "properties": { "background": "transparent", "text-color": "button-ghost-text", "border-color": "transparent", "border-width": "0px", "icon-color": "button-ghost-text" } }, { "name": "danger", "description": "High-emphasis destructive button.", "properties": { "background": "button-danger-bg", "text-color": "button-danger-text", "border-color": "transparent", "border-width": "0px", "icon-color": "button-danger-text" } } ], "sizes": [ { "name": "small", "description": "Compact size — 32px height.", "properties": { "min-height": "32px", "font-size": "12px", "line-height": "16px", "icon-size": "14px", "icon-gap": "4px", "padding-horizontal": "space-3", "padding-vertical": "space-1" }, "spacing": { "internal": { "container-horizontal": "space-3", "container-vertical": "space-1", "icon-to-label": "space-1" } }, "sizing": { "minWidth": "44px", "minHeight": "32px" }, "typography": { "label": { "fontSize": "12px", "fontWeight": "500", "lineHeight": "16px", "typeToken": "$label-01" } } }, { "name": "large", "description": "Touch-optimized — 48px height.", "properties": { "min-height": "48px", "font-size": "16px", "line-height": "24px", "icon-size": "20px", "icon-gap": "space-3", "padding-horizontal": "space-5", "padding-vertical": "space-3" }, "spacing": { "internal": { "container-horizontal": "space-5", "container-vertical": "space-3", "icon-to-label": "space-3" } }, "sizing": { "minWidth": "80px", "minHeight": "48px" }, "typography": { "label": { "fontSize": "16px", "fontWeight": "500", "lineHeight": "24px", "typeToken": "$body-compact-02" } } } ], "states": [ { "name": "hover", "description": "Pointer over the button. Background shifts to indicate interactivity.", "properties": { "background": "button-hover-bg" } }, { "name": "active", "description": "Button is being pressed.", "properties": { "background": "button-active-bg" } }, { "name": "focus", "description": "Keyboard focus. A 2px focus ring appears with a 2px offset.", "properties": { "focus-ring-color": "color-focus", "focus-ring-width": "2px", "focus-ring-offset": "2px" } }, { "name": "disabled", "description": "Non-interactive. Opacity reduced, pointer-events disabled. Uses aria-disabled.", "properties": { "opacity": "0.4" } }, { "name": "loading", "description": "Label replaced by spinner. Non-interactive but retains dimensions. Spinner respects prefers-reduced-motion." } ], "variantStates": [ { "variant": "primary", "state": "hover", "properties": { "background": "button-primary-hover-bg" } }, { "variant": "primary", "state": "active", "properties": { "background": "button-primary-active-bg" } }, { "variant": "danger", "state": "hover", "properties": { "background": "button-danger-hover-bg" } }, { "variant": "danger", "state": "active", "properties": { "background": "button-danger-active-bg" } } ], "agents": { "intent": "Provide concrete token values, spacing, sizing, and typography specs for implementing the Button across all variants and sizes.", "keywords": [ "tokens", "spacing", "sizing", "typography", "responsive", "measurements", "implementation" ] } }, { "kind": "purpose", "useCases": [ { "description": "When the user needs to trigger an action such as submitting a form, saving data, opening a dialog, or confirming a decision.", "kind": "positive" }, { "description": "When a destructive or irreversible action needs to be initiated, such as deleting a record or revoking access. Pair with a confirmation dialog.", "kind": "positive" }, { "description": "When the action navigates the user to a different page or URL.", "kind": "negative", "alternative": { "name": "link", "rationale": "Links carry native navigation semantics. Screen readers announce them as links, and browsers support standard navigation behaviors such as open-in-new-tab." } }, { "description": "When the user needs to select one option from a set of mutually exclusive choices.", "kind": "negative", "alternative": { "name": "radio-group", "rationale": "Radio groups communicate exclusivity through their semantic role. A set of buttons styled to look like a selector does not convey mutual exclusivity to assistive technology." } }, { "description": "When the only content is an icon with no visible text label.", "kind": "negative", "alternative": { "name": "icon-button", "rationale": "Icon buttons enforce an aria-label requirement and apply size adjustments for icon-only touch targets. A standard button with its label removed may fail accessibility requirements silently." } } ], "agents": { "intent": "Clarify when to use Button versus similar interactive elements.", "disambiguation": [ { "entity": "link", "distinction": "Button triggers an in-page action; Link navigates to a URL." }, { "entity": "icon-button", "distinction": "Button always has a visible text label; IconButton is icon-only and requires aria-label." } ], "keywords": [ "when to use", "use case", "action", "submit", "navigation" ] } }, { "kind": "guideline", "items": [ { "guidance": "Limit each surface to one primary button.", "rationale": "Multiple primary buttons dilute visual hierarchy. When everything is emphasized, nothing is. A single primary button directs the user to the most important action.", "kind": "required", "category": "visual-design" }, { "guidance": "Place the primary button on the right side of a button group in left-to-right layouts.", "rationale": "Users scan in the direction of the layout's reading order. Placing the primary action at the natural endpoint aligns with the completion point of reading.", "kind": "encouraged", "category": "visual-design" }, { "guidance": "Do not use a Button when the action navigates the user to a different page or URL. Use a Link component instead.", "rationale": "Buttons and links have different semantic roles. Buttons trigger actions (submit, open, close). Links navigate. Screen reader users rely on element role to anticipate behavior.", "kind": "prohibited", "category": "visual-design" }, { "guidance": "Use the danger variant exclusively for destructive or irreversible actions. Pair danger buttons with a confirmation dialog.", "rationale": "Red is a strong signal. If danger styling is used for non-destructive actions, it dilutes the warning signal and conditions users to ignore it.", "kind": "required", "category": "visual-design" }, { "guidance": "Use the loading state instead of disabling the button during asynchronous operations.", "rationale": "A disabled button gives no feedback that an action is in progress. The loading state communicates that the action was registered and the system is working.", "kind": "encouraged", "category": "interaction" }, { "guidance": "Do not wrap a button's label text across multiple lines.", "rationale": "Multi-line button labels are harder to scan and create inconsistent button heights in groups. If the label is too long, rewrite it to be shorter.", "kind": "prohibited", "category": "visual-design" }, { "guidance": "Maintain a minimum tap target of 44x44 CSS pixels for all button sizes.", "rationale": "The WCAG 2.5.8 target size criterion requires a minimum 24x24px target, with 44x44px recommended. Touch devices require larger targets to prevent mis-taps.", "kind": "required", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#target-size-minimum" } ] }, { "guidance": "Use a verb or verb phrase that describes the action the button performs. Two words maximum.", "rationale": "Action-oriented labels set clear expectations about what will happen on activation. Short labels prevent truncation on narrow viewports.", "kind": "required", "category": "content", "target": "label" }, { "guidance": "Use sentence case capitalization.", "rationale": "Sentence case is easier to read than title case or all caps. It also localizes more predictably across languages where capitalization rules differ.", "kind": "required", "category": "content", "target": "label" }, { "guidance": "When using an icon-only button (no visible label), provide an aria-label that describes the action.", "rationale": "Screen readers announce button content as the accessible name. Without visible text, there is no accessible name. The aria-label provides one.", "kind": "required", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#name-role-value" } ] }, { "guidance": "Use the native <button> element. Do not recreate button behavior on a <div> or <span>.", "rationale": "Native buttons provide built-in keyboard interaction (Enter, Space), focus management, and form submission behavior. Recreating this on a non-semantic element is error-prone.", "kind": "required", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#name-role-value" } ] }, { "guidance": "Prefer aria-disabled=\"true\" over the HTML disabled attribute when the button should remain discoverable by screen reader users.", "rationale": "The HTML disabled attribute removes the button from the tab order, making it invisible to keyboard users. aria-disabled keeps the button focusable and announceable.", "kind": "encouraged", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#keyboard" } ] }, { "guidance": "The focus ring must be visible in all color modes (light, dark, high contrast).", "rationale": "Keyboard users depend on the focus indicator to track their position. If the focus ring is invisible against the background, navigation becomes impossible.", "kind": "required", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#focus-visible" } ] }, { "guidance": "Button label text must meet a minimum 4.5:1 contrast ratio against the button background.", "rationale": "Text contrast ensures readability for users with low vision.", "kind": "required", "category": "accessibility", "criteria": [ { "url": "https://www.w3.org/TR/WCAG22/#contrast-minimum" } ] } ], "agents": { "intent": "Enforce correct Button usage rules in generated UI code and design decisions.", "constraints": [ { "rule": "Never place two primary buttons on the same surface.", "level": "must-not" }, { "rule": "Always pair a danger button with a confirmation dialog.", "level": "must" } ], "keywords": [ "rules", "best practices", "visual design", "accessibility", "content" ] } }, { "kind": "accessibility", "wcagLevel": "AA", "keyboardInteraction": [ { "key": "Enter", "action": "Activates the button." }, { "key": "Space", "action": "Activates the button." }, { "key": "Tab", "action": "Moves focus to the next focusable element in the tab order." }, { "key": "Shift+Tab", "action": "Moves focus to the previous focusable element in the tab order." } ], "ariaAttributes": [ { "attribute": "role", "value": "button", "description": "Applied automatically by the <button> element. Only set explicitly when using the 'as' prop to render a non-button element.", "required": false }, { "attribute": "aria-disabled", "value": "true | false", "description": "Set to 'true' when the button is non-interactive. Preferred over the HTML disabled attribute when the button should remain focusable for screen reader discoverability.", "required": false }, { "attribute": "aria-label", "value": "string", "description": "Provides an accessible name for icon-only buttons that lack visible text. Not needed when a visible label is present.", "required": false }, { "attribute": "aria-busy", "value": "true | false", "description": "Set to 'true' when the button is in the loading state. Communicates to assistive technology that the button's action is in progress.", "required": false } ], "screenReaderBehavior": "Announced as '[label], button'. When disabled via aria-disabled, announced as '[label], button, dimmed' (VoiceOver) or '[label], button, unavailable' (NVDA/JAWS). When loading, announced as '[label], button, busy'.", "focusManagement": "The button participates in the normal tab order. It does not trap or redirect focus. When the button triggers a modal or popover, focus is moved to the opened element — this is the responsibility of the modal/popover component, not the button.", "colorContrast": [ { "foreground": "color-text-on-action", "background": "color-action-primary", "contrastRatio": 7.2, "level": "AAA", "context": "Label text on primary button background in light mode." }, { "foreground": "color-action-primary", "background": "color-background-default", "contrastRatio": 4.8, "level": "AA", "context": "Secondary button border/text against the default page background in light mode." }, { "foreground": "color-text-on-action", "background": "color-action-danger", "contrastRatio": 6.5, "level": "AAA", "context": "Label text on danger button background in light mode." } ], "motionConsiderations": "The loading spinner animation respects the prefers-reduced-motion media query. When reduced motion is preferred, the spinner is replaced with a static ellipsis indicator.", "agents": { "intent": "Provide keyboard interaction patterns, ARIA requirements, and contrast ratios for implementing an accessible Button.", "constraints": [ { "rule": "Use aria-disabled instead of the HTML disabled attribute when the button must remain in the tab order.", "level": "should" }, { "rule": "Focus ring must be visible with a minimum 3:1 contrast ratio against the surrounding background.", "level": "must" } ], "keywords": [ "WCAG", "keyboard", "ARIA", "screen reader", "focus", "contrast", "a11y" ] } } ], "agents": { "intent": "Trigger a user-initiated action within the current view without causing page navigation.", "constraints": [ { "rule": "Do not use for navigating to a different page or URL.", "level": "must-not" }, { "rule": "Limit each surface to one primary-emphasis button.", "level": "must" }, { "rule": "Always provide an accessible label via visible text or aria-label.", "level": "must" } ], "disambiguation": [ { "entity": "link", "distinction": "Use button for in-page actions; use link for navigation to a URL." }, { "entity": "icon-button", "distinction": "Use button when a visible text label is present; use icon-button for icon-only affordances." } ], "antiPatterns": [ { "description": "Using a button to navigate to another page.", "instead": "Use a link element with href." }, { "description": "Placing multiple primary buttons on the same surface.", "instead": "Use one primary and one or more secondary or tertiary buttons." } ], "keywords": [ "action", "submit", "click", "CTA", "trigger", "call-to-action", "interactive" ] }, "$extensions": { "com.designTool": { "componentId": "abc123def456" } } }

Design System Documentation Standard (DSDS) 0.1 — Draft Specification

GitHub