Stepper
BetaMuestra en qué paso de un proceso está la persona y cuántos faltan.
registry/ds/stepper.tsximport { Stepper } from "@/registry/ds/stepper";
const steps = [
{ label: "Datos personales", description: "Nombre y documento" },
{ label: "Carrera", description: "Plan y sede" },
{ label: "Documentación", description: "Título y foto" },
{ label: "Confirmación" },
];
export default function StepperBasic() {
return (
<div className="flex w-full max-w-2xl flex-col gap-10">
<Stepper steps={steps} current={2} label="Inscripción" />
<div className="max-w-xs">
<Stepper steps={steps} current={1} orientation="vertical" size="sm" label="Inscripción, vertical" />
</div>
</div>
);
}Cuándo usarlo
Procesos lineales de 3 a 6 pasos que no entran en una pantalla: inscripción, checkout, configuración inicial. Con 2 pasos no hace falta; con más de 6, agrupá.
- Labels cortos, sustantivos: "Carrera", "Documentación".
- La descripción aclara qué se pide, no repite el label.
- Se puede volver a un paso completado (
onClick); nunca saltar a uno pendiente. - Horizontal arriba del formulario; vertical en una columna lateral o en mobile.
Controlado
current lo maneja el padre: Anterior y Siguiente lo mueven, y los pasos
completados reciben onClick para volver sin perder lo cargado.
Paso 2 de 4: Carrera
Plan y sede
"use client";
import { useState } from "react";
import { Button } from "@/registry/ds/button";
import { Stepper } from "@/registry/ds/stepper";
const pasos = [
{ label: "Datos personales", description: "Nombre y documento" },
{ label: "Carrera", description: "Plan y sede" },
{ label: "Documentación", description: "Título y foto" },
{ label: "Confirmación", description: "Revisá y enviá" },
];
/** Controlado: Anterior/Siguiente mueven `current`; los pasos ya completados son clickeables para volver sin perder lo cargado */
export default function StepperInteractive() {
const [current, setCurrent] = useState(1);
const done = current >= pasos.length;
return (
<div className="flex w-full max-w-2xl flex-col gap-6">
<Stepper steps={pasos.map((p, i) => ({ ...p, onClick: i < current ? () => setCurrent(i) : undefined }))} current={current} label="Inscripción" />
<div className="rounded-xl border border-border bg-bg-elevated p-5 text-sm">
<p className="font-medium text-fg">{done ? "¡Inscripción enviada!" : `Paso ${current + 1} de ${pasos.length}: ${pasos[current].label}`}</p>
<p className="mt-1 text-fg-muted">{done ? "Te avisamos por correo cuando esté aprobada." : pasos[current].description}</p>
</div>
<div className="flex justify-between">
<Button variant="stroke" disabled={current === 0} onClick={() => setCurrent((c) => Math.max(0, c - 1))}>
Anterior
</Button>
<Button onClick={() => setCurrent((c) => Math.min(pasos.length, c + 1))} disabled={done}>
{current === pasos.length - 1 ? "Enviar" : "Siguiente"}
</Button>
</div>
</div>
);
}| Size | Círculo | Conector | Label |
|---|---|---|---|
sm | 24 | 2 px | Text sm / Medium |
md | 32 | 2 px | Text sm / Medium |
Descripción Text xs. Gap círculo–texto 8 (horizontal) / 12 (vertical).
| Parte | Propiedad | Token |
|---|---|---|
| Completado | Fondo + check | primary/solid |
| Actual | Borde 2 px y número | primary/accent |
| Actual | Fondo | bg/elevated |
| Pendiente | Borde | border/default |
| Pendiente | Número y label | text/subtle |
| Conector recorrido | Fondo | primary/solid |
| Conector pendiente | Fondo | border/default |
| Label | Color | text/default |
| Descripción | Color | text/muted |
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/stepper.jsonPlayground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
Orientación
size
Paso actual
const steps = [
{ label: "Datos personales", description: "Nombre y documento" },
{ label: "Carrera", description: "Plan y sede" },
{ label: "Documentación", description: "Título y foto" },
{ label: "Confirmación", description: "Revisá y enviá" },
];
<Stepper steps={steps} current={2} label="Inscripción" />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
steps | { label, description?, onClick? }[] | — | Requerido. onClick solo aplica a pasos completados. |
current | number | — | Índice del paso actual, desde 0. Igual al largo = todo completado. |
orientation | "horizontal" | "vertical" | "horizontal" | Disposición. |
size | "sm" | "md" | "md" | Círculos de 24 / 32. |
label | string | "Progreso" | Nombre accesible del nav. |
<nav>con lista ordenada: el lector anuncia "Progreso, lista de 4 elementos".- El paso actual lleva
aria-current="step"; cada paso anuncia "Completado", "Paso actual" o "Pendiente". - El estado no depende solo del color: check, borde grueso y peso del label.
- Los pasos completados clickeables son botones reales.