{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button-filled",
  "title": "ButtonFilled",
  "description": "Highest-emphasis command button for the single primary action in a local decision area, with semantic consequence and async progress treatments.",
  "type": "registry:ui",
  "meta": {
    "library": "@ds-mo/ui",
    "distribution": "npm",
    "storybook": "https://zainadeel.github.io/compomo/?path=/story/button-filled",
    "source": "src/wc/components/ButtonFilled/ButtonFilled.tsx",
    "intentStatus": "complete",
    "intent": {
      "audience": "general",
      "status": "stable",
      "summary": "Highest-emphasis command button for the single primary action in a local decision area, with semantic consequence and async progress treatments.",
      "useWhen": [
        "The user initiates the single primary command in a local decision area.",
        "An action's consequence requires a matching semantic treatment.",
        "A standalone compact primary action needs an icon-only presentation with an explicit accessible name."
      ],
      "avoidWhen": [
        "Another filled action already exists in the same local decision area.",
        "The action is unfilled icon-only shell or navigation chrome.",
        "The element represents navigation rather than a command.",
        "The content is a persistent selected state rather than an action.",
        "A semantic color would be decorative or merely match the surrounding product area.",
        "The control is an overflow or more-options menu whose only content is an Ellipses glyph; that role is never filled."
      ],
      "alternatives": [
        {
          "when": "The action is icon-only shell, navigation, overflow, or tool-rail chrome.",
          "component": "component:ds-button-unfilled",
          "reason": "Unfilled buttons provide the correct active, border, and surface-aware interaction states for chrome."
        },
        {
          "when": "The user is choosing a destination in application navigation.",
          "component": "component:ds-panel-nav",
          "reason": "Navigation components own destination selection, active state, and navigation semantics."
        },
        {
          "when": "The control is the overflow or more-options menu for a row, card, or toolbar.",
          "component": "component:ds-button-unfilled",
          "reason": "An icon-only Ellipses trigger is chrome, and its glyph conveys the menu without a chevron; a filled button never carries that role."
        }
      ],
      "commonlyComposedWith": [
        "component:ds-modal",
        "component:ds-field",
        "component:ds-menu"
      ],
      "patterns": [
        "pattern:menu-trigger"
      ],
      "accessibility": [
        "Provide an explicit accessible name for icon-only buttons.",
        "Set hasMenu when the action opens a Menu; it implies aria-haspopup of menu and renders the trailing chevron. Pass controls with the Menu id and expanded from the same open state given to Menu.",
        "Use haspopup directly for a non-menu popup such as a dialog; that case gets no chevron.",
        "Use inactive state only when the action must remain visible but unavailable.",
        "Do not encode the action meaning through color alone.",
        "Loading keeps the action focused and named, exposes busy and disabled state, and blocks activation; announce broader operation progress from the owning workflow when users need confirmation.",
        "Use native submit behavior for form submission and handle the form submit event rather than submitting from the button click event."
      ],
      "states": [
        "Every size applies one complete control-density recipe across frame height, inline padding, icon zone, text-container inset, gap, and radius.",
        "Set isInset only when the button is nested inside a same-size control. The default single inset removes 4px overall; insetDepth double removes 8px overall so the parent can add 4px per edge while preserving its outer alignment box. Double inset falls back to single at xs to protect minimum icon geometry.",
        "Use only one filled action per local decision area; render all secondary actions with ButtonUnfilled.",
        "Choose brand or neutral for general commands. Reserve positive, negative, warning, and caution for matching action consequences, and AI, guide, or walkthrough for actions that enter those feature experiences.",
        "Contrast serves both surface adaptation and hierarchy, but never permits a second filled action in the same decision area.",
        "The optional inset border is off by default. Enable it only when the filled action needs an explicit edge against its parent surface.",
        "Parent surface context changes only the optional inset border color. Contrast continues to own the button fill, foreground, hover, pressed, and focus treatments; never use surface context to change action hierarchy.",
        "Rounded changes only the control radius to the half-radius treatment; it does not change hierarchy, size, variant, intent, or interaction semantics.",
        "Loading prevents duplicate activation without applying inactive opacity or removing the current keyboard focus.",
        "Icon and icon-label loading replace the icon in place. Label-only loading centers the loader while preserving the button's measured width.",
        "Physical press scaling is on by default. Disable it only when an owning composite must keep child or background geometry fixed; hover and pressed interaction washes remain available.",
        "ButtonFilled has no active, pressed, or selected state; a filled action is a command, not a toggle or a persistent selection.",
        "An open popup is a transient pressed state instead. While expanded is true the pressed wash is held for the popup's complete rendered lifecycle and survives hover, so the trigger reads as held open without introducing a selected treatment.",
        "hasMenu supports only the label and icon-label variants. The chevron is what communicates that a menu will open, so an icon-only filled menu button would leave the affordance unstated."
      ],
      "responsiveBehavior": [
        "Use fill width only when the parent layout calls for a full-width action, commonly on narrow form layouts."
      ],
      "frameworkCaveats": {
        "customElements": [
          "For form submission, use the native submit type and listen to the form submit event; use dsClick for non-submit commands."
        ],
        "react": [
          "Keep async state in the owner and pass it to the loading state until the command settles."
        ],
        "angular": [
          "Keep async state in the owner and pass it to the loading state until the command settles."
        ]
      },
      "references": [
        {
          "label": "Storybook examples",
          "path": "src/wc/components/ButtonFilled/ButtonFilled.stories.ts"
        }
      ]
    },
    "api": {
      "props": {
        "ariaLabel": {
          "type": "string | null",
          "resolvedType": "null | string",
          "attribute": "aria-label",
          "default": "null",
          "required": false,
          "mutable": false,
          "description": "Accessible name override. Required for icon-only buttons."
        },
        "background": {
          "type": "ButtonFilledBackground | undefined",
          "resolvedType": "\"always-dark\" | \"bold\" | \"faint\" | \"inverted\" | \"media\" | \"medium\" | \"strong\" | \"translucent\" | undefined",
          "attribute": "background",
          "required": false,
          "mutable": false,
          "description": "Actual parent surface context for the optional inset border color only.\nOmit on primary and secondary surfaces."
        },
        "contrast": {
          "type": "ButtonFilledContrast",
          "resolvedType": "\"bold\" | \"faint\" | \"medium\" | \"strong\"",
          "attribute": "contrast",
          "default": "'bold'",
          "required": false,
          "mutable": false,
          "description": "Background fill weight. Foreground uses the paired contrast token:\nbold → faint, strong → medium, medium → strong, faint → bold."
        },
        "controls": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "controls",
          "required": false,
          "mutable": false,
          "description": "ID of the popup this button controls."
        },
        "expanded": {
          "type": "boolean | undefined",
          "resolvedType": "boolean | undefined",
          "attribute": "expanded",
          "required": false,
          "mutable": false,
          "description": "Controlled open state of the popup this button triggers. Holds the pressed\nwash for the popup's rendered lifecycle. ButtonFilled has no selected state,\nso an open popup never promotes to an active treatment."
        },
        "hasBorder": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "has-border",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Show a 1px secondary inset border."
        },
        "hasMenu": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "has-menu",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "This action *has* a menu: implies `aria-haspopup=\"menu\"` and adds the trailing\nchevron that carries the affordance.\n\nOnly `label` and `icon-label` are supported. A filled button is never the\noverflow / more-options control — that role belongs to ButtonUnfilled with an\n`Ellipses` glyph, which conveys the menu without a chevron.\n\nUse `haspopup` directly for non-menu popups."
        },
        "haspopup": {
          "type": "ButtonFilledPopup | undefined",
          "resolvedType": "\"dialog\" | \"grid\" | \"listbox\" | \"menu\" | \"tree\" | \"true\" | undefined",
          "attribute": "haspopup",
          "required": false,
          "mutable": false,
          "description": "Popup type exposed to assistive technology."
        },
        "icon": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "icon",
          "default": "''",
          "required": false,
          "mutable": false,
          "description": "Icon name passed to <ds-icon> for `icon` / `icon-label` variants."
        },
        "insetDepth": {
          "type": "ControlInsetDepth",
          "resolvedType": "\"double\" | \"single\"",
          "attribute": "inset-depth",
          "default": "'single'",
          "required": false,
          "mutable": false,
          "description": "Single removes 4px overall; double removes 8px overall (xs stays single)."
        },
        "intent": {
          "type": "ButtonFilledIntent",
          "resolvedType": "\"ai\" | \"brand\" | \"caution\" | \"guide\" | \"negative\" | \"neutral\" | \"positive\" | \"walkthrough\" | \"warning\"",
          "attribute": "intent",
          "default": "'brand'",
          "required": false,
          "mutable": false,
          "description": "Semantic colour intent."
        },
        "isInactive": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "is-inactive",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Disables interaction."
        },
        "isInset": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "is-inset",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Use reduced outer geometry when nested inside a control of the same size."
        },
        "isLoading": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "is-loading",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Shows an inline loader and prevents interaction without applying inactive opacity."
        },
        "label": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "label",
          "default": "''",
          "required": false,
          "mutable": false,
          "description": "Visible text for `label` / `icon-label` variants."
        },
        "pressScale": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "press-scale",
          "default": "true",
          "required": false,
          "mutable": false,
          "description": "Scale down during a physical pointer press.\nDisable when an owning composite requires fixed child or background geometry."
        },
        "rounded": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "rounded",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Use the half-radius treatment instead of the default control radius."
        },
        "size": {
          "type": "ButtonFilledSize",
          "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
          "attribute": "size",
          "default": "'md'",
          "required": false,
          "mutable": false,
          "description": "Control density (height, padding, icon, type)."
        },
        "type": {
          "type": "'button' | 'submit' | 'reset'",
          "resolvedType": "\"button\" | \"reset\" | \"submit\"",
          "attribute": "type",
          "default": "'button'",
          "required": false,
          "mutable": false,
          "description": "Native button type."
        },
        "variant": {
          "type": "ButtonFilledVariant",
          "resolvedType": "\"icon\" | \"icon-label\" | \"label\"",
          "attribute": "variant",
          "default": "'label'",
          "required": false,
          "mutable": false,
          "description": "Content layout. Default is label-only; pass `icon` for icon-only chrome\n(nav / tool rails) or `icon-label` for leading icon + text."
        },
        "width": {
          "type": "ButtonFilledWidth",
          "resolvedType": "\"fill\" | \"hug\"",
          "attribute": "width",
          "default": "'hug'",
          "required": false,
          "mutable": false,
          "description": "Width fit — hug content (default) or fill the parent."
        }
      },
      "events": [
        {
          "name": "dsClick",
          "detail": "MouseEvent",
          "bubbles": true,
          "cancelable": true,
          "composed": true
        }
      ],
      "methods": [
        {
          "name": "setFocus",
          "signature": "setFocus() => Promise<void>"
        }
      ],
      "slots": []
    },
    "props": {
      "ariaLabel": {
        "type": "string | null",
        "resolvedType": "null | string",
        "attribute": "aria-label",
        "default": "null",
        "required": false,
        "mutable": false,
        "description": "Accessible name override. Required for icon-only buttons."
      },
      "background": {
        "type": "ButtonFilledBackground | undefined",
        "resolvedType": "\"always-dark\" | \"bold\" | \"faint\" | \"inverted\" | \"media\" | \"medium\" | \"strong\" | \"translucent\" | undefined",
        "attribute": "background",
        "required": false,
        "mutable": false,
        "description": "Actual parent surface context for the optional inset border color only.\nOmit on primary and secondary surfaces."
      },
      "contrast": {
        "type": "ButtonFilledContrast",
        "resolvedType": "\"bold\" | \"faint\" | \"medium\" | \"strong\"",
        "attribute": "contrast",
        "default": "'bold'",
        "required": false,
        "mutable": false,
        "description": "Background fill weight. Foreground uses the paired contrast token:\nbold → faint, strong → medium, medium → strong, faint → bold."
      },
      "controls": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "controls",
        "required": false,
        "mutable": false,
        "description": "ID of the popup this button controls."
      },
      "expanded": {
        "type": "boolean | undefined",
        "resolvedType": "boolean | undefined",
        "attribute": "expanded",
        "required": false,
        "mutable": false,
        "description": "Controlled open state of the popup this button triggers. Holds the pressed\nwash for the popup's rendered lifecycle. ButtonFilled has no selected state,\nso an open popup never promotes to an active treatment."
      },
      "hasBorder": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "has-border",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Show a 1px secondary inset border."
      },
      "hasMenu": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "has-menu",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "This action *has* a menu: implies `aria-haspopup=\"menu\"` and adds the trailing\nchevron that carries the affordance.\n\nOnly `label` and `icon-label` are supported. A filled button is never the\noverflow / more-options control — that role belongs to ButtonUnfilled with an\n`Ellipses` glyph, which conveys the menu without a chevron.\n\nUse `haspopup` directly for non-menu popups."
      },
      "haspopup": {
        "type": "ButtonFilledPopup | undefined",
        "resolvedType": "\"dialog\" | \"grid\" | \"listbox\" | \"menu\" | \"tree\" | \"true\" | undefined",
        "attribute": "haspopup",
        "required": false,
        "mutable": false,
        "description": "Popup type exposed to assistive technology."
      },
      "icon": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "icon",
        "default": "''",
        "required": false,
        "mutable": false,
        "description": "Icon name passed to <ds-icon> for `icon` / `icon-label` variants."
      },
      "insetDepth": {
        "type": "ControlInsetDepth",
        "resolvedType": "\"double\" | \"single\"",
        "attribute": "inset-depth",
        "default": "'single'",
        "required": false,
        "mutable": false,
        "description": "Single removes 4px overall; double removes 8px overall (xs stays single)."
      },
      "intent": {
        "type": "ButtonFilledIntent",
        "resolvedType": "\"ai\" | \"brand\" | \"caution\" | \"guide\" | \"negative\" | \"neutral\" | \"positive\" | \"walkthrough\" | \"warning\"",
        "attribute": "intent",
        "default": "'brand'",
        "required": false,
        "mutable": false,
        "description": "Semantic colour intent."
      },
      "isInactive": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "is-inactive",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Disables interaction."
      },
      "isInset": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "is-inset",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Use reduced outer geometry when nested inside a control of the same size."
      },
      "isLoading": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "is-loading",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Shows an inline loader and prevents interaction without applying inactive opacity."
      },
      "label": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "label",
        "default": "''",
        "required": false,
        "mutable": false,
        "description": "Visible text for `label` / `icon-label` variants."
      },
      "pressScale": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "press-scale",
        "default": "true",
        "required": false,
        "mutable": false,
        "description": "Scale down during a physical pointer press.\nDisable when an owning composite requires fixed child or background geometry."
      },
      "rounded": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "rounded",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Use the half-radius treatment instead of the default control radius."
      },
      "size": {
        "type": "ButtonFilledSize",
        "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
        "attribute": "size",
        "default": "'md'",
        "required": false,
        "mutable": false,
        "description": "Control density (height, padding, icon, type)."
      },
      "type": {
        "type": "'button' | 'submit' | 'reset'",
        "resolvedType": "\"button\" | \"reset\" | \"submit\"",
        "attribute": "type",
        "default": "'button'",
        "required": false,
        "mutable": false,
        "description": "Native button type."
      },
      "variant": {
        "type": "ButtonFilledVariant",
        "resolvedType": "\"icon\" | \"icon-label\" | \"label\"",
        "attribute": "variant",
        "default": "'label'",
        "required": false,
        "mutable": false,
        "description": "Content layout. Default is label-only; pass `icon` for icon-only chrome\n(nav / tool rails) or `icon-label` for leading icon + text."
      },
      "width": {
        "type": "ButtonFilledWidth",
        "resolvedType": "\"fill\" | \"hug\"",
        "attribute": "width",
        "default": "'hug'",
        "required": false,
        "mutable": false,
        "description": "Width fit — hug content (default) or fill the parent."
      }
    },
    "events": [
      {
        "name": "dsClick",
        "detail": "MouseEvent",
        "bubbles": true,
        "cancelable": true,
        "composed": true
      }
    ],
    "methods": [
      {
        "name": "setFocus",
        "signature": "setFocus() => Promise<void>"
      }
    ],
    "slots": [],
    "exports": {
      "customElement": "ds-button-filled",
      "react": "DsButtonFilled",
      "vue": "DsButtonFilled",
      "angular": "DsButtonFilled"
    },
    "consumption": {
      "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
      "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
      "customElements": {
        "import": "import '@ds-mo/ui/dist/components/ds-button-filled.js';",
        "example": "<ds-button-filled></ds-button-filled>"
      },
      "react": {
        "import": "import { DsButtonFilled } from '@ds-mo/ui/react';",
        "example": "<DsButtonFilled />"
      },
      "vue": {
        "import": "import { DsButtonFilled } from '@ds-mo/ui/vue';",
        "example": "<DsButtonFilled />"
      },
      "angular": {
        "import": "import { DsButtonFilled } from '@ds-mo/ui/angular/ds-button-filled';",
        "example": "<ds-button-filled></ds-button-filled>"
      },
      "peerDependencies": {
        "required": [
          "@ds-mo/tokens ^6.5.0",
          "@ds-mo/icons ^7.0.0"
        ],
        "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
      }
    }
  },
  "dependencies": [
    "@ds-mo/ui",
    "@ds-mo/tokens",
    "@ds-mo/icons"
  ],
  "registryDependencies": [
    "badge",
    "icon",
    "loader",
    "text"
  ],
  "files": [
    {
      "path": "src/wc/components/ButtonFilled/ButtonFilled.css",
      "content": "@import '../../utils/button-base.css';\n\n:host {\n  --ds-button-filled-bg: var(--color-background-bold-brand);\n  --ds-button-filled-fg: var(--color-foreground-faint-brand);\n  --ds-button-filled-border: var(--color-border-secondary);\n}\n\n.button-filled {\n  background-color: var(--ds-button-filled-bg);\n  color: var(--ds-button-filled-fg);\n  transition:\n    background-color var(--effect-motion-short-2),\n    color var(--effect-motion-short-2);\n  /* Hover/press: `.ds-interaction-fill` + contrast → `--on-*` (token overlay, not color-mix). */\n}\n\n/*\n * An open popup is a transient pressed state, not a selected state. ButtonFilled\n * deliberately has no active state, so hold the pressed wash for the popup's\n * complete rendered lifecycle instead of promoting any selected treatment.\n */\n/* Inset stroke via interaction-fill ::after (above hover and pressed washes). */\n.button-filled--bordered {\n  --ds-interaction-border-color: var(--ds-button-filled-border);\n}\n\n/*\n * Parent-surface context affects only the optional inset border.\n * Intent + contrast continue to own fill, foreground, and interaction overlays.\n */\n.button-filled--background-medium {\n  --ds-button-filled-border: var(--color-border-on-medium-background-secondary);\n}\n\n.button-filled--background-bold {\n  --ds-button-filled-border: var(--color-border-on-bold-background-secondary);\n}\n\n.button-filled--background-strong {\n  --ds-button-filled-border: var(--color-border-on-strong-background-secondary);\n}\n\n.button-filled--background-translucent {\n  --ds-button-filled-border: var(--color-translucent-border-secondary);\n}\n\n.button-filled--background-inverted {\n  --ds-button-filled-border: var(--color-inverted-border-secondary);\n}\n\n.button-filled--background-media {\n  --ds-button-filled-border: var(--color-media-border-secondary);\n}\n\n.button-filled--background-always-dark {\n  --ds-button-filled-border: var(--color-always-dark-border-secondary);\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n/* ── Intent + contrast (bold default) ─────────────────────────────────────── */\n\n.button-filled--intent-neutral {\n  --ds-button-filled-bg: var(--color-background-bold-neutral);\n  --ds-button-filled-fg: var(--color-foreground-faint-neutral);\n}\n\n.button-filled--intent-brand {\n  --ds-button-filled-bg: var(--color-background-bold-brand);\n  --ds-button-filled-fg: var(--color-foreground-faint-brand);\n}\n\n.button-filled--intent-ai {\n  --ds-button-filled-bg: var(--color-background-bold-ai);\n  --ds-button-filled-fg: var(--color-foreground-faint-ai);\n}\n\n.button-filled--intent-negative {\n  --ds-button-filled-bg: var(--color-background-bold-negative);\n  --ds-button-filled-fg: var(--color-foreground-faint-negative);\n}\n\n.button-filled--intent-warning {\n  --ds-button-filled-bg: var(--color-background-bold-warning);\n  --ds-button-filled-fg: var(--color-foreground-faint-warning);\n}\n\n.button-filled--intent-caution {\n  --ds-button-filled-bg: var(--color-background-bold-caution);\n  --ds-button-filled-fg: var(--color-foreground-faint-caution);\n}\n\n.button-filled--intent-positive {\n  --ds-button-filled-bg: var(--color-background-bold-positive);\n  --ds-button-filled-fg: var(--color-foreground-faint-positive);\n}\n\n.button-filled--intent-guide {\n  --ds-button-filled-bg: var(--color-background-bold-guide);\n  --ds-button-filled-fg: var(--color-foreground-faint-guide);\n}\n\n.button-filled--intent-walkthrough {\n  --ds-button-filled-bg: var(--color-background-bold-walkthrough);\n  --ds-button-filled-fg: var(--color-foreground-faint-walkthrough);\n}\n\n/* strong → medium fg */\n.button-filled--contrast-strong.button-filled--intent-neutral {\n  --ds-button-filled-bg: var(--color-background-strong-neutral);\n  --ds-button-filled-fg: var(--color-foreground-medium-neutral);\n}\n\n.button-filled--contrast-strong.button-filled--intent-brand {\n  --ds-button-filled-bg: var(--color-background-strong-brand);\n  --ds-button-filled-fg: var(--color-foreground-medium-brand);\n}\n\n.button-filled--contrast-strong.button-filled--intent-ai {\n  --ds-button-filled-bg: var(--color-background-strong-ai);\n  --ds-button-filled-fg: var(--color-foreground-medium-ai);\n}\n\n.button-filled--contrast-strong.button-filled--intent-negative {\n  --ds-button-filled-bg: var(--color-background-strong-negative);\n  --ds-button-filled-fg: var(--color-foreground-medium-negative);\n}\n\n.button-filled--contrast-strong.button-filled--intent-warning {\n  --ds-button-filled-bg: var(--color-background-strong-warning);\n  --ds-button-filled-fg: var(--color-foreground-medium-warning);\n}\n\n.button-filled--contrast-strong.button-filled--intent-caution {\n  --ds-button-filled-bg: var(--color-background-strong-caution);\n  --ds-button-filled-fg: var(--color-foreground-medium-caution);\n}\n\n.button-filled--contrast-strong.button-filled--intent-positive {\n  --ds-button-filled-bg: var(--color-background-strong-positive);\n  --ds-button-filled-fg: var(--color-foreground-medium-positive);\n}\n\n.button-filled--contrast-strong.button-filled--intent-guide {\n  --ds-button-filled-bg: var(--color-background-strong-guide);\n  --ds-button-filled-fg: var(--color-foreground-medium-guide);\n}\n\n.button-filled--contrast-strong.button-filled--intent-walkthrough {\n  --ds-button-filled-bg: var(--color-background-strong-walkthrough);\n  --ds-button-filled-fg: var(--color-foreground-medium-walkthrough);\n}\n\n/* medium → strong fg */\n.button-filled--contrast-medium.button-filled--intent-neutral {\n  --ds-button-filled-bg: var(--color-background-medium-neutral);\n  --ds-button-filled-fg: var(--color-foreground-strong-neutral);\n}\n\n.button-filled--contrast-medium.button-filled--intent-brand {\n  --ds-button-filled-bg: var(--color-background-medium-brand);\n  --ds-button-filled-fg: var(--color-foreground-strong-brand);\n}\n\n.button-filled--contrast-medium.button-filled--intent-ai {\n  --ds-button-filled-bg: var(--color-background-medium-ai);\n  --ds-button-filled-fg: var(--color-foreground-strong-ai);\n}\n\n.button-filled--contrast-medium.button-filled--intent-negative {\n  --ds-button-filled-bg: var(--color-background-medium-negative);\n  --ds-button-filled-fg: var(--color-foreground-strong-negative);\n}\n\n.button-filled--contrast-medium.button-filled--intent-warning {\n  --ds-button-filled-bg: var(--color-background-medium-warning);\n  --ds-button-filled-fg: var(--color-foreground-strong-warning);\n}\n\n.button-filled--contrast-medium.button-filled--intent-caution {\n  --ds-button-filled-bg: var(--color-background-medium-caution);\n  --ds-button-filled-fg: var(--color-foreground-strong-caution);\n}\n\n.button-filled--contrast-medium.button-filled--intent-positive {\n  --ds-button-filled-bg: var(--color-background-medium-positive);\n  --ds-button-filled-fg: var(--color-foreground-strong-positive);\n}\n\n.button-filled--contrast-medium.button-filled--intent-guide {\n  --ds-button-filled-bg: var(--color-background-medium-guide);\n  --ds-button-filled-fg: var(--color-foreground-strong-guide);\n}\n\n.button-filled--contrast-medium.button-filled--intent-walkthrough {\n  --ds-button-filled-bg: var(--color-background-medium-walkthrough);\n  --ds-button-filled-fg: var(--color-foreground-strong-walkthrough);\n}\n\n/* faint → bold fg */\n.button-filled--contrast-faint.button-filled--intent-neutral {\n  --ds-button-filled-bg: var(--color-background-faint-neutral);\n  --ds-button-filled-fg: var(--color-foreground-bold-neutral);\n}\n\n.button-filled--contrast-faint.button-filled--intent-brand {\n  --ds-button-filled-bg: var(--color-background-faint-brand);\n  --ds-button-filled-fg: var(--color-foreground-bold-brand);\n}\n\n.button-filled--contrast-faint.button-filled--intent-ai {\n  --ds-button-filled-bg: var(--color-background-faint-ai);\n  --ds-button-filled-fg: var(--color-foreground-bold-ai);\n}\n\n.button-filled--contrast-faint.button-filled--intent-negative {\n  --ds-button-filled-bg: var(--color-background-faint-negative);\n  --ds-button-filled-fg: var(--color-foreground-bold-negative);\n}\n\n.button-filled--contrast-faint.button-filled--intent-warning {\n  --ds-button-filled-bg: var(--color-background-faint-warning);\n  --ds-button-filled-fg: var(--color-foreground-bold-warning);\n}\n\n.button-filled--contrast-faint.button-filled--intent-caution {\n  --ds-button-filled-bg: var(--color-background-faint-caution);\n  --ds-button-filled-fg: var(--color-foreground-bold-caution);\n}\n\n.button-filled--contrast-faint.button-filled--intent-positive {\n  --ds-button-filled-bg: var(--color-background-faint-positive);\n  --ds-button-filled-fg: var(--color-foreground-bold-positive);\n}\n\n.button-filled--contrast-faint.button-filled--intent-guide {\n  --ds-button-filled-bg: var(--color-background-faint-guide);\n  --ds-button-filled-fg: var(--color-foreground-bold-guide);\n}\n\n.button-filled--contrast-faint.button-filled--intent-walkthrough {\n  --ds-button-filled-bg: var(--color-background-faint-walkthrough);\n  --ds-button-filled-fg: var(--color-foreground-bold-walkthrough);\n}\n\n/*\n * Bold, strong, and medium fills already communicate semantic intent. Their\n * on-background foreground tokens preserve AA text contrast across themes,\n * including brand pairs whose chromatic counterparts sit on the 4.5:1 edge.\n */\n.button-filled--contrast-bold.button-filled {\n  --ds-button-filled-fg: var(--color-foreground-on-bold-background-primary);\n}\n\n.button-filled--contrast-strong.button-filled {\n  --ds-button-filled-fg: var(--color-foreground-on-strong-background-primary);\n}\n\n.button-filled--contrast-medium.button-filled {\n  --ds-button-filled-fg: var(--color-foreground-on-medium-background-primary);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/wc/components/ButtonFilled/ButtonFilled.tsx",
      "content": "import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';\nimport { controlWidthClass } from '../../utils';\nimport type { ChoiceBackground } from '../../utils/choice-list';\nimport { beginElevatedControlPress } from '../../utils/control-press';\nimport { renderButtonContent } from '../../utils/button-render';\nimport type { ControlInsetDepth } from '../../utils/control-text';\nimport type {\n  ButtonPopup,\n  ButtonSize,\n  ButtonVariant,\n  ButtonWidth,\n} from '../../utils/button-types';\n\nexport type ButtonFilledIntent =\n  | 'neutral'\n  | 'brand'\n  | 'ai'\n  | 'negative'\n  | 'warning'\n  | 'caution'\n  | 'positive'\n  | 'guide'\n  | 'walkthrough';\n\nexport type ButtonFilledContrast = 'bold' | 'strong' | 'medium' | 'faint';\n\nexport type ButtonFilledBackground = ChoiceBackground;\n\nexport type ButtonFilledVariant = ButtonVariant;\nexport type ButtonFilledSize = ButtonSize;\nexport type ButtonFilledWidth = ButtonWidth;\nexport type ButtonFilledPopup = ButtonPopup;\n\n@Component({\n  tag: 'ds-button-filled',\n  styleUrl: 'ButtonFilled.css',\n  scoped: true,\n})\nexport class ButtonFilled {\n  @Element() el!: HTMLElement;\n\n  /**\n   * Content layout. Default is label-only; pass `icon` for icon-only chrome\n   * (nav / tool rails) or `icon-label` for leading icon + text.\n   */\n  @Prop() variant: ButtonFilledVariant = 'label';\n\n  /** Control density (height, padding, icon, type). */\n  @Prop() size: ButtonFilledSize = 'md';\n\n  /** Use reduced outer geometry when nested inside a control of the same size. */\n  @Prop() isInset: boolean = false;\n\n  /** Single removes 4px overall; double removes 8px overall (xs stays single). */\n  @Prop() insetDepth: ControlInsetDepth = 'single';\n\n  /** Width fit — hug content (default) or fill the parent. */\n  @Prop() width: ButtonFilledWidth = 'hug';\n\n  /** Visible text for `label` / `icon-label` variants. */\n  @Prop() label: string = '';\n\n  /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */\n  @Prop() icon: string = '';\n\n  /** Semantic colour intent. */\n  @Prop() intent: ButtonFilledIntent = 'brand';\n\n  /**\n   * Background fill weight. Foreground uses the paired contrast token:\n   * bold → faint, strong → medium, medium → strong, faint → bold.\n   */\n  @Prop() contrast: ButtonFilledContrast = 'bold';\n\n  /** Show a 1px secondary inset border. */\n  @Prop() hasBorder: boolean = false;\n\n  /**\n   * Actual parent surface context for the optional inset border color only.\n   * Omit on primary and secondary surfaces.\n   */\n  @Prop() background: ButtonFilledBackground | undefined;\n\n  /** Use the half-radius treatment instead of the default control radius. */\n  @Prop() rounded: boolean = false;\n\n  /**\n   * Scale down during a physical pointer press.\n   * Disable when an owning composite requires fixed child or background geometry.\n   */\n  @Prop() pressScale: boolean = true;\n\n  /** Disables interaction. */\n  @Prop() isInactive: boolean = false;\n\n  /** Shows an inline loader and prevents interaction without applying inactive opacity. */\n  @Prop() isLoading: boolean = false;\n\n  /** Native button type. */\n  @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n  /** Accessible name override. Required for icon-only buttons. */\n  @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n\n  /** ID of the popup this button controls. */\n  @Prop() controls: string | undefined;\n\n  /**\n   * Controlled open state of the popup this button triggers. Holds the pressed\n   * wash for the popup's rendered lifecycle. ButtonFilled has no selected state,\n   * so an open popup never promotes to an active treatment.\n   */\n  @Prop() expanded: boolean | undefined;\n\n  /** Popup type exposed to assistive technology. */\n  @Prop() haspopup: ButtonFilledPopup | undefined;\n\n  /**\n   * This action *has* a menu: implies `aria-haspopup=\"menu\"` and adds the trailing\n   * chevron that carries the affordance.\n   *\n   * Only `label` and `icon-label` are supported. A filled button is never the\n   * overflow / more-options control — that role belongs to ButtonUnfilled with an\n   * `Ellipses` glyph, which conveys the menu without a chevron.\n   *\n   * Use `haspopup` directly for non-menu popups.\n   */\n  @Prop() hasMenu: boolean = false;\n\n  @Event() dsClick!: EventEmitter<MouseEvent>;\n\n  private buttonEl: HTMLButtonElement | null = null;\n\n  @Method()\n  async setFocus() {\n    this.buttonEl?.focus();\n  }\n\n  private handleClick = (event: MouseEvent) => {\n    if (this.isInactive || this.isLoading) {\n      event.preventDefault();\n      event.stopPropagation();\n      return;\n    }\n    this.dsClick.emit(event);\n  };\n\n  private get resolvedHaspopup(): ButtonFilledPopup | undefined {\n    return this.haspopup ?? (this.hasMenu ? 'menu' : undefined);\n  }\n\n  private get accessibleName(): string | undefined {\n    if (this.ariaLabel) return this.ariaLabel;\n    if (this.isLoading && this.variant === 'label' && this.label) return this.label;\n    return undefined;\n  }\n\n  private get doubleInset(): boolean {\n    return this.isInset && this.insetDepth === 'double' && this.size !== 'xs';\n  }\n\n  render() {\n    const cls: Record<string, boolean> = {\n      'button-filled': true,\n      'ds-button': true,\n      'ds-focus-ring-inset': true,\n      'ds-control-press-scale': this.pressScale,\n      'ds-interaction-fill': !this.isInactive,\n      /* Bold is the default filled contrast — on-bold interaction tokens. */\n      'ds-interaction-fill--on-bold': this.contrast === 'bold',\n      'ds-interaction-fill--on-strong': this.contrast === 'strong',\n      'ds-interaction-fill--on-medium': this.contrast === 'medium',\n      /* faint → default app interaction tokens (no --on-*). */\n      'button-filled--bordered': this.hasBorder,\n      'ds-button--bordered': this.hasBorder,\n      'button-filled--expanded': this.expanded === true && !this.isInactive,\n      'ds-button--expanded': this.expanded === true && !this.isInactive,\n      'ds-control-inactive': this.isInactive,\n      'ds-control--lg': this.size === 'lg',\n      'ds-control--md': this.size === 'md',\n      'ds-control--sm': this.size === 'sm',\n      'ds-control--xs': this.size === 'xs',\n      'ds-control--inset': this.isInset && !this.doubleInset,\n      'ds-control--inset-double': this.doubleInset,\n      'ds-control-frame': true,\n      'button-filled--icon': this.variant === 'icon',\n      'ds-button--icon': this.variant === 'icon',\n      'button-filled--label': this.variant === 'label',\n      'button-filled--icon-label': this.variant === 'icon-label',\n      'button-filled--rounded': this.rounded,\n      'ds-button--rounded': this.rounded,\n      [`button-filled--background-${this.background}`]: this.background !== undefined,\n      [`button-filled--intent-${this.intent}`]: true,\n      [`button-filled--contrast-${this.contrast}`]: true,\n    };\n\n    return (\n      <Host\n        class={{\n          'button-filled-host': true,\n          'button-filled-host--icon': this.variant === 'icon',\n          'ds-button-host--icon': this.variant === 'icon',\n          'ds-control--lg': this.size === 'lg',\n          'ds-control--md': this.size === 'md',\n          'ds-control--sm': this.size === 'sm',\n          'ds-control--xs': this.size === 'xs',\n          'ds-control--inset': this.isInset && !this.doubleInset,\n          'ds-control--inset-double': this.doubleInset,\n          ...controlWidthClass(this.width),\n        }}\n        tabIndex={-1}\n      >\n        <button\n          ref={el => {\n            this.buttonEl = el ?? null;\n          }}\n          type={this.type}\n          class={cls}\n          disabled={this.isInactive}\n          aria-label={this.accessibleName}\n          aria-busy={this.isLoading ? 'true' : undefined}\n          aria-disabled={this.isLoading ? 'true' : undefined}\n          aria-controls={this.controls}\n          aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}\n          aria-haspopup={this.resolvedHaspopup}\n          onPointerDown={event =>\n            beginElevatedControlPress(\n              event,\n              this.pressScale && !this.isInactive && !this.isLoading,\n            )\n          }\n          onClick={this.handleClick}\n        >\n          {renderButtonContent({\n            namespace: 'button-filled',\n            variant: this.variant,\n            size: this.size,\n            label: this.label,\n            icon: this.icon,\n            hasMenu: this.hasMenu,\n            isLoading: this.isLoading,\n          })}\n        </button>\n      </Host>\n    );\n  }\n}\n",
      "type": "registry:ui"
    }
  ]
}
