{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox",
  "type": "registry:ui",
  "title": "Checkbox",
  "description": "Fluent 2-styled checkbox with shadcn APIs. Built on @base-ui/react's Checkbox primitive for real form participation and keyboard behavior; supports checked, unchecked, and indeterminate states with Fluent's checkmark/dash glyphs from @fluentui/react-icons.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "@fluentui/react-icons"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/checkbox.tsx",
      "type": "registry:ui",
      "target": "components/ui/checkbox.tsx",
      "content": "\"use client\";\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\";\nimport { CheckmarkFilled, SubtractRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Checkbox — Fluent 2-styled, shadcn-API checkbox.\n *\n * This is a Base UI case (conventions §9): the checked/unchecked/indeterminate\n * state machine, the hidden native `<input type=\"checkbox\">` for real form\n * participation, and keyboard activation (Space/Enter) are genuine behavior a\n * styled `<span>` can't express on its own, so this wraps\n * `@base-ui/react/checkbox` (`Checkbox`, imported as a namespace exposing\n * `Checkbox.Root` / `Checkbox.Indicator` — matches the actual\n * `export * as Checkbox from \"./index.parts.js\"` shape in node_modules, the\n * same pattern `avatar.tsx` and `radio-group.tsx` use for their own Base UI\n * subpaths). `Checkbox.Root` renders a `<span role=\"checkbox\">` plus a\n * visually-hidden native `<input type=\"checkbox\">` sibling for form\n * submission; none of that is reimplemented here.\n *\n * `\"use client\"` is required here — integration-pass correction of the\n * original reasoning below. `@base-ui/react/checkbox`'s `CheckboxRoot` and\n * `CheckboxIndicator` do each carry their own `'use client'` directive, so\n * *those* parts alone wouldn't force this file to be a client boundary\n * (same reasoning as `avatar.tsx` / `radio-group.tsx`). But\n * `CheckmarkFilled`/`SubtractRegular` from `@fluentui/react-icons` don't hold\n * to that pattern: the package's shared icon-sizing module\n * (`createFluentIcon.styles.js`) calls `@griffel/react`'s `__styles()` at\n * module scope *without* its own `'use client'` directive, even though\n * `__styles` itself is client-only. Rendering either icon from a Server\n * Component pulls that module into the server's RSC graph, and `next build`\n * (Turbopack) fails collecting page data with \"Attempted to call __styles()\n * from the server but __styles is on the client\" — reproduced against\n * `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and confirmed to\n * affect every route that shares Turbopack's chunk for these icons, not just\n * this one. `\"use client\"` here keeps the icon imports inside a client\n * boundary so they're never evaluated on the server. `select.tsx` carries the\n * same fix for the same reason.\n *\n * Box: Fluent 2's checkbox indicator is 16px square (`size-4`) with\n * `borderRadius: tokens.borderRadiusSmall` (confirmed against Fluent UI v9's\n * `useCheckboxStyles.styles.ts` — `indicatorSizeMedium = '16px'`,\n * `borderRadius: tokens.borderRadiusSmall`, checkmark `fontSize: '12px'`).\n * `borderRadiusSmall` is Fluent's 2px step, which is exactly this kit's\n * `rounded-sm` (`--radius-sm: calc(var(--radius) - 4px)` = 6px − 4px = 2px in\n * `tokens.css`) — no new radius token needed. `aspect-square` guards the box\n * against flex-row squish when paired with a `Label` (mirrors\n * `radio-group.tsx`'s `RadioGroupItem`).\n *\n * Borders + interaction ramp: the unchecked box outlines with Fluent's\n * `NeutralStrokeAccessible` ramp — rest `#616161` (`border-stroke-accessible`),\n * hover `#575757` (`border-stroke-accessible-hover`), pressed `#4d4d4d`\n * (`border-stroke-accessible-pressed`) — the higher-contrast neutral Fluent\n * specs for interactive control outlines (extracted from the Figma checkbox),\n * NOT the lighter `--border`/`border-input` grey the box previously used. When\n * checked/indeterminate the box fills brand, and its border + fill step\n * through Fluent's `CompoundBrandBackground` on hover/press (`brand-70` /\n * `brand-60`; dark hover `brand-80`), mirroring Button's documented per-theme\n * brand ramp (conventions §4). The `data-[checked]:hover:`/`:active:`\n * selectors outrank the neutral hover/active border by specificity, so a\n * checked box stays brand (never the neutral grey) while hovered.\n *\n * Checked glyph: `CheckmarkFilled` from `@fluentui/react-icons` at `size-3`\n * (12px, matching Fluent's checkmark `fontSize`) — a real glyph is\n * appropriate here (unlike Radio's plain dot) because Fluent's checkbox\n * indicator specifically shows a checkmark, not just a filled shape.\n *\n * Indeterminate: Base UI's `indeterminate` prop (forwarded to `Checkbox.Root`\n * so it drives `aria-checked=\"mixed\"` and the `data-indeterminate` attribute\n * Base UI uses for its own state) renders a dash instead of a checkmark. The\n * spec offered a choice between `SubtractRegular` and a hand-drawn 8×2 bar;\n * `SubtractRegular` is used since `@fluentui/react-icons` ships that exact\n * glyph and pulling in an icon dependency is already paid for by the checked\n * state. Which icon to render is decided from this component's own\n * `indeterminate` prop (a plain ternary), not a CSS toggle keyed off Base\n * UI's data attributes — simpler and directly assertable in tests, and\n * `Checkbox.Indicator` already only mounts while `checked || indeterminate`\n * (see `CheckboxIndicator.mjs`'s `rendered` check), so no extra\n * checked-conditional is needed beyond that ternary.\n *\n * States: `data-checked`/`data-unchecked`/`data-indeterminate` and\n * `data-disabled` are Base UI's own presence attributes on `Checkbox.Root`'s\n * rendered `<span>` (see `CheckboxRootDataAttributes` in node_modules —\n * boolean presence, not a `data-state=\"checked\"` value pair like Radix),\n * matched here via Tailwind's `data-[checked]:` / `data-[indeterminate]:` /\n * `data-[disabled]:` arbitrary-attribute variants — the same convention\n * `radio-group.tsx` established. There is no native `:disabled` pseudo-class\n * to reach for (confirmed against `useFocusableWhenDisabled.mjs`: a\n * non-native-button Base UI root gets `aria-disabled`, not a real `disabled`\n * DOM attribute, when disabled — and `:disabled` only ever matches elements\n * that support the HTML `disabled` attribute natively), so the\n * opacity/pointer-events disabled recipe from conventions §4 is re-expressed\n * against `data-[disabled]:` instead of `disabled:`. Focus, per conventions\n * §4, stays the generic offset-ring recipe (not the field bottom-accent\n * variant — a checkbox is a selectable control, not a typed-into field).\n * `aria-invalid` is a bare ARIA attribute Base UI passes through untouched\n * (not in `CheckboxRoot`'s destructured prop list), so the standard\n * `aria-invalid:` recipe applies as-is.\n */\nfunction Checkbox({\n  className,\n  indeterminate = false,\n  ...props\n}: ComponentProps<typeof CheckboxPrimitive.Root>) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      indeterminate={indeterminate}\n      className={cn(\n        // layout — Fluent 16px box, small radius (Fluent borderRadiusSmall)\n        \"aspect-square size-4 shrink-0 cursor-pointer rounded-sm border bg-background\",\n        \"inline-flex items-center justify-center\",\n        // motion\n        \"outline-none transition-colors duration-fast ease-ease\",\n        // unchecked — Fluent NeutralStrokeAccessible rest/hover/pressed ramp\n        \"border-stroke-accessible hover:border-stroke-accessible-hover active:border-stroke-accessible-pressed\",\n        // checked / indeterminate — brand fill (Base UI presence attributes)\n        \"data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground\",\n        \"data-[indeterminate]:border-primary data-[indeterminate]:bg-primary data-[indeterminate]:text-primary-foreground\",\n        // checked interaction ramp — Fluent CompoundBrandBackground Hover/\n        // Pressed (border + fill together); dark hover brightens to brand-80\n        // per Button's per-theme ramp. Outranks the neutral hover/active\n        // border above by specificity so the checked box stays brand.\n        \"data-[checked]:hover:border-brand-70 data-[checked]:hover:bg-brand-70 data-[checked]:active:border-brand-60 data-[checked]:active:bg-brand-60 dark:data-[checked]:hover:border-brand-80 dark:data-[checked]:hover:bg-brand-80\",\n        // disabled — opacity-based (conventions §4), re-expressed against\n        // Base UI's data-disabled presence attribute (this renders a\n        // <span>, not a native disableable element — see doc comment)\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        // focus — generic offset-ring recipe (conventions §4)\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        // invalid — shadcn aria-invalid treatment (conventions §4)\n        \"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"flex items-center justify-center\"\n      >\n        {indeterminate ? (\n          <SubtractRegular aria-hidden=\"true\" className=\"size-3\" />\n        ) : (\n          <CheckmarkFilled aria-hidden=\"true\" className=\"size-3\" />\n        )}\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
    }
  ]
}
