{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "type": "registry:component",
  "title": "Button",
  "description": "Triggers an action with solid primary, 1px ghost, or borderless subtle styling. Every target is at least 44px; icon size is a 44px square.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/button.tsx",
      "content": "import * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Solid ink primary, hairline ghost, or borderless subtle. One primary per view. */\n  variant?: \"primary\" | \"ghost\" | \"subtle\";\n  /** Icon buttons stay square at every breakpoint. Supply an accessible name. */\n  size?: \"default\" | \"sm\" | \"icon\";\n  /** Flush into a ButtonGroup: no own stroke, one ink seam. */\n  grouped?: boolean;\n}\n\nconst styles = stylex.create({\n  base: {\n    boxSizing: \"border-box\",\n    minWidth: vlak.hit,\n    fontFamily: \"inherit\",\n    letterSpacing: \"-0.01em\",\n    display: \"inline-flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    gap: \"0.5rem\",\n    cursor: {\n      default: \"pointer\",\n      \":disabled\": \"not-allowed\",\n    },\n    height: {\n      default: vlak.controlH,\n      [mq.phone]: vlak.controlH,\n    },\n    minHeight: {\n      default: null,\n      [mq.phone]: vlak.hit,\n    },\n    width: {\n      default: null,\n      [mq.phone]: \"100%\",\n    },\n    fontSize: vlak.controlFs,\n    paddingInline: {\n      default: \"1.375rem\",\n      [mq.phone]: \"1.25rem\",\n    },\n    borderWidth: vlak.hairline,\n    borderStyle: \"solid\",\n    borderRadius: {\n      default: vlak.radiusSm,\n      [mq.phone]: vlak.radiusSm,\n    },\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    /* Hover and disabled are opacity on paper; in forced colors they become system colors instead. */\n    opacity: {\n      default: 1,\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: 0.85 },\n      \":disabled\": 0.4,\n      [mq.forcedColors]: {\n        default: 1,\n        \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: 1 },\n        \":disabled\": 1,\n      },\n    },\n    outlineWidth: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n    outlineStyle: {\n      default: null,\n      \":focus-visible\": \"solid\",\n    },\n    outlineColor: {\n      default: null,\n      \":focus-visible\": vlak.ink,\n    },\n    outlineOffset: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n  },\n  primary: {\n    fontWeight: 600,\n    backgroundColor: {\n      default: vlak.ink,\n      [mq.forcedColors]: \"ButtonFace\",\n    },\n    color: {\n      default: vlak.paper,\n      [mq.forcedColors]: {\n        default: \"ButtonText\",\n        \":disabled\": \"GrayText\",\n      },\n    },\n    borderColor: {\n      default: \"transparent\",\n      [mq.forcedColors]: {\n        default: \"ButtonText\",\n        \":disabled\": \"GrayText\",\n      },\n    },\n  },\n  ghost: {\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    fontWeight: 500,\n    backgroundColor: {\n      default: \"transparent\",\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: vlak.controlFill },\n      \":disabled\": \"transparent\",\n      [mq.forcedColors]: \"ButtonFace\",\n    },\n    color: {\n      default: vlak.ink,\n      [mq.forcedColors]: {\n        default: \"ButtonText\",\n        \":disabled\": \"GrayText\",\n      },\n    },\n    borderColor: {\n      default: vlak.divider,\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: vlak.controlFill },\n      \":disabled\": vlak.divider,\n      [mq.forcedColors]: {\n        default: \"ButtonText\",\n        \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: \"Highlight\" },\n        \":disabled\": \"GrayText\",\n      },\n    },\n  },\n  subtle: {\n    minHeight: vlak.hit,\n    fontWeight: 500,\n    borderWidth: { default: 0, [mq.forcedColors]: vlak.hairline },\n    borderColor: { default: \"transparent\", [mq.forcedColors]: { default: \"ButtonText\", \":disabled\": \"GrayText\", \"[aria-disabled='true']\": \"GrayText\" } },\n    backgroundColor: { default: \"transparent\", [mq.forcedColors]: \"ButtonFace\" },\n    color: {\n      default: vlak.gray,\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: vlak.ink },\n      [mq.forcedColors]: { default: \"ButtonText\", \":disabled\": \"GrayText\", \"[aria-disabled='true']\": \"GrayText\" },\n    },\n    cursor: { default: \"pointer\", \":disabled\": \"not-allowed\", \"[aria-disabled='true']\": \"not-allowed\" },\n    opacity: {\n      default: 1,\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: 1 },\n      \":disabled\": 0.4,\n      \"[aria-disabled='true']\": 0.4,\n      [mq.forcedColors]: { default: 1, \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: 1 }, \":disabled\": 1, \"[aria-disabled='true']\": 1 },\n    },\n  },\n  sm: {\n    height: {\n      default: vlak.hit,\n      [mq.phone]: vlak.controlH,\n    },\n    minHeight: {\n      default: null,\n      [mq.phone]: vlak.hit,\n    },\n    paddingInline: {\n      default: \"0.875rem\",\n      [mq.phone]: \"1rem\",\n    },\n    fontSize: {\n      default: \"0.8125rem\",\n      [mq.phone]: vlak.controlFs,\n    },\n    minWidth: {\n      default: \"6.5rem\",\n      [mq.phone]: 0,\n    },\n  },\n  icon: {\n    height: { default: vlak.hit, [mq.phone]: vlak.hit },\n    minHeight: { default: vlak.hit, [mq.phone]: vlak.hit },\n    maxHeight: vlak.hit,\n    width: { default: vlak.hit, [mq.phone]: vlak.hit },\n    minWidth: { default: vlak.hit, [mq.phone]: vlak.hit },\n    maxWidth: vlak.hit,\n    flexBasis: vlak.hit,\n    flexGrow: { default: 0, [mq.phone]: 0 },\n    flexShrink: 0,\n    paddingInline: { default: 0, [mq.phone]: 0 },\n    paddingBlock: 0,\n  },\n  grouped: {\n    height: \"auto\",\n    minHeight: vlak.hit,\n    width: {\n      default: null,\n      [mq.phone]: null,\n    },\n    borderWidth: 0,\n    borderRadius: 0,\n    margin: 0,\n    flexGrow: {\n      default: null,\n      [mq.phone]: 1,\n    },\n    minWidth: {\n      default: 0,\n      [mq.phone]: 0,\n    },\n    outlineOffset: {\n      default: null,\n      \":focus-visible\": -2,\n    },\n  },\n  groupedGhost: {\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    backgroundColor: {\n      default: vlak.paper,\n      \":hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current])\": { default: null, [mq.hover]: vlak.controlFill },\n      \":disabled\": vlak.paper,\n      [mq.forcedColors]: \"ButtonFace\",\n    },\n  },\n  groupedSubtle: {\n    backgroundColor: {\n      default: vlak.paper,\n      \"[aria-pressed='true']\": vlak.controlFill,\n      [mq.forcedColors]: { default: \"ButtonFace\", \"[aria-pressed='true']\": \"Highlight\" },\n    },\n    color: {\n      default: vlak.gray,\n      \"[aria-pressed='true']\": vlak.ink,\n      [mq.forcedColors]: { default: \"ButtonText\", \"[aria-pressed='true']\": \"HighlightText\", \":disabled\": \"GrayText\", \"[aria-disabled='true']\": \"GrayText\" },\n    },\n    fontWeight: { default: 500, \"[aria-pressed='true']\": 600 },\n  },\n});\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n  { variant = \"primary\", size = \"default\", grouped = false, type = \"button\", className, style, ...props },\n  ref,\n) {\n  const sx = variant === \"subtle\"\n    ? rs([\"rs-btn-subtle\", size === \"sm\" && \"rs-btn-sm\", className, grouped && \"rs-btn-grouped\", grouped && \"rs-btn-grouped-subtle\", size === \"icon\" && \"rs-btn-icon\"], styles.base, styles.subtle, size === \"sm\" && styles.sm, grouped && styles.grouped, grouped && styles.groupedSubtle, size === \"icon\" && styles.icon)\n    : rs([variant === \"ghost\" ? \"rs-btn-ghost\" : \"rs-btn-primary\", size === \"sm\" && \"rs-btn-sm\", className, grouped && \"rs-btn-grouped\", grouped && variant === \"ghost\" && \"rs-btn-grouped-ghost\", size === \"icon\" && \"rs-btn-icon\"], styles.base, variant === \"ghost\" ? styles.ghost : styles.primary, size === \"sm\" && styles.sm, grouped && styles.grouped, grouped && variant === \"ghost\" && styles.groupedGhost, size === \"icon\" && styles.icon);\n  return <button ref={ref} type={type} {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/button.tsx"
    },
    {
      "path": "vlak/styles/button.css",
      "content": "/* ── button: generated from packages/react/src/components/button.tsx ── */\n.rs-btn-subtle,.rs-btn-ghost,.rs-btn-primary{box-sizing:border-box;min-width:var(--hit);font-family:inherit;letter-spacing:-0.01em;display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;cursor:pointer;height:var(--control-h);font-size:var(--control-fs);padding-inline:1.375rem;border-width:1px;border-style:solid;border-radius:var(--radius-sm);transition:var(--transition);opacity:1}\n.rs-btn-subtle:disabled,.rs-btn-ghost:disabled,.rs-btn-primary:disabled{cursor:not-allowed;opacity:0.4}\n.rs-btn-subtle:focus-visible,.rs-btn-ghost:focus-visible,.rs-btn-primary:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n@media (max-width: 640px){.rs-btn-subtle,.rs-btn-ghost,.rs-btn-primary{height:var(--control-h);min-height:var(--hit);width:100%;padding-inline:1.25rem;border-radius:var(--radius-sm)}}\n@media (prefers-reduced-motion: reduce){.rs-btn-subtle,.rs-btn-ghost,.rs-btn-primary{transition:none}}\n@media (hover: hover) and (pointer: fine){.rs-btn-subtle:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]),.rs-btn-ghost:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]),.rs-btn-primary:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){opacity:0.85}}\n@media (forced-colors: active){.rs-btn-subtle,.rs-btn-ghost,.rs-btn-primary{opacity:1}.rs-btn-subtle:disabled,.rs-btn-ghost:disabled,.rs-btn-primary:disabled{opacity:1}}\n@media (forced-colors: active) and (hover: hover) and (pointer: fine){.rs-btn-subtle:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]),.rs-btn-ghost:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]),.rs-btn-primary:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){opacity:1}}\n.rs-btn-primary{font-weight:600;background-color:var(--text);color:var(--bg);border-color:transparent}\n@media (forced-colors: active){.rs-btn-primary{background-color:ButtonFace;color:ButtonText;border-color:ButtonText}.rs-btn-primary:disabled{color:GrayText;border-color:GrayText}}\n.rs-btn-ghost{transition:var(--transition);font-weight:500;background-color:transparent;color:var(--text);border-color:var(--divider)}\n.rs-btn-ghost:disabled{background-color:transparent;border-color:var(--divider)}\n@media (prefers-reduced-motion: reduce){.rs-btn-ghost{transition:none}}\n@media (hover: hover) and (pointer: fine){.rs-btn-ghost:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){background-color:var(--control-fill);border-color:var(--control-fill)}}\n@media (forced-colors: active){.rs-btn-ghost{background-color:ButtonFace;color:ButtonText;border-color:ButtonText}.rs-btn-ghost:disabled{color:GrayText;border-color:GrayText}}\n@media (forced-colors: active) and (hover: hover) and (pointer: fine){.rs-btn-ghost:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){border-color:Highlight}}\n.rs-btn-subtle{min-height:var(--hit);font-weight:500;border-width:0;border-color:transparent;background-color:transparent;color:var(--text-secondary);cursor:pointer;opacity:1}\n.rs-btn-subtle:disabled{cursor:not-allowed;opacity:0.4}\n.rs-btn-subtle[aria-disabled='true']{cursor:not-allowed;opacity:0.4}\n@media (forced-colors: active){.rs-btn-subtle{border-width:1px;border-color:ButtonText;background-color:ButtonFace;color:ButtonText;opacity:1}.rs-btn-subtle:disabled{border-color:GrayText;color:GrayText;opacity:1}.rs-btn-subtle[aria-disabled='true']{border-color:GrayText;color:GrayText;opacity:1}}\n@media (hover: hover) and (pointer: fine){.rs-btn-subtle:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){color:var(--text);opacity:1}}\n@media (forced-colors: active) and (hover: hover) and (pointer: fine){.rs-btn-subtle:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){opacity:1}}\n.rs-btn-sm{height:var(--hit);padding-inline:0.875rem;font-size:0.8125rem;min-width:6.5rem}\n@media (max-width: 640px){.rs-btn-sm{height:var(--control-h);min-height:var(--hit);padding-inline:1rem;font-size:var(--control-fs);min-width:0}}\n.rs-btn-icon{height:var(--hit);min-height:var(--hit);max-height:var(--hit);width:var(--hit);min-width:var(--hit);max-width:var(--hit);flex-basis:var(--hit);flex-grow:0;flex-shrink:0;padding-inline:0;padding-block:0}\n@media (max-width: 640px){.rs-btn-icon{height:var(--hit);min-height:var(--hit);width:var(--hit);min-width:var(--hit);flex-grow:0;padding-inline:0}}\n.rs-btn-grouped{height:auto;min-height:var(--hit);border-width:0;border-radius:0;margin:0;min-width:0}\n.rs-btn-grouped:focus-visible{outline-offset:-2px}\n@media (max-width: 640px){.rs-btn-grouped{flex-grow:1;min-width:0}}\n.rs-btn-grouped-ghost{transition:var(--transition);background-color:var(--bg)}\n.rs-btn-grouped-ghost:disabled{background-color:var(--bg)}\n@media (prefers-reduced-motion: reduce){.rs-btn-grouped-ghost{transition:none}}\n@media (hover: hover) and (pointer: fine){.rs-btn-grouped-ghost:hover:not(:disabled):not([aria-disabled='true']):not([aria-pressed='true']):not([aria-selected='true']):not([aria-current]){background-color:var(--control-fill)}}\n@media (forced-colors: active){.rs-btn-grouped-ghost{background-color:ButtonFace}}\n.rs-btn-grouped-subtle{background-color:var(--bg);color:var(--text-secondary);font-weight:500}\n.rs-btn-grouped-subtle[aria-pressed='true']{background-color:var(--control-fill);color:var(--text);font-weight:600}\n@media (forced-colors: active){.rs-btn-grouped-subtle{background-color:ButtonFace;color:ButtonText}.rs-btn-grouped-subtle[aria-pressed='true']{background-color:Highlight;color:HighlightText}.rs-btn-grouped-subtle:disabled{color:GrayText}.rs-btn-grouped-subtle[aria-disabled='true']{color:GrayText}}\n",
      "type": "registry:file",
      "target": "styles/vlak/button.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "actions",
      "classes": [
        "rs-btn-primary",
        "rs-btn-ghost",
        "rs-btn-subtle",
        "rs-btn-sm",
        "rs-btn-icon",
        "rs-btn-grouped",
        "rs-btn-grouped-ghost",
        "rs-btn-grouped-subtle"
      ],
      "snippet": "<button class=\"rs-btn-primary\">Primary action</button>\n<button class=\"rs-btn-ghost\">Secondary</button>\n<button class=\"rs-btn-subtle\">Details</button>\n<button class=\"rs-btn-subtle\" disabled>Unavailable</button>\n<button class=\"rs-btn-subtle rs-btn-icon\" aria-label=\"Download\"><svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\"><path d=\"M3.5 11.5V13h9v-1.5M8 3.5v7M5.5 8 8 10.5 10.5 8\" /></svg></button>",
      "cssOnly": false,
      "registryDependencies": [
        "vlak-lib"
      ],
      "aliases": [
        "Button",
        "Primary button",
        "Ghost button",
        "Secondary button",
        "Subtle button",
        "Icon button"
      ],
      "example": "import { Button, Icon } from \"@noorddev/vlak-react\";\n\n<Button>Primary action</Button>\n<Button variant=\"ghost\" size=\"sm\">Secondary</Button>\n<Button variant=\"subtle\">Details</Button>\n<Button variant=\"subtle\" disabled>Unavailable</Button>\n<Button variant=\"subtle\" size=\"icon\" aria-label=\"Download\">\n  <Icon name=\"download\" />\n</Button>",
      "usage": {
        "use": [
          "One primary action per view, with ghost for the secondary action.",
          "Subtle for supporting actions in a toolbar or response. Its transparent surface stays unchanged while muted text turns to ink on hover.",
          "Inside ButtonGroup, subtle buttons use an opaque paper surface and a soft fill for application-supplied aria-pressed selection.",
          "Icon size for an icon-only action with a 44px square target and an accessible label.",
          "Submitting a form or answering a dialog."
        ],
        "avoid": [
          "Navigation that changes the URL; use Link or a nav component.",
          "Standalone persistent settings; use Toggle or Switch. ButtonGroup can present application-owned selection with aria-pressed."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Moves focus to the button"
        },
        {
          "keys": "Enter, Space",
          "does": "Activates it"
        }
      ],
      "a11y": [
        "Renders a native <button>; type defaults to \"button\", so pass type=\"submit\" inside a form.",
        "The visible text is the name. Icon size changes the shape, not the accessible name; give icon-only buttons an aria-label and hide decorative icons.",
        "2px ink focus ring on :focus-visible. disabled uses the native attribute and 40% opacity; forced colors keep system colors."
      ]
    }
  }
}
