# Slider

> Elegir un número —o un rango— arrastrando, con el slider de Photos de iCloud: pista de 2 px y perilla de 14. Marcas opcionales y valor visible.

```tsx
import { Slider } from "sebs7n-ui/slider"
```

## Ejemplos

### Un valor, con el número a la vista

El `label` visible queda asociado al thumb por Base UI: no hace falta `aria-label`. Se mueve con las flechas, de a `largeStep` con Shift, y a los extremos con Inicio y Fin.

```tsx
import { Slider } from "sebs7n-ui/slider"
import { useState } from "react"

function Basico() {
  const [opacidad, setOpacidad] = useState(60)
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Slider label="Opacidad de la marca de agua" onValueChange={setOpacidad} showValue value={opacidad} />
      <div
        className="h-16 rounded-control bg-label"
        style={{ opacity: opacidad / 100 }}
      />
    </div>
  )
}
```

### Rango, con marcas

`defaultValue={[min, max]}` pinta los dos thumbs solo. `minStepsBetweenValues` evita que terminen encimados, y las marcas son referencias visuales (`aria-hidden`), no topes.

```tsx
import { Slider } from "sebs7n-ui/slider"
import { useState } from "react"

function Rango() {
  const [precio, setPrecio] = useState<readonly number[]>([200_000, 700_000])
  const formato = new Intl.NumberFormat("es-AR", { style: "currency", currency: "ARS", maximumFractionDigits: 0 })
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Slider
        label="Presupuesto"
        marks={[0, 250_000, 500_000, 750_000, 1_000_000]}
        max={1_000_000}
        min={0}
        minStepsBetweenValues={1}
        onValueChange={setPrecio}
        step={25_000}
        value={precio}
      />
      <p className="text-callout text-label-secondary">
        De {formato.format(precio[0])} a {formato.format(precio[1])}
      </p>
    </div>
  )
}
```

### Tamaños, formato y deshabilitado

`sm` (32px de área arrastrable) para un panel denso; `md` (40px) suelto. `format` es el de `Intl.NumberFormat`: cambia lo que muestra `showValue` y también el `aria-valuetext` que lee el lector de pantalla.

```tsx
import { Slider } from "sebs7n-ui/slider"

function TamanosYFormato() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Slider
        defaultValue={120_000}
        format={{ style: "currency", currency: "ARS", maximumFractionDigits: 0 }}
        label="Tope de gasto mensual"
        locale="es-AR"
        max={500_000}
        showValue
        size="sm"
        step={10_000}
      />
      <Slider defaultValue={8} label="Cuotas" marks={[1, 3, 6, 12]} max={12} min={1} showValue size="md" />
      <Slider defaultValue={50} disabled label="Solo lectura" showValue />
    </div>
  )
}
```

## Props

### Slider

Hereda las props de `Slider.Root`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `aria-label` | `string` | — | Nombre accesible del elemento. |
| `controlClassName` | `string` | — | Clases del área arrastrable, por si hay que cambiarle el ancho. |
| `defaultValue` | `Value` | — | El valor inicial. La forma que le des acá decide si el slider es simple (`40`) o de rango (`[20, 60]`). |
| `label` | `React.ReactNode` | — | Etiqueta visible. Base UI la asocia sola con los thumbs; sin ella hace falta `aria-label`. |
| `marks` | `readonly number[]` | — | Valores donde va un punto de referencia. Siguen a `min` y `max`, no al 0–100 fijo. |
| `max` | `number` | `100` | El extremo de la derecha. Por defecto, 100. |
| `min` | `number` | `0` | El extremo de la izquierda. Por defecto, 0. |
| `showValue` | `boolean` | `false` | Muestra el valor (o "20 – 60" en un rango) a la derecha de la etiqueta. |
| `size` | `"sm" \| "md"` | `"md"` | `sm` 24px · `md` 32px de área arrastrable. La pista (2 px) y la perilla (14) son las mismas en los dos. |
| `value` | `Value` | — | Un número, o un array de dos para un rango. Pasarlo lo vuelve controlado. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |
| `disabled` | `boolean` | — | **Heredada de Base UI.** Apaga la interacción y lo marca con `data-disabled`, que es el atributo del que cuelgan los estilos de apagado. |
| `name` | `string` | — | **Heredada de Base UI.** El nombre con el que el valor viaja en el submit y en el objeto `errors` de `Form`. |
| `onValueChange` | `((value: Value extends number ? number : Value, eventDetails: SliderRoot.ChangeEventDetails) => void)` | — | **Heredada de Base UI.** Se llama con el valor nuevo cada vez que cambia. |
| `orientation` | `"horizontal" \| "vertical"` | — | **Heredada de Base UI.** `horizontal` (default) o `vertical`. Define qué flechas mueven el foco. |

## Teclado

| Tecla | Qué hace |
|---|---|
| ← ↓ | Baja un `step`. |
| → ↑ | Sube un `step`. |
| Shift + flecha | Se mueve un `largeStep` (10 por defecto). |
| Re Pág · Av Pág | Igual que Shift + flecha. |
| Inicio · Fin | Va al mínimo y al máximo. |
| Tab | Entra y sale. En un rango, cada thumb es su propia parada. |

## Accesibilidad

- Cada thumb es un `<input type="range">` de verdad: el teclado y los lectores de pantalla lo tratan como el control nativo.
- El `label` visible queda asociado a los thumbs por Base UI. Sin `label`, el `aria-label` que pases viaja al input, no solo al grupo.
- En un rango, `aria-valuetext` distingue el thumb de inicio del de fin.
- El anillo de foco va en el thumb, **por fuera** (como el del Switch: adentro de una perilla de 14 un anillo interior la taparía), porque el foco real vive en el input de adentro.
- La perilla es lo que se agarra: su borde de 2 px en el label llega a 3:1 contra la página en los dos temas. El área de toque la agranda un `::after` (34 px, 46 con el dedo).
- Las marcas son `aria-hidden`: el valor lo canta el thumb, no un punto.

## Reglas de uso

- **Si el número exacto importa, es un `Input`.** El slider es para proporciones: volumen, opacidad, un presupuesto «de tanto a tanto».
- Con `showValue` el número se lee mientras se arrastra; sin él, el valor solo existe para el lector de pantalla.
- `marks` son referencias, no topes: el valor sigue siendo continuo salvo que subas el `step`.
- En un rango, `minStepsBetweenValues` evita que los dos thumbs terminen encimados.
- `onValueCommitted` para lo caro (pegarle a la API): `onValueChange` dispara en cada píxel del arrastre.

## Relacionados

[input](/docs/components/input.md) · [switch](/docs/components/switch.md) · [progress](/docs/components/progress.md)
