{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "range-slider",
  "title": "Range Slider",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/range-slider/RangeSlider.vue",
      "content": "<script setup lang=\"ts\">\nimport type { SliderRootProps } from 'reka-ui'\nimport type { HTMLAttributes } from 'vue'\nimport { computed } from 'vue'\nimport { reactiveOmit } from '@vueuse/core'\nimport { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'reka-ui'\nimport { cn } from '@/lib/utils'\n\n// CLAUDE.md mandate: avoid `interface extends ReakUiX` (SFC compiler bails\n// in Vue 3.5+ because reka-ui has no exports.types). Intersection form below.\nexport type RangeSliderProps = Omit<SliderRootProps, 'defaultValue'> & {\n  class?: HTMLAttributes['class']\n  /** The controlled value of the range slider. Can be binded with v-model. */\n  modelValue?: [number, number]\n  /** The value of the range slider that should be checked when initially rendered. */\n  defaultValue?: [number, number]\n  /** When `true`, prevents the user from interacting with the range slider */\n  disabled?: boolean\n  /** Minimum value */\n  min?: number\n  /** Maximum value */\n  max?: number\n  /** Step value */\n  step?: number\n  /** Label for the range slider */\n  label?: string\n  /** Hint text for the range slider */\n  hint?: string\n  /** Error messages to display */\n  errorMessages?: string | string[]\n  /** Whether to show error state */\n  error?: boolean\n  /** Custom color for the track fill */\n  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | string\n  /** Thumb size */\n  thumbSize?: 'sm' | 'md' | 'lg'\n  /** Track height */\n  trackHeight?: 'sm' | 'md' | 'lg'\n  /** Show ticks */\n  showTicks?: boolean\n  /** Tick interval */\n  tickInterval?: number\n  /** Show thumb labels */\n  thumbLabel?: boolean\n  /** Always show dirty state */\n  alwaysDirty?: boolean\n  /** Invert the slider */\n  inverted?: boolean\n  /** Density of the slider */\n  density?: 'compact' | 'default' | 'comfortable'\n  /** Hide thumb labels (deprecated, use thumbLabel instead) */\n  hideThumbLabel?: boolean\n  /** Format thumb label */\n  thumbLabelFormat?: (value: number) => string\n  /** Strict range (thumbs cannot cross) */\n  strict?: boolean\n}\n\nconst props = withDefaults(defineProps<RangeSliderProps>(), {\n  min: 0,\n  max: 100,\n  step: 1,\n  density: 'default',\n  thumbSize: 'md',\n  trackHeight: 'md',\n})\n\nconst emits = defineEmits<{\n  'update:modelValue': [value: [number, number]]\n}>()\n\nconst delegatedProps = reactiveOmit(\n  props,\n  'class',\n  'label',\n  'hint',\n  'errorMessages',\n  'error',\n  'color',\n  'thumbSize',\n  'trackHeight',\n  'showTicks',\n  'tickInterval',\n  'thumbLabel',\n  'thumbLabelFormat',\n  'alwaysDirty',\n  'inverted',\n  'density',\n  'hideThumbLabel',\n  'strict',\n)\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\n// Color classes\nconst colorClasses: Record<string, string> = {\n  primary: 'bg-primary',\n  secondary: 'bg-secondary',\n  success: 'bg-[var(--success)]',\n  warning: 'bg-[var(--warning)]',\n  error: 'bg-destructive',\n  info: 'bg-[var(--info)]',\n}\n\n// Track height classes\nconst trackHeightClasses = {\n  sm: 'h-1',\n  md: 'h-1.5',\n  lg: 'h-2',\n}\n\n// Thumb size classes\nconst thumbSizeClasses = {\n  sm: 'size-3',\n  md: 'size-4',\n  lg: 'size-5',\n}\n\n// Generate ticks\nconst ticks = computed(() => {\n  if (!props.showTicks || !props.tickInterval) return []\n  const result: number[] = []\n  for (let i = props.min; i <= props.max; i += props.tickInterval) {\n    result.push(i)\n  }\n  return result\n})\n\n// Build error state\nconst hasError = computed(() => {\n  if (props.error) return true\n  if (\n    props.errorMessages &&\n    (typeof props.errorMessages === 'string' ? props.errorMessages : props.errorMessages.length > 0)\n  )\n    return true\n  return false\n})\n\n// Calculate positions for ticks and labels\nconst tickPositions = computed(() => {\n  return ticks.value.map((tick) => ({\n    value: tick,\n    position: ((tick - props.min) / (props.max - props.min)) * 100,\n  }))\n})\n</script>\n\n<template>\n  <div class=\"flex flex-col gap-2\">\n    <label v-if=\"label\" class=\"text-sm font-medium\">\n      {{ label }}\n    </label>\n\n    <p v-if=\"hint && !hasError\" class=\"text-muted-foreground text-xs\">\n      {{ hint }}\n    </p>\n\n    <div class=\"flex items-center gap-4\">\n      <!-- Min value display -->\n      <div class=\"text-muted-foreground min-w-[3rem] text-sm\">\n        {{ modelValue?.[0] ?? min }}\n      </div>\n\n      <SliderRoot\n        v-slot=\"{ modelValue }\"\n        v-bind=\"{ 'data-slot': 'range-slider', ...forwarded }\"\n        class=\"relative flex w-full touch-none items-center select-none\"\n        @update:model-value=\"(val) => emits('update:modelValue', val as [number, number])\"\n      >\n        <SliderTrack\n          data-uipkge\n          data-slot=\"slider-track\"\n          :class=\"cn('bg-muted relative w-full overflow-hidden rounded-full', trackHeightClasses[trackHeight])\"\n        >\n          <SliderRange\n            data-uipkge\n            data-slot=\"slider-range\"\n            :class=\"cn('absolute h-full', colorClasses[color as string] || colorClasses.primary)\"\n          />\n        </SliderTrack>\n\n        <!-- Ticks -->\n        <div\n          v-if=\"showTicks && ticks.length > 0\"\n          class=\"pointer-events-none absolute top-1/2 right-0 left-0 flex -translate-y-1/2 justify-between\"\n        >\n          <div v-for=\"tick in ticks\" :key=\"tick\" class=\"bg-muted-foreground/30 h-2 w-0.5 rounded-full\" />\n        </div>\n\n        <!-- Start Thumb (Min) -->\n        <SliderThumb\n          data-uipkge\n          data-slot=\"slider-thumb\"\n          :index=\"0\"\n          :class=\"\n            cn(\n              'border-primary ring-ring/50 bg-background block rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',\n              thumbSizeClasses[thumbSize],\n              hasError && 'border-destructive',\n            )\n          \"\n        >\n          <span\n            v-if=\"thumbLabel\"\n            class=\"bg-background absolute -top-6 left-1/2 -translate-x-1/2 rounded px-1 text-xs whitespace-nowrap\"\n          >\n            {{ thumbLabelFormat ? thumbLabelFormat(modelValue?.[0] ?? 0) : (modelValue?.[0] ?? 0) }}\n          </span>\n        </SliderThumb>\n\n        <!-- End Thumb (Max) -->\n        <SliderThumb\n          data-uipkge\n          data-slot=\"slider-thumb\"\n          :index=\"1\"\n          :class=\"\n            cn(\n              'border-primary ring-ring/50 bg-background block rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',\n              thumbSizeClasses[thumbSize],\n              hasError && 'border-destructive',\n            )\n          \"\n        >\n          <span\n            v-if=\"thumbLabel\"\n            class=\"bg-background absolute -top-6 left-1/2 -translate-x-1/2 rounded px-1 text-xs whitespace-nowrap\"\n          >\n            {{ thumbLabelFormat ? thumbLabelFormat(modelValue?.[1] ?? 0) : (modelValue?.[1] ?? 0) }}\n          </span>\n        </SliderThumb>\n      </SliderRoot>\n\n      <!-- Max value display -->\n      <div class=\"text-muted-foreground min-w-[3rem] text-sm\">\n        {{ modelValue?.[1] ?? max }}\n      </div>\n    </div>\n\n    <!-- Tick labels -->\n    <div v-if=\"showTicks && ticks.length > 0\" class=\"text-muted-foreground flex justify-between px-1 text-xs\">\n      <span>{{ min }}</span>\n      <span>{{ max }}</span>\n    </div>\n\n    <div v-if=\"hasError\" class=\"flex flex-col gap-0.5\">\n      <p v-if=\"typeof errorMessages === 'string'\" class=\"text-destructive text-xs\">\n        {{ errorMessages }}\n      </p>\n      <template v-else>\n        <p v-for=\"(msg, i) in errorMessages\" :key=\"i\" class=\"text-destructive text-xs\">\n          {{ msg }}\n        </p>\n      </template>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/range-slider/RangeSlider.vue"
    },
    {
      "path": "packages/registry-vue/components/range-slider/index.ts",
      "content": "export { default as RangeSlider } from './RangeSlider.vue'\n\nexport interface RangeSliderProps {\n  /** The controlled value of the range slider. Can be binded with v-model. */\n  modelValue?: [number, number]\n  /** The value of the range slider that should be checked when initially rendered. */\n  defaultValue?: [number, number]\n  /** When `true`, prevents the user from interacting with the range slider */\n  disabled?: boolean\n  /** Minimum value */\n  min?: number\n  /** Maximum value */\n  max?: number\n  /** Step value */\n  step?: number\n  /** Label for the range slider */\n  label?: string\n  /** Hint text for the range slider */\n  hint?: string\n  /** Error messages to display */\n  errorMessages?: string | string[]\n  /** Whether to show error state */\n  error?: boolean\n  /** Custom color for the track fill */\n  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | string\n  /** Thumb size */\n  thumbSize?: 'sm' | 'md' | 'lg'\n  /** Track height */\n  trackHeight?: 'sm' | 'md' | 'lg'\n  /** Show ticks */\n  showTicks?: boolean\n  /** Tick interval */\n  tickInterval?: number\n  /** Show thumb labels */\n  thumbLabel?: boolean\n  /** Always show dirty state */\n  alwaysDirty?: boolean\n  /** Invert the slider */\n  inverted?: boolean\n  /** Density of the slider */\n  density?: 'compact' | 'default' | 'comfortable'\n}\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/range-slider/index.ts"
    }
  ],
  "dependencies": [
    "@vueuse/core",
    "reka-ui"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Two-thumb range slider for \"between X and Y\" inputs — price filters, age ranges, time windows. Built on reka-ui with proper keyboard handling and aria-valuetext.",
  "categories": [
    "form"
  ]
}