HextaUI

Tabs

Cambia entre vistas con un indicador que se desliza hasta la pestaña activa, como control segmentado o con subrayado, y listas que se desplazan cuando desbordan.

Overview

Visitors are up 12% this week, mostly from search.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

export function TabsDemo() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-md">
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="analytics">Analytics</TabsTrigger>
        <TabsTrigger value="reports">Reports</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">
        <div className="rounded-xl border p-4 text-sm">
          <p className="font-medium">Overview</p>
          <p className="text-muted-foreground">
            Visitors are up 12% this week, mostly from search.
          </p>
        </div>
      </TabsContent>
      <TabsContent value="analytics">
        <div className="rounded-xl border p-4 text-sm">
          <p className="font-medium">Analytics</p>
          <p className="text-muted-foreground">
            Average session length is 4m 12s across 18,240 visits.
          </p>
        </div>
      </TabsContent>
      <TabsContent value="reports">
        <div className="rounded-xl border p-4 text-sm">
          <p className="font-medium">Reports</p>
          <p className="text-muted-foreground">
            Three scheduled reports go out on Monday.
          </p>
        </div>
      </TabsContent>
    </Tabs>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/tabs.json

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

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="analytics">Analytics</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">…</TabsContent>
  <TabsContent value="analytics">…</TabsContent>
</Tabs>

Un indicador se desliza hasta la pestaña activa y se redimensiona para ajustarse a ella, mientras el contenido cambia al instante, de modo que cambiar con frecuencia nunca espera a una animación. El indicador se coloca antes de que la página se hidrate, así que nunca aparece de golpe al cargar.

Tabs
├── TabsList
│   └── TabsTrigger
└── TabsContent

Subrayado

variant="line" en TabsList desliza un subrayado en lugar de una píldora.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

const tabs = ["Activity", "Pull requests", "Issues", "Settings"]

export function TabsLine() {
  return (
    <Tabs defaultValue="Activity" className="w-full max-w-md">
      <TabsList variant="line">
        {tabs.map((tab) => (
          <TabsTrigger key={tab} value={tab}>
            {tab}
          </TabsTrigger>
        ))}
      </TabsList>
      {tabs.map((tab) => (
        <TabsContent key={tab} value={tab}>
          <p className="py-2 text-sm text-muted-foreground">
            {tab} for this repository.
          </p>
        </TabsContent>
      ))}
    </Tabs>
  )
}

Muchas pestañas

Cuando las pestañas no caben, la lista se desplaza lateralmente, desvanece los bordes que tienen más por ver y desplaza la pestaña activa o enfocada hasta dejarla a la vista.

import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

const months = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
]

export function TabsOverflow() {
  return (
    <Tabs defaultValue="September" className="w-full max-w-sm">
      <TabsList variant="line">
        {months.map((month) => (
          <TabsTrigger key={month} value={month}>
            {month}
          </TabsTrigger>
        ))}
      </TabsList>
    </Tabs>
  )
}

Muchas pestañas, segmentadas

El estilo pill se desplaza igual, manteniendo su pista mientras la pestaña elegida permanece a la vista.

import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

const categories = [
  "All",
  "Design",
  "Engineering",
  "Marketing",
  "Sales",
  "Support",
  "Finance",
  "Legal",
  "Operations",
]

export function TabsOverflowSegmented() {
  return (
    <Tabs defaultValue="Finance" className="w-full max-w-xs">
      <TabsList>
        {categories.map((category) => (
          <TabsTrigger key={category} value={category}>
            {category}
          </TabsTrigger>
        ))}
      </TabsList>
    </Tabs>
  )
}

Iconos y contadores

Pon iconos y badges dentro de los triggers.

import { IconCode, IconGitPullRequest, IconMessage } from "@tabler/icons-react"

import { Badge } from "@/components/ui/badge"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

export function TabsIcons() {
  return (
    <Tabs defaultValue="code">
      <TabsList variant="line">
        <TabsTrigger value="code">
          <IconCode />
          Code
        </TabsTrigger>
        <TabsTrigger value="pulls">
          <IconGitPullRequest />
          Pull requests
          <Badge>12</Badge>
        </TabsTrigger>
        <TabsTrigger value="discussions">
          <IconMessage />
          Discussions
        </TabsTrigger>
      </TabsList>
    </Tabs>
  )
}

Vertical

orientation="vertical" apila las pestañas; el subrayado pasa al lateral.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

const sections = [
  { value: "general", label: "General", text: "Name, avatar and language." },
  { value: "security", label: "Security", text: "Password and two-factor." },
  { value: "billing", label: "Billing", text: "Plan, invoices and payment." },
]

