Search

Ctrl + K

Slider

This document outlines the steps to create Slider component styled withTailwind CSS and using some npm dependency libraries.

Prerequisites

  • Node.js and npm installed on your machine.
  • Tailwind CSS installed in your project.
  • CVA(class-variance-authority) is a utility for managing CSS class names based on various conditions.
  • clsx is a tiny utility for constructing className strings conditionally.
Step 1: Create variant styles
1// slider.helpers.ts
2import { cva } from "class-variance-authority";
3
4export const wrapperVariants = cva(
5  "w-full relative flex items-center overflow-hidden rounded-full",
6  {
7    variants: {
8      sliderSize: {
9        small: "h-4",
10        medium: "h-6",
11        large: "h-7",
12      },
13    },
14  },
15);
16
17export const barWrapperVariants = cva(
18  "rounded-full w-full bg-neutral-200 dark:bg-neutral-500 relative overflow-hidden",
19  {
20    variants: {
21      sliderSize: {
22        small: "h-1",
23        medium: "h-3",
24        large: "h-7",
25      },
26    },
27  },
28);
29
30export const colorVariants = cva("", {
31  variants: {
32    bgColor: {
33      primary: "bg-blue-500 data-[disabled=true]:bg-blue-300",
34      secondary: "bg-gray-500 data-[disabled=true]:bg-gray-300",
35      success: "bg-green-500 data-[disabled=true]:bg-green-300",
36      danger: "bg-red-500 data-[disabled=true]:bg-red-300",
37      warning: "bg-yellow-500 data-[disabled=true]:bg-yellow-200",
38      info: "bg-cyan-500 data-[disabled=true]:bg-cyan-200",
39      light: "bg-gray-400 data-[disabled=true]:bg-gray-300",
40      dark: "bg-black data-[disabled=true]:bg-gray-300",
41    },
42  },
43});
44
45export const thumbVariants = cva("absolute top-0", {
46  variants: {
47    sliderSize: {
48      small: "w-4 h-4 p-0.5 rounded-full",
49      medium: "w-6 h-6 p-0.5 rounded-full",
50      large: "w-7 h-7 p-1 !bg-none",
51    },
52  },
53});
54
55export const getSliderInitValue = (
56  value: number | undefined,
57  defaultValue: number | undefined,
58  min: number,
59) => {
60  if (value && !Number.isNaN(+value)) return value;
61  if (defaultValue && !Number.isNaN(+defaultValue)) return defaultValue;
62  return min;
63};
64
Step 2: Create Slider component
1// slider.component.tsx
2import clsx from "clsx";
3"use client";
4
5import { cn } from "@/lib/utils";
6import { useState } from "react";
7import type { VariantProps } from "class-variance-authority";
8import {
9  barWrapperVariants,
10  colorVariants,
11  getSliderInitValue,
12  thumbVariants,
13  wrapperVariants,
14} from "./slider.helpers";
15
16export interface SliderProps
17  extends React.InputHTMLAttributes<HTMLInputElement>,
18    VariantProps<typeof wrapperVariants>,
19    VariantProps<typeof colorVariants> {
20  value?: number;
21  defaultValue?: number;
22  min?: number;
23  max?: number;
24  className?: string;
25}
26
27const Slider: React.FC<SliderProps> = ({
28  disabled,
29  value,
30  defaultValue,
31  min = 0,
32  max = 100,
33  sliderSize = "medium",
34  bgColor = "primary",
35  className,
36  onChange,
37  ...props
38}) => {
39  const [innerValue, setInnerValue] = useState<number>(
40    getSliderInitValue(value, defaultValue, min),
41  );
42
43  const percent = Math.abs(innerValue - min) / Math.abs(max - min);
44
45  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
46    const num = +e.target.value;
47    setInnerValue(Number.isNaN(num) ? min : num);
48    onChange?.(e);
49  };
50
51  return (
52    <div className={cn(wrapperVariants({ sliderSize }), className)}>
53      <div className={barWrapperVariants({ sliderSize })}>
54        <div
55          data-disabled={Boolean(disabled)}
56          style={{ width: `${percent * 100}%` }}
57          className={cn("absolute inset-0", colorVariants({ bgColor }))}
58        />
59      </div>
60      <div
61        data-disabled={Boolean(disabled)}
62        style={{
63          left: `calc(${percent} * (100% - ${sliderSize === "small" ? 1 : sliderSize === "medium" ? 1.5 : 1.75}rem))`,
64        }}
65        className={cn(
66          colorVariants({ bgColor }),
67          thumbVariants({ sliderSize }),
68        )}
69      >
70        {sliderSize === "large" && (
71          <>
72            <div className="absolute inset-y-0 right-0 w-1/2 bg-neutral-200 dark:bg-neutral-500" />
73            <div
74              data-disabled={Boolean(disabled)}
75              className={cn(
76                "absolute inset-0 rounded-r-full",
77                colorVariants({ bgColor }),
78              )}
79            />
80          </>
81        )}
82        <div className="absolute inset-1 rounded-full bg-white" />
83      </div>
84      <input
85        type="range"
86        value={value}
87        disabled={disabled}
88        min={min}
89        max={max}
90        onChange={handleChange}
91        className="absolute inset-0 opacity-0"
92        {...props}
93      />
94    </div>
95  );
96};
97
98export default Slider;
99