Tabs
BetaAlterna entre vistas hermanas dentro de la misma página, sin salir de contexto.
Abrir en Figma
registry/ds/tabs.tsxNombre, idioma y zona horaria de tu cuenta.
import { Tab, TabList, TabPanel, Tabs } from "@/registry/ds/tabs";
export default function TabsBasic() {
return (
<Tabs defaultValue="general" className="w-full max-w-lg">
<TabList aria-label="Configuración de la cuenta">
<Tab value="general">General</Tab>
<Tab value="miembros">Miembros</Tab>
<Tab value="facturacion">Facturación</Tab>
<Tab value="ajustes" disabled>Ajustes</Tab>
</TabList>
<TabPanel value="general" className="text-sm text-fg-muted">Nombre, idioma y zona horaria de tu cuenta.</TabPanel>
<TabPanel value="miembros" className="text-sm text-fg-muted">Quién tiene acceso y con qué permisos.</TabPanel>
<TabPanel value="facturacion" className="text-sm text-fg-muted">Medios de pago, comprobantes y plan actual.</TabPanel>
<TabPanel value="ajustes" className="text-sm text-fg-muted">Ajustes avanzados.</TabPanel>
</Tabs>
);
}Cuándo usarlo
Las tabs organizan contenido del mismo nivel; no disparan acciones ni son pasos de un proceso.
| Usá Tabs para… | Usá otra cosa para… |
|---|---|
| Secciones hermanas de una misma entidad (una cuenta, un curso). | Ir a otra página → navegación o Link. |
| Cambiar la vista de los mismos datos (Día / Semana / Mes). | Pasos en orden → Stepper. |
| 2 a 7 opciones de label corto. | Filtrar una lista → Chip o Select. |
Estilos
Un solo estilo —y un solo radio— por vista.
underline
solid
segmented
outline
import { Tab, TabList, Tabs, type TabsVariant } from "@/registry/ds/tabs";
const variants: TabsVariant[] = ["underline", "solid", "segmented", "outline"];
export default function TabsVariants() {
return (
<div className="flex w-full max-w-lg flex-col gap-8">
{variants.map((variant) => (
<div key={variant} className="flex flex-col gap-2">
<span className="text-xs font-medium uppercase tracking-wide text-fg-subtle">{variant}</span>
<Tabs variant={variant} defaultValue="1">
<TabList aria-label={`Ejemplo ${variant}`}>
<Tab value="1">General</Tab>
<Tab value="2">Miembros</Tab>
<Tab value="3">Facturación</Tab>
</TabList>
</Tabs>
</div>
))}
</div>
);
}| Estilo | Cuándo |
|---|---|
underline | El default. Navegación principal de una página o sección. |
solid | Más presencia: filtros de vista protagonistas, marketing. |
segmented | Alternar la vista de un mismo contenido, en toolbars. Se lee como un control, no como navegación. |
outline | Opciones que se sienten como elecciones separadas. |
Icono, contador, tamaño y radio
import { Badge } from "@/registry/ds/badge";
import { Home } from "@/registry/ds/icons";
import { Tab, TabList, Tabs } from "@/registry/ds/tabs";
export default function TabsExtras() {
return (
<div className="flex w-full max-w-lg flex-col gap-8">
<Tabs defaultValue="inicio">
<TabList aria-label="Con icono y contador">
<Tab value="inicio" icon={<Home />}>Inicio</Tab>
<Tab value="mensajes" badge={<Badge size="sm">2</Badge>}>Mensajes</Tab>
<Tab value="tareas" badge={<Badge size="sm">14</Badge>}>Tareas</Tab>
</TabList>
</Tabs>
<Tabs variant="solid" radius="semi" size="sm" defaultValue="dia">
<TabList aria-label="Solid, semi, small">
<Tab value="dia">Día</Tab>
<Tab value="semana">Semana</Tab>
<Tab value="mes">Mes</Tab>
</TabList>
</Tabs>
</div>
);
}El contador es un Badge real: cuenta cosas pendientes, no decora.
Contenido
- Sustantivos de una o dos palabras: "Miembros", no "Ver miembros".
- Orden por importancia o frecuencia; la primera es la que se ve al llegar.
- Siempre hay una activa. Nunca más de una fila de tabs.
- Una tab deshabilitada necesita explicación: un Tooltip o texto cerca.
- Si no entran, el listado scrollea horizontalmente. Si pasa seguido, son demasiadas.
Medidas
| Estilo | Alto sm / md | Padding X | Radio Semi |
|---|---|---|---|
underline | 34 / 40 | 8 | — · indicador 2 px |
solid | 32 / 40 | 12 / 16 | radius-sm / radius-md |
segmented | 28 / 36 | 12 / 16 | radius-sm · pista padding 4 |
outline | 32 / 40 | 12 / 16 | radius-sm / radius-md |
Label: Text sm — Regular, Medium en la activa. Icono 16. Gap interno 6.
Tokens por estado
| Parte | Propiedad | Token |
|---|---|---|
| Label | Reposo → hover | text/muted |
| Label activo | Color | text/default |
| Label deshabilitado | Color | text/disabled |
| Underline | Línea base | border/default |
| Underline · activa | Indicador 2 px | primary/solid |
| Solid · hover | Fondo | bg/subtle |
| Solid · activa | Fondo / texto | primary/solid |
| Segmented | Pista | bg/surface |
| Segmented · activa | Fondo + Elevation/xs | bg/elevated |
| Outline | Borde → hover | border/default |
| Outline · activa | Borde y texto | primary/accent |
| Foco | Anillo 2 px | focus/ring |
En Figma
| Propiedad en Figma | Valores | En código | Nota |
|---|---|---|---|
| Style | Underline · Solid · Segmented · Outline · Marker | variant | Marker (óvalo a mano) queda solo en Figma, para piezas expresivas. |
| Size | sm · md | size | — |
| Radius | Full · Semi | radius | — |
| .Tab item › State | Default · Hover · Active · Disabled | value / disabled | — |
| Icon leading (+ swap) | on/off · icono | icon | — |
| Badge | on/off | badge | — |
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/tabs.jsonPlayground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
Nombre, idioma y zona horaria de tu cuenta.
variant
size
radius
<Tabs defaultValue="general">
<TabList aria-label="Configuración de la cuenta">
<Tab value="general">General</Tab>
<Tab value="miembros">Miembros</Tab>
<Tab value="informes">Informes</Tab>
</TabList>
<TabPanel value="general">…</TabPanel>
<TabPanel value="miembros">…</TabPanel>
<TabPanel value="informes">…</TabPanel>
</Tabs>Uso
<Tabs defaultValue="general">
<TabList aria-label="Configuración de la cuenta">
<Tab value="general">General</Tab>
<Tab value="miembros">Miembros</Tab>
</TabList>
<TabPanel value="general">…</TabPanel>
<TabPanel value="miembros">…</TabPanel>
</Tabs>Props
Sobre Base UI Tabs.
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
Tabs · variant | "underline" | "solid" | "segmented" | "outline" | "underline" | Estilo de toda la barra. |
Tabs · size | "sm" | "md" | "md" | Alto de las tabs. |
Tabs · radius | "full" | "semi" | "full" | Sin efecto en underline. |
Tabs · value / defaultValue | string | — | Tab activa, controlada o inicial. |
Tabs · onValueChange | (value) => void | — | Al cambiar de tab. |
TabList · aria-label | string | — | Nombre del conjunto. Requerido. |
Tab · value | string | — | Identificador; lo comparte con su TabPanel. |
Tab · icon · badge | ReactNode | — | Icono antes y contador después del label. |
Tab · disabled | boolean | false | No se puede activar; sigue siendo enfocable para que se pueda leer. |
Teclado
| Tecla | Qué hace |
|---|---|
| Tab | Entra a la barra (a la tab activa) y después pasa al panel. |
| ←→ | Mueve el foco entre tabs. |
| HomeEnd | Primera / última tab. |
| EnterEspacio | Activa la tab enfocada. |
Qué viene resuelto
role="tablist",tabytabpanelconectados conaria-controls/aria-labelledby, yaria-selecteden la activa.- Un solo tab stop para toda la barra. Las deshabilitadas siguen siendo enfocables (así un lector de pantalla las encuentra) pero no se activan.
- La activa no depende solo del color: cambia el peso y tiene indicador o fondo.
Qué tenés que cuidar vos
TabListnecesitaaria-label.- Si cada tab tiene su URL, sincronizá
valuecon la ruta: que el link se pueda compartir y el botón Atrás funcione. - No metas contenido crítico solo en una tab escondida (errores de un formulario).