VibeUI
ComponentsDocs
VibeUI

Components with taste, that you own. Copy the source, retint the tokens, keep every line.

Library

  • All components
  • Signature pieces
  • Text effects
  • Footers

Docs

  • Introduction
  • Installation
  • Theming
  • CLI

Project

  • GitHub
  • Agents and MCP
  • Issues
  • MIT license
© 2026 VibeUI · MIT licensed
  1. Components
  2. Forms
  3. Slider

Slider

Range slider with an accent fill, built on a native range input.

FormsClient component
Volume62
Corner radius24

Install

bash
npx @agents-npm-packages/vibeui add slider --dir ./src/components/ui

Import

tsx
import { Slider } from "@/components/ui/slider"

API

Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.

<Slider>

PropTypeDefaultDescription
valuenumber—Controlled value.
defaultValuenumber40Initial value when uncontrolled.
minnumber0Lower bound.
maxnumber100Upper bound.
stepnumber1Increment between values.
onValueChange((value: number) => void)—Called with the new value.
classNamestring—Extra classes, merged with the component's own.
labelstring—Visible label.
disabledboolean—Prevents interaction and dims the control.

Source

src/components/ui/slider.tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

/** Range slider with an accent fill, built on a native range input. */
export function Slider({
  value,
  defaultValue = 40,
  min = 0,
  max = 100,
  step = 1,
  onValueChange,
  className,
  label,
  disabled,
}: {
  value?: number;
  defaultValue?: number;
  min?: number;
  max?: number;
  step?: number;
  onValueChange?: (value: number) => void;
  className?: string;
  label?: string;
  disabled?: boolean;
}) {
  const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
  const current = value ?? uncontrolled;
  const pct = ((current - min) / (max - min)) * 100;

  return (
    <div className={cn("w-full", className)}>
      {label ? (
        <div className="mb-1 flex items-center justify-between text-[13px]">
          <span className="font-medium text-ink-soft">{label}</span>
          <span className="font-mono text-xs tabular-nums text-muted">
            {current}
          </span>
        </div>
      ) : null}
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={current}
        disabled={disabled}
        aria-label={label}
        onChange={(e) => {
          const next = Number(e.target.value);
          setUncontrolled(next);
          onValueChange?.(next);
        }}
        style={{
          background: `linear-gradient(to right, var(--accent) ${pct}%, var(--surface-sunken) ${pct}%)`,
        }}
        className={cn(
          "my-2.5 block h-1.5 w-full cursor-pointer appearance-none rounded-full disabled:cursor-not-allowed disabled:opacity-45",
          "[&::-webkit-slider-thumb]:size-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-accent [&::-webkit-slider-thumb]:bg-surface [&::-webkit-slider-thumb]:shadow-[0_1px_3px_rgb(0_0_0/0.25)] [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:duration-150 hover:[&::-webkit-slider-thumb]:scale-110 active:[&::-webkit-slider-thumb]:scale-95",
          "[&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-accent [&::-moz-range-thumb]:bg-surface [&::-moz-range-thumb]:shadow-[0_1px_3px_rgb(0_0_0/0.25)]",
          "focus-visible:outline-none focus-visible:[&::-webkit-slider-thumb]:ring-[3px] focus-visible:[&::-webkit-slider-thumb]:ring-ring",
        )}
      />
    </div>
  );
}

Exports Slider. Peer dependencies: clsx, tailwind-merge.

PreviousSelectNextSwitch