IndiaCN UI

Slider

Single or range slider with optional value labels.

Introduction

UX4G specifies a two-thumb range slider with an optional value label under each thumb. The same component handles a single value — pass one value instead of two.

Installation

npx shadcn@latest add https://indiacn.in/r/slider.json
import { Slider } from '@/components/ui/slider';

Usage

const [value, setValue] = useState([25, 75]);

<Slider value={value} onValueChange={setValue} showLabels />

Examples

Single value

40%

SliderDefault
'use client';

import { useState } from 'react';

import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';

export default function Component() {
  const [value, setValue] = useState([40]);

  return (
    <div className='w-full max-w-[360px]'>
      <Label className='mb-3 block'>Household income share</Label>
      <Slider value={value} onValueChange={setValue} showLabels />
    </div>
  );
}

Range

25%

75%

SliderRange
'use client';

import { useState } from 'react';

import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';

export default function Component() {
  const [value, setValue] = useState([25, 75]);

  return (
    <div className='w-full max-w-[360px]'>
      <Label className='mb-3 block'>Age range</Label>
      <Slider value={value} onValueChange={setValue} showLabels />
    </div>
  );
}

Formatted values

formatValue controls both the visible label and each thumb's accessible name, so they cannot drift apart.

₹20,000

₹65,000

SliderFormatted
'use client';

import { useCallback, useState } from 'react';

import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';

export default function Component() {
  const [value, setValue] = useState([20000, 65000]);
  const formatRupees = useCallback((amount: number) => `₹${amount.toLocaleString('en-IN')}`, []);

  return (
    <div className='w-full max-w-[360px]'>
      <Label className='mb-3 block'>Annual income</Label>
      <Slider
        value={value}
        onValueChange={setValue}
        min={0}
        max={100000}
        step={5000}
        showLabels
        formatValue={formatRupees}
      />
    </div>
  );
}

Disabled

30%

60%

SliderDisabled
'use client';

import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';

export default function Component() {
  return (
    <div className='w-full max-w-[360px]'>
      <Label className='mb-3 block'>Locked after submission</Label>
      <Slider defaultValue={[30, 60]} showLabels disabled />
    </div>
  );
}

Accessibility

Built on Radix Slider, so dragging, touch and keyboard behaviour are not reimplemented — arrow keys step, Home and End jump to the bounds, and each thumb is a separate focus stop.

Every thumb takes its accessible name from formatValue. A screen-reader user hearing "50" learns nothing; hearing "₹50,000" they know what they are setting. That is why the formatter feeds both.

Measurements

Read off the Figma symbols:

track8px, neutral-100 (#dddddd)
selected range8px, primary (#613af5)
thumb24px, neutral-0 fill, 1px primary border
label offset13px below the track

The labels sit at the ends rather than following the thumbs. UX4G's symbols show them tracking each thumb's position, but a label pinned under a thumb collides with its neighbour as the two values converge — and at 0%/100% it would hang off the ends. Ends are the honest compromise; pass showLabels={false} and render your own if you need them to follow.

API Reference

Slider

PropTypeDefaultDescription
valuenumber[]Controlled. One entry per thumb
defaultValuenumber[]Uncontrolled starting value
onValueChange(value: number[]) => voidFires while dragging
min / maxnumber0 / 100Bounds
stepnumber1Increment
showLabelsbooleanfalseRenders each value beneath the track
formatValue(value: number) => stringappends %Label text and accessible name
disabledbooleanfalseDims and disables

Any other Radix Slider prop is forwarded.

On this page