HextaUI

Slider

Elige un valor o un rango arrastrando, tocando la pista o usando las teclas, con saltos que se deslizan y una burbuja de valor opcional.

Volume
60
import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderDemo() {
  return (
    <Slider defaultValue={60} className="max-w-xs">
      <SliderLabel>Volume</SliderLabel>
      <SliderValue />
    </Slider>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/slider.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"
<Slider defaultValue={[50]} max={100} step={1} aria-label="Volume" />

<Slider defaultValue={50}>
  <SliderLabel>Volume</SliderLabel>
  <SliderValue />
</Slider>

<Slider /> dibuja un thumb por valor, así que un número o un array de un elemento crea un slider simple y dos elementos crean un rango. Hijos como <SliderLabel /> y <SliderValue /> se sitúan en una línea sobre la pista.

Mientras arrastras, el thumb sigue exactamente al puntero. Cuando el valor salta, por un toque en la pista, una tecla o un nuevo valor controlado, el thumb y el relleno se deslizan hasta allí en 180ms en lugar de teletransportarse.

Slider
├── SliderLabel
└── SliderValue

Tamaños

sm, default y lg escalan juntos el thumb y la pista.

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

export function SliderSizes() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-8">
      <Slider size="sm" defaultValue={25} aria-label="Small" />
      <Slider defaultValue={50} aria-label="Default" />
      <Slider size="lg" defaultValue={75} aria-label="Large" />
    </div>
  )
}

Rango

Pasa dos valores para un rango. Da nombre a cada thumb con getAriaLabel y mantenlos separados con minStepsBetweenValues. Con draggableRange, arrastra en cualquier punto entre los thumbs para mover ambos a la vez; un toque ahí sigue moviendo el thumb más cercano.

"use client"

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderRange() {
  return (
    <Slider
      defaultValue={[200, 800]}
      max={1000}
      step={10}
      minStepsBetweenValues={10}
      draggableRange
      format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
      getAriaLabel={(index) =>
        index === 0 ? "Minimum price" : "Maximum price"
      }
      className="max-w-xs"
    >
      <SliderLabel>Price</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

Burbuja de valor

showValue muestra el valor con formato sobre el thumb mientras se arrastra o tiene el foco del teclado. Úsalo en lugar de una fila de etiqueta, ya que la burbuja sube al espacio sobre la pista.

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

export function SliderValueBubble() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-10 pt-8">
      <Slider
        defaultValue={0.4}
        max={1}
        step={0.01}
        format={{ style: "percent" }}
        showValue
        aria-label="Opacity"
      />
      <Slider defaultValue={[30, 70]} showValue aria-label="Range" />
    </div>
  )
}

Pasos y formato

step ajusta el valor, largeStep define el salto de Page Up y Shift + flecha, y format da formato a cada número que el slider muestra o anuncia.

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderSteps() {
  return (
    <Slider
      defaultValue={1}
      min={0.5}
      max={2}
      step={0.25}
      largeStep={0.5}
      format={{ minimumFractionDigits: 2 }}
      className="max-w-xs"
    >
      <SliderLabel>Playback speed</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

Controlado

onValueChange se dispara en cada movimiento y onValueCommitted una vez al soltar, que es el momento de guardar. Definir el valor desde fuera también se desliza.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderControlled() {
  const [value, setValue] = React.useState(40)
  const [saved, setSaved] = React.useState(40)

  return (
    <div className="flex w-full max-w-xs flex-col gap-4">
      <Slider
        value={value}
        onValueChange={setValue}
        onValueCommitted={setSaved}
      >
        <SliderLabel>Brightness</SliderLabel>
        <SliderValue />
      </Slider>
      <div className="flex items-center justify-between gap-3">
        <span className="text-sm text-muted-foreground">Saved: {saved}</span>
        <div className="flex gap-2">
          <Button variant="outline" size="sm" onClick={() => setValue(0)}>
            Off
          </Button>
          <Button variant="outline" size="sm" onClick={() => setValue(100)}>
            Max
          </Button>
        </div>
      </div>
    </div>
  )
}

Vertical

Con orientation="vertical" el slider llena la altura de su padre, y Up sube el valor.

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

const bands = [
  { label: "60 Hz", value: 70 },
  { label: "250 Hz", value: 45 },
  { label: "1 kHz", value: 55 },
  { label: "4 kHz", value: 80 },
  { label: "16 kHz", value: 35 },
]

export function SliderVertical() {
  return (
    <div className="flex h-48 gap-6">
      {bands.map((band) => (
        <div key={band.label} className="flex flex-col items-center gap-3">
          <Slider
            orientation="vertical"
            defaultValue={band.value}
            aria-label={band.label}
          />
          <span className="text-xs text-muted-foreground tabular-nums">
            {band.label}
          </span>
        </div>
      ))}
    </div>
  )
}

Con iconos

Coloca iconos junto al slider en una fila flex. El slider ocupa el ancho restante.

import { IconVolume, IconVolume3 } from "@tabler/icons-react"

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

export function SliderIcons() {
  return (
    <div className="flex w-full max-w-xs items-center gap-3 text-muted-foreground">
      <IconVolume3 className="size-4 shrink-0" aria-hidden />
      <Slider defaultValue={50} aria-label="Volume" />
      <IconVolume className="size-4 shrink-0" aria-hidden />
    </div>
  )
}

Deshabilitado

Todo el slider se atenúa e ignora la entrada del puntero y del teclado.

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderDisabled() {
  return (
    <Slider defaultValue={30} disabled className="max-w-xs">
      <SliderLabel>Bass boost</SliderLabel>
      <SliderValue />
    </Slider>
  )
}

Etiquetas largas

Las etiquetas largas se ajustan, y el valor se queda al final.

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderLongLabel() {
  return (
    <Slider
      defaultValue={[1200, 48000]}
      min={0}
      max={50000}
      step={100}
      format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
      className="max-w-xs"
    >
      <SliderLabel>
        Annual household income before taxes, including side projects
      </SliderLabel>
      <SliderValue />
    </Slider>
  )
}

De derecha a izquierda

Dentro de un padre de derecha a izquierda el relleno empieza por la derecha y las flechas lo siguen. Pasa locale para usar los dígitos del lector.

import { Slider, SliderLabel, SliderValue } from "@/components/ui/slider"

export function SliderRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-xs flex-col gap-8">
      <Slider defaultValue={65} locale="ar-EG">
        <SliderLabel>مستوى الصوت</SliderLabel>
        <SliderValue />
      </Slider>
      <Slider defaultValue={[20, 70]} locale="ar-EG" draggableRange>
        <SliderLabel>النطاق</SliderLabel>
        <SliderValue />
      </Slider>
    </div>
  )
}
KeyAcción
TabMueve el foco al siguiente thumb.
→↑Aumenta el valor un paso. → lo disminuye en derecha a izquierda.
←↓Disminuye el valor un paso. ← lo aumenta en derecha a izquierda.
Shift + ArrowPage UpPage DownSe mueve según largeStep.
HomeDefine el mínimo, o el valor del thumb anterior en un rango.
EndDefine el máximo, o el valor del siguiente thumb en un rango.
  • Cada thumb contiene un input type="range" nativo, así que es un slider con su propio valor, mínimo y máximo.
  • <SliderLabel /> da nombre a cada thumb. Sin él, el aria-label de <Slider /> se pasa a los thumbs y no al contenedor, de modo que el slider nunca queda sin nombre. En un rango, usa getAriaLabel para distinguir los thumbs.
  • <SliderValue /> y la burbuja de valor no se anuncian en cada cambio; el thumb ya informa de su valor.
  • Pulsar un thumb no muestra un anillo de foco; las flechas sí. Con movimiento reducido el thumb salta en lugar de deslizarse.
  • En pantallas táctiles el área de toque crece hasta al menos 44px sin cambiar el diseño.

