{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "type": "registry:ui",
  "title": "Slider",
  "description": "Range / value-picker primitive on top of @base-ui/react/slider. Compositional API: Slider.Root + Track + Indicator + Thumb. Re-exports the DS-styled surface (rail, fill, thumb) without owning state — Base UI handles keyboard (Arrow / Home / End / PageUp /…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "form",
    "primitive"
  ],
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/slider.tsx",
      "target": "components/ui/slider.tsx",
      "type": "registry:ui",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// @interlace/slider v1.2.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/slider\n// What changed since: https://ds.interlace.tools/c/slider#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Slider\n *\n * Range / value-picker primitive on top of @base-ui/react/slider. Compositional\n * API: Slider.Root + Track + Indicator + Thumb. Re-exports the DS-styled\n * surface (rail, fill, thumb) without owning state — Base UI handles keyboard\n * (Arrow / Home / End / PageUp / PageDown), drag, focus-visible, and ARIA.\n *\n * ## Anatomy\n *\n *   Slider (Root)                    (div — data-min-viewport=320)\n *     ├─ SliderControl              (the rail track wrapper — flex row)\n *     │   ├─ SliderTrack            (the rail)\n *     │   │   └─ SliderIndicator    (the filled portion)\n *     │   └─ SliderThumb            (the draggable knob — repeats for range)\n *     └─ {value readout}             (consumer-supplied, optional)\n *\n * ## MIN_VIEWPORT — 320\n *\n * ## Target size (SC 2.5.8, AA)\n *\n * The painted knob is 20×20 CSS px; a transparent `before:` pseudo-element\n * extends the hit area to 24×24 without changing the visual, so the\n * primitive is usable by touch on every phone.\n *\n * ## Contrast (verified by token math)\n *\n * | Composite                                     | Light  | Dark    | Floor           |\n * | --------------------------------------------- | ------ | ------- | --------------- |\n * | rail `bg-input` on `--background`             | 3.62:1 | 3.35:1  | 3:1 (SC 1.4.11) |\n * | knob `border-primary` on `--background`       | 8.80:1 | 11.79:1 | 3:1 (SC 1.4.11) |\n * | fill `bg-primary` on the rail                 | 2.43:1 | 3.52:1  | see below       |\n * | `focus-visible:ring-ring` on `--background`   | 8.80:1 | 11.79:1 | 3:1 (SC 2.4.13) |\n *\n * The rail used to be `bg-muted` — 1.07:1 on `--background`, an invisible\n * track. `--input` fixes that, but note the fill/rail row: in LIGHT mode\n * those two cannot both clear 3:1. The rail needs L ≥ 0.308 to sit 3:1\n * from `--primary` (L 0.069), and any rail that dark measures ≤2.93:1\n * against a white page. The two constraints are mutually exclusive for\n * this brand orange — no rail value satisfies both.\n *\n * Resolved the way WCAG's own Understanding note for sliders does: the\n * KNOB is the state indicator that carries SC 1.4.11 (8.80:1 / 11.79:1\n * against the page in both modes), and the fill is supplementary\n * reinforcement. Do not \"fix\" the fill/rail row by lightening the rail —\n * that reintroduces the invisible-track failure, which is the worse one.\n *\n * `disabled:opacity-50` is exempt (inactive component).\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends Base UI part props       | Each wrapper extends `React.ComponentProps<typeof BaseSlider.X>` |\n * | R6   | data-slot on every part          | slider / -control / -track / -indicator / -thumb            |\n * | R7   | className merged + ...rest       | `cn(BASE, className)` + `{...props}`                        |\n * | R8   | No isXxx; enums for variants     | n/a — no variants yet                                       |\n * | R12  | Reuse over wrap                  | Wraps Base UI's slider — no bespoke drag/keyboard state    |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R18  | Tailwind only                    | Zero inline style; Tailwind classes only                    |\n * | R19  | Tokens only                      | bg-primary, bg-muted, border-border — semantic tokens       |\n * | R25  | Client component                 | Required — Base UI Slider ships client hooks                |\n * | R26  | A11y from upstream               | Base UI owns role=slider, aria-valuenow, keyboard           |\n */\n\nimport { Slider as BaseSlider } from '@base-ui/react/slider';\n\nimport { cn } from '@/lib/utils';\n\nexport const MIN_VIEWPORT = 320 as const;\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof BaseSlider.Root>,\n  React.ComponentProps<typeof BaseSlider.Root>\n>(({ className, ...props }, ref) => (\n  <BaseSlider.Root\n    ref={ref}\n    data-slot=\"slider\"\n    data-min-viewport={String(MIN_VIEWPORT)}\n    className={cn('relative flex w-full touch-none select-none items-center', className)}\n    {...props}\n  />\n));\nSlider.displayName = 'Slider';\n\nconst SliderControl = React.forwardRef<\n  React.ElementRef<typeof BaseSlider.Control>,\n  React.ComponentProps<typeof BaseSlider.Control>\n>(({ className, ...props }, ref) => (\n  <BaseSlider.Control\n    ref={ref}\n    data-slot=\"slider-control\"\n    className={cn('relative flex w-full items-center', className)}\n    {...props}\n  />\n));\nSliderControl.displayName = 'SliderControl';\n\nconst SliderTrack = React.forwardRef<\n  React.ElementRef<typeof BaseSlider.Track>,\n  React.ComponentProps<typeof BaseSlider.Track>\n>(({ className, ...props }, ref) => (\n  <BaseSlider.Track\n    ref={ref}\n    data-slot=\"slider-track\"\n    // bg-input, not bg-muted: the unfilled rail is what identifies the\n    // slider's extent, so SC 1.4.11 applies. `--muted` measured 1.07:1 on\n    // `--background` — the rail was invisible. `--input` is the DS's\n    // 3:1-cleared control-boundary token (3.62 light / 3.35 dark).\n    className={cn('bg-input relative h-2 w-full grow overflow-hidden rounded-full', className)}\n    {...props}\n  />\n));\nSliderTrack.displayName = 'SliderTrack';\n\nconst SliderIndicator = React.forwardRef<\n  React.ElementRef<typeof BaseSlider.Indicator>,\n  React.ComponentProps<typeof BaseSlider.Indicator>\n>(({ className, ...props }, ref) => (\n  <BaseSlider.Indicator\n    ref={ref}\n    data-slot=\"slider-indicator\"\n    className={cn('bg-primary absolute h-full', className)}\n    {...props}\n  />\n));\nSliderIndicator.displayName = 'SliderIndicator';\n\nconst SliderThumb = React.forwardRef<\n  React.ElementRef<typeof BaseSlider.Thumb>,\n  React.ComponentProps<typeof BaseSlider.Thumb>\n>(({ className, ...props }, ref) => (\n  <BaseSlider.Thumb\n    ref={ref}\n    data-slot=\"slider-thumb\"\n    className={cn(\n      'border-primary bg-background block size-5 rounded-full border-2 shadow-sm transition-colors',\n      // Full-opacity ring (8.80:1 light / 11.79:1 dark on --background) —\n      // already well past the 3:1 focus floor, so no /60 alpha needed here.\n      'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n      'disabled:pointer-events-none disabled:opacity-50',\n      // SC 2.5.8 — transparent 24×24 hit area around the 20px knob.\n      \"relative before:absolute before:-inset-[2px] before:content-['']\",\n      className,\n    )}\n    {...props}\n  />\n));\nSliderThumb.displayName = 'SliderThumb';\n\n/* ─────────────────────────────────────────────────────────────────\n * SimpleSlider — convenience composition.\n *\n * Renders Root → Control → Track → Indicator → Thumb(s) with a single\n * `label` prop that auto-derives thumb aria-labels:\n *   - single thumb:  `getAriaLabel = () => label`\n *   - range (2):     `getAriaLabel = (i) => i === 0 ? `${label} minimum` : `${label} maximum``\n *\n * Use this when you don't need per-part className overrides. Drops to\n * about 90% of slider call-sites; the compositional API stays for the\n * remaining 10% that need per-part customisation.\n * ──────────────────────────────────────────────────────────────── */\ninterface SimpleSliderProps\n  extends Omit<React.ComponentProps<typeof BaseSlider.Root>, 'defaultValue'> {\n  /** Accessible label propagated to every thumb's aria-label. Required. */\n  label: string;\n  /**\n   * Initial value. Number for single-thumb, [min, max] tuple for range\n   * (renders 2 thumbs labelled `${label} minimum` / `${label} maximum`).\n   */\n  defaultValue?: number | [number, number];\n}\n\nfunction SimpleSlider({\n  label,\n  defaultValue = 0,\n  className,\n  ...props\n}: SimpleSliderProps) {\n  const isRange = Array.isArray(defaultValue);\n  const thumbs = isRange ? 2 : 1;\n  const getAriaLabel = (i: number) =>\n    isRange ? `${label} ${i === 0 ? 'minimum' : 'maximum'}` : label;\n  const rootValue = Array.isArray(defaultValue) ? defaultValue : [defaultValue];\n\n  return (\n    <Slider\n      defaultValue={rootValue}\n      aria-label={label}\n      className={className}\n      {...props}\n    >\n      <SliderControl>\n        <SliderTrack>\n          <SliderIndicator />\n        </SliderTrack>\n        {Array.from({ length: thumbs }).map((_, i) => (\n          <SliderThumb key={i} getAriaLabel={() => getAriaLabel(i)} />\n        ))}\n      </SliderControl>\n    </Slider>\n  );\n}\n\nexport {\n  Slider,\n  SliderControl,\n  SliderTrack,\n  SliderIndicator,\n  SliderThumb,\n  SimpleSlider,\n};\nexport type { SimpleSliderProps };\n"
    }
  ],
  "meta": {
    "tier": "primitive",
    "client": true,
    "minViewport": 320,
    "loading": false,
    "version": "1.2.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/slider\n\nInstalled to `components/ui/slider.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/slider';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/slider\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