export function TabsVertical() {
  return (
    <Tabs
      defaultValue="general"
      orientation="vertical"
      className="w-full max-w-md"
    >
      <TabsList variant="line">
        {sections.map((section) => (
          <TabsTrigger key={section.value} value={section.value}>
            {section.label}
          </TabsTrigger>
        ))}
      </TabsList>
      {sections.map((section) => (
        <TabsContent key={section.value} value={section.value}>
          <div className="flex flex-col gap-1 py-2 text-sm">
            <p className="font-medium">{section.label}</p>
            <p className="text-muted-foreground">{section.text}</p>
          </div>
        </TabsContent>
      ))}
    </Tabs>
  )
}

Deshabilitado

El teclado omite las pestañas deshabilitadas.

import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

export function TabsDisabled() {
  return (
    <Tabs defaultValue="day">
      <TabsList>
        <TabsTrigger value="day">Day</TabsTrigger>
        <TabsTrigger value="week">Week</TabsTrigger>
        <TabsTrigger value="month" disabled>
          Month
        </TabsTrigger>
      </TabsList>
    </Tabs>
  )
}

Controlado

Pasa value y onValueChange. Los cambios externos también deslizan el indicador.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"

const steps = ["Cart", "Shipping", "Payment", "Review"]

export function TabsControlled() {
  const [step, setStep] = React.useState("Cart")
  const index = steps.indexOf(step)

  return (
    <div className="flex flex-col items-start gap-4">
      <Tabs value={step} onValueChange={(value) => setStep(String(value))}>
        <TabsList>
          {steps.map((item) => (
            <TabsTrigger key={item} value={item}>
              {item}
            </TabsTrigger>
          ))}
        </TabsList>
      </Tabs>
      <Button
        size="sm"
        variant="outline"
        onClick={() => setStep(steps[(index + 1) % steps.length])}
      >
        Next step
      </Button>
    </div>
  )
}

De derecha a izquierda

Las pestañas, las flechas y el indicador siguen la dirección de lectura.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

export function TabsRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <Tabs defaultValue="account">
        <TabsList variant="line">
          <TabsTrigger value="account">الحساب</TabsTrigger>
          <TabsTrigger value="password">كلمة المرور</TabsTrigger>
          <TabsTrigger value="alerts">التنبيهات</TabsTrigger>
        </TabsList>
        <TabsContent value="account">
          <p className="py-2 text-sm text-muted-foreground">إعدادات الحساب.</p>
        </TabsContent>
        <TabsContent value="password">
          <p className="py-2 text-sm text-muted-foreground">
            غيّر كلمة المرور.
          </p>
        </TabsContent>
        <TabsContent value="alerts">
          <p className="py-2 text-sm text-muted-foreground">إدارة التنبيهات.</p>
        </TabsContent>
      </Tabs>
    </div>
  )
}
KeyAcción
TabEntra en la lista sobre la pestaña activa y luego pasa al panel.
←→Se mueve entre pestañas, dando la vuelta en los extremos. ↑ y ↓ en vertical.
HomeEndVa a la primera o a la última pestaña.
EnterSpaceAbre la pestaña enfocada. Define activateOnFocus en TabsList para abrir las pestañas a medida que se mueve el foco.
  • La lista es un tablist; cada trigger es un tab que controla su tabpanel.
  • Los paneles son enfocables, para que los usuarios de teclado puedan llegar a contenido que no tiene enlaces ni botones.
  • Con movimiento reducido, el indicador salta en lugar de deslizarse.
PropTipoPredeterminado
value
any–
defaultValue
any0
onValueChange
(value, details) => void–
orientation
"horizontal" | "vertical""horizontal"
AtributoDescripción
data-activation-direction"left", "right", "up" o "down": dónde está la nueva pestaña respecto a la anterior.
PropTipoPredeterminado
variant
"default" | "line""default"
activateOnFocus
booleanfalse
loopFocus
booleantrue
AtributoDescripción
data-slot="tabs-indicator"La píldora o el subrayado deslizante.
data-scrolled-start / data-scrolled-endPresente cuando la lista puede desplazarse en esa dirección.
--active-tab-left / --active-tab-widthPosición y tamaño de la pestaña activa, también -top y -height.
PropTipoPredeterminado
value
any–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescripción
data-activeLa pestaña abierta.
data-disabledLa pestaña está deshabilitada.
PropTipoPredeterminado
value
any–
keepMountedMantiene el panel en el DOM mientras está oculto.
booleanfalse

Usado en bloques

Bloques que se construyen sobre Tabs.