Construido sobre el slider de Base UI. Cada parte acepta las props de la primitiva que envuelve.

PropTipoPredeterminado
valueEl valor controlado. Un thumb por elemento.
number | number[]–
defaultValue
number | number[]–
onValueChangeSe dispara en cada cambio mientras se arrastra.
(value, eventDetails) => void–
onValueCommittedSe dispara una vez cuando termina un cambio.
(value, eventDetails) => void–
min
number0
max
number100
step
number1
largeStep
number10
minStepsBetweenValues
number0
size
"sm" | "default" | "lg""default"
orientation
"horizontal" | "vertical""horizontal"
draggableRangeEn un rango, arrastrar entre los thumbs los mueve juntos, manteniendo la separación.
booleanfalse
showValueMuestra el valor sobre el thumb mientras se arrastra o tiene el foco del teclado.
booleanfalse
thumbAlignmentedge mantiene el thumb dentro de la pista en min y max.
"edge" | "center" | "edge-client-only""edge"
thumbCollisionBehavior
"push" | "swap" | "none""push"
format
Intl.NumberFormatOptions–
locale
Intl.LocalesArgument"en-US"
aria-labelDa nombre a los thumbs.
string–
getAriaLabelDa nombre a cada thumb de un rango.
(index: number) => string–
getAriaValueText
(formattedValue: string, value: number, index: number) => string–
name
string–
disabled
booleanfalse
className
string | (state) => string–
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="slider"La raíz.
data-sizesm, default o lg.
data-jumpPresente durante 220ms mientras el thumb se desliza a un nuevo valor.
data-range-draggingPresente mientras se arrastra todo el rango.
data-orientationhorizontal o vertical.
data-draggingPresente mientras hay un puntero pulsado.
data-disabledPresente cuando está deshabilitado.
data-invalidPresente cuando no es válido dentro de un Field.
--slider-thumbEl diámetro del thumb, definido por size.
--slider-trackEl grosor de la pista, definido por size.
data-slot="slider-control"El área pulsable alrededor de la pista.
data-slot="slider-track"La pista.
data-slot="slider-range"El relleno entre el inicio, o el primer thumb, y el último thumb.
data-slot="slider-thumb"Cada thumb. data-index indica su posición.
data-activeEn el thumb que tiene el foco o se está arrastrando.
data-slot="slider-thumb-value"La burbuja de valor de showValue.

Da nombre a los thumbs y crece para llenar la fila. Renderiza un <div>.

PropTipoPredeterminado
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescripción
data-slot="slider-label"La etiqueta.

Muestra los valores con formato, unidos con un guion en un rango. Renderiza un <output>.

PropTipoPredeterminado
childrenTexto personalizado.
(formattedValues: string[], values: number[]) => ReactNode–
render
ReactElement | (props, state) => ReactElement<output>
AtributoDescripción
data-slot="slider-value"El valor.
data-orientationhorizontal o vertical.
data-draggingPresente mientras hay un puntero pulsado.
data-disabledPresente cuando está deshabilitado.
data-invalidPresente cuando no es válido dentro de un Field.

Usado en bloques

Bloques que se construyen sobre Slider.