Introduction

Shadcn/ui, which is a UI Library that provides a set of pre-styled, accessible React UI components built on top of Radix UI.

However, the slider component does not appear to support the range features provided by the Radix slider. There’s an issue open that points out that the Radix Slider should be able to support ranges with multiple handles. We’ve modified the shadcn/ui slider component to implement range features provided by Radix (or we’ve tried our best and it seems to be working). We’ve also added label support for the multiple handles.

So far shadcn/ui is a very impressive UI library that we’re exited to investigate and share with others.

Steps to modify the shadcn/ui slider

1. Review the original slider component code

The original shadcn Slider component (at the time of this article) is as follows:

import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"

import { cn } from "@/components/app/shadcn/lib/utils"

const Slider = React.forwardRef(({ className, ...props }, ref) => (
  <SliderPrimitive.Root
    ref={ref}
    className={cn("relative flex w-full touch-none select-none items-center", className)}
    {...props}>
    <SliderPrimitive.Track
      className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
      <SliderPrimitive.Range className="absolute h-full bg-primary" />
    </SliderPrimitive.Track>
    <SliderPrimitive.Thumb
      className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
  </SliderPrimitive.Root>
))

Slider.displayName = SliderPrimitive.Root.displayName

export { Slider }Code language: JavaScript (javascript)

2. Add range support & label support

The idea is to change the value prop from a single number to an array [min, max] as well as allow us to pass labels that show the min and max values selected by each handle:

import React, { useState } from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider';

import { cn } from '@/components/app/shadcn/lib/utils';

const Slider = React.forwardRef(({ className, min, max, step, formatLabel, value, onValueChange, ...props }, ref) => {
    const initialValue = Array.isArray(value) ? value : [min, max];
    const [localValues, setLocalValues] = useState(initialValue);

    const handleValueChange = (newValues) => {
        setLocalValues(newValues);
        if (onValueChange) {
            onValueChange(newValues);
        }
    };

    return (
        <SliderPrimitive.Root
            ref={ref}
            min={min}
            max={max}
            step={step}
            value={localValues}
            onValueChange={handleValueChange}
            className={cn('relative flex w-full touch-none select-none items-center', className)}
            {...props}
        >
            <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
                <SliderPrimitive.Range className="absolute h-full bg-primary" />
            </SliderPrimitive.Track>
            {localValues.map((value, index) => (
                <React.Fragment key={index}>
                    <div className="absolute text-center" style={{ left: `calc(${((value - min) / (max - min)) * 100}% + 0px)`  , top: `10px`}}>
                        <span className="text-xs">{formatLabel ? formatLabel(value) : value}</span>
                    </div>
                    <SliderPrimitive.Thumb
                        className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
                    />
                </React.Fragment>
            ))}
        </SliderPrimitive.Root>
    );
});

Slider.displayName = SliderPrimitive.Root.displayName;

export { Slider };
Code language: JavaScript (javascript)

3. Usage

You can use the enhanced shadcn Slider in your component as follows:

import { Slider } from "./path-to-enhanced-slider"; // Adjust the import path accordingly

const MyComponent = () => {
  const [range, setRange] = useState([0, 24]);

  const handleRangeChange = (value) => {
    setRange(value);
  };

  return (
    <Slider
      defaultValue={[0, 24]}
      max={48}
      min={0}
      step={1}
      value={range}
      onValueChange={handleRangeChange}
      formatLabel={(value) => `${value} hrs`}
    />
  );
};Code language: JavaScript (javascript)

Conclusion

By extending the original shadcn/ui slider component, we’ve added the ability to select a range with two handles and display labels below them.

Please consider we’ve stripped down code to help with presentation. If you have any comments, please ask here or even better in the shadcn/ui github issue mentioned earlier.

Happy coding!

Support our work

Support the GBTI network by becoming a member of our private community.

Become a supporter and join the GBTI Community.