Ir al contenido

Tabs

Paneles con pestañas y navegación con teclado entre ellas.

Navegadores
Chrome 123+Firefox 121+Safari 17.5+
Nivel
0
Tamaño comprimido
897 B
Presupuesto de diseño forzado
0 ms
Presupuesto de axe
0
Compatible con SSR
Sí
Basado en
elements/choice
Contrato publicado
Entorno de pruebas

Zona de pruebas

Tabs

Automática

Ingresos mensualesActualizado ahora mismo
$12,480
Actividad reciente12 eventos nuevos esta semana
Configuración del espacio de trabajoGestiona miembros y preferencias

Manual

Ingresos mensualesActualizado ahora mismo
$12,480
Actividad reciente12 eventos nuevos esta semana
Configuración del espacio de trabajoGestiona miembros y preferencias
import { Tabs } from "@gunkin/uiify";

export default function Example() {
  return (
    <Tabs
      className=""
      id="account-tabs"
      defaultValue="profile"
      items={[
        {
          value: "profile",
          label: <span className="">Profile</span>,
          panel: <div className="">Profile settings</div>,
        },
        {
          value: "security",
          label: <span className="">Security</span>,
          panel: <div className="">Security settings</div>,
        },
      ]}
    />
  );
}
/* Tabs — list of triggers with an active underline; content panels below.
   Adapts to [data-orientation]; active state from [data-state="active"]. */

@layer components {
  [data-uiify-tabs] [data-part="trigger"] {
    padding-block: var(--space-sm);
    padding-inline: var(--space-md);
    border: none;
    background: none;
    color: var(--muted-foreground);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    font-weight: var(--weight-medium);
    cursor: pointer;
    border-block-end: 2px solid transparent;
    margin-block-end: calc(-1 * var(--border-width));
    transition: var(--transition-colors);
  }

  [data-uiify-tabs] [data-part="trigger"]:hover {
    color: var(--foreground);
  }

  [data-uiify-tabs] [data-part="trigger"][data-state="active"] {
    border-block-end-color: var(--primary);
    color: var(--foreground);
  }

  /* Panel visibility is owned by @gunkin/uiify/components/behavior.css (radio :checked). */

  [data-uiify-tabs] [data-part="panel"] {
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
  }

  [data-uiify-tabs] > [data-part="item"] {
    display: contents;
  }

  [data-uiify-tabs] {
    display: grid;
    grid-template-rows: auto auto;
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    align-items: start;
    gap: 0;
  }

  [data-uiify-tabs] > [data-part="item"] > input {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  [data-uiify-tabs] > [data-part="item"] > label {
    display: inline-flex;
    grid-row: 1;
    padding-block: var(--space-sm);
    padding-inline: var(--space-md);
    border-block-end: 2px solid transparent;
    color: var(--muted-foreground);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    font-weight: var(--weight-medium);
    cursor: pointer;
  }

  [data-uiify-tabs] > [data-part="item"] > label:hover {
    color: var(--foreground);
  }

  [data-uiify-tabs] > [data-part="item"]:has(> input:checked) > label {
    border-block-end-color: var(--primary);
    color: var(--foreground);
  }

  [data-uiify-tabs] > [data-part="item"] > input:focus-visible + label {
    outline: none;
    box-shadow: 0 0 0 var(--ring-width) color-mix(in oklch, var(--ring) 16%, transparent);
  }

  [data-uiify-tabs] > [data-part="item"] > [data-part="panel"] {
    grid-column: 1 / -1;
    grid-row: 2;
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
  }

  /* Shared presence fade for the newly selected panel; the previous one leaves at once. */
  @media (prefers-reduced-motion: no-preference) {
    [data-uiify-tabs] > [data-part="item"] > [data-part="panel"] {
      transition: opacity var(--duration-fast) var(--ease-out);
    }

    @starting-style {
      [data-uiify-tabs] > [data-part="item"] > [data-part="control"]:checked ~ [data-part="panel"] {
        opacity: 0;
      }
    }
  }
}
import { Tabs } from "@gunkin/uiify";

export default function Example() {
  return (
    <Tabs
      className={
        "[&_[data-part=trigger]]:py-sm [&_[data-part=trigger]]:px-md [&_[data-part=trigger]]:border-0 [&_[data-part=" +
        "trigger]]:border-current [&_[data-part=trigger]]:border-none [&_[data-part=trigger]]:bg-none [&_[data-part=" +
        "trigger]]:bg-transparent [&_[data-part=trigger]]:text-muted-foreground [&_[data-part=trigger]]:text-sm [&_[" +
        "data-part=trigger]]:[line-height:var(--leading-normal)] [&_[data-part=trigger]]:font-[number:var(--weight-m" +
        "edium)] [&_[data-part=trigger]]:cursor-pointer [&_[data-part=trigger]]:[border-block-end-width:2px] [&_[dat" +
        "a-part=trigger]]:[border-block-end-style:solid] [&_[data-part=trigger]]:[border-block-end-color:transparent" +
        "] [&_[data-part=trigger]]:[margin-block-end:calc(-1_*_var(--border-width))] [&_[data-part=trigger]]:[transi" +
        "tion:var(--transition-colors)] [&_[data-part=trigger]:hover]:text-foreground [&_[data-part=trigger][data-st" +
        "ate=active]]:[border-block-end-color:var(--primary)] [&_[data-part=trigger][data-state=active]]:text-foregr" +
        "ound [&_[data-part=panel]]:text-sm [&_[data-part=panel]]:[line-height:var(--leading-normal)] [&>[data-part=" +
        "item]]:contents grid [grid-template-rows:auto_auto] [grid-auto-flow:column] [grid-auto-columns:max-content]" +
        " [align-items:start] gap-0 [&>[data-part=item]>input]:absolute [&>[data-part=item]>input]:[inline-size:1px]" +
        " [&>[data-part=item]>input]:[block-size:1px] [&>[data-part=item]>input]:[margin:-1px] [&>[data-part=item]>i" +
        "nput]:overflow-hidden [&>[data-part=item]>input]:[clip-path:inset(50%)] [&>[data-part=item]>input]:whitespa" +
        "ce-nowrap [&>[data-part=item]>label]:inline-flex [&>[data-part=item]>label]:[grid-row:1] [&>[data-part=item" +
        "]>label]:py-sm [&>[data-part=item]>label]:px-md [&>[data-part=item]>label]:[border-block-end-width:2px] [&>" +
        "[data-part=item]>label]:[border-block-end-style:solid] [&>[data-part=item]>label]:[border-block-end-color:t" +
        "ransparent] [&>[data-part=item]>label]:text-muted-foreground [&>[data-part=item]>label]:text-sm [&>[data-pa" +
        "rt=item]>label]:[line-height:var(--leading-normal)] [&>[data-part=item]>label]:font-[number:var(--weight-me" +
        "dium)] [&>[data-part=item]>label]:cursor-pointer [&>[data-part=item]>label:hover]:text-foreground [&>[data-" +
        "part=item]:has(>input:checked)>label]:[border-block-end-color:var(--primary)] [&>[data-part=item]:has(>inpu" +
        "t:checked)>label]:text-foreground [&>[data-part=item]>input:focus-visible+label]:[outline:none] [&>[data-pa" +
        "rt=item]>input:focus-visible+label]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%" +
        ",_transparent)] [&>[data-part=item]>[data-part=panel]]:[grid-column:1_/_-1] [&>[data-part=item]>[data-part=" +
        "panel]]:[grid-row:2] [&>[data-part=item]>[data-part=panel]]:text-sm [&>[data-part=item]>[data-part=panel]]:" +
        "[line-height:var(--leading-normal)] motion-safe:[&>[data-part=item]>[data-part=panel]]:[transition:opacity_" +
        "var(--duration-fast)_var(--ease-out)] motion-safe:starting:[&>[data-part=item]>[data-part=control]:checked~" +
        "[data-part=panel]]:opacity-0"
      }
      id="account-tabs"
      defaultValue="profile"
      items={[
        {
          value: "profile",
          label: <span className="">Profile</span>,
          panel: <div className="">Profile settings</div>,
        },
        {
          value: "security",
          label: <span className="">Security</span>,
          panel: <div className="">Security settings</div>,
        },
      ]}
    />
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('[&_[data-part=trigger]]:py-sm [&_[data-part=trigger]]:px-md [&_[data-part=trigger]]:border-0 [&_[data-part=trigger]]:border-current [&_[data-part=trigger]]:border-none [&_[data-part=trigger]]:bg-none [&_[data-part=trigger]]:bg-transparent [&_[data-part=trigger]]:text-muted-foreground [&_[data-part=trigger]]:text-sm [&_[data-part=trigger]]:[line-height:var(--leading-normal)] [&_[data-part=trigger]]:font-[number:var(--weight-medium)] [&_[data-part=trigger]]:cursor-pointer [&_[data-part=trigger]]:[border-block-end-width:2px] [&_[data-part=trigger]]:[border-block-end-style:solid] [&_[data-part=trigger]]:[border-block-end-color:transparent] [&_[data-part=trigger]]:[margin-block-end:calc(-1_*_var(--border-width))] [&_[data-part=trigger]]:[transition:var(--transition-colors)] [&_[data-part=trigger]:hover]:text-foreground [&_[data-part=trigger][data-state=active]]:[border-block-end-color:var(--primary)] [&_[data-part=trigger][data-state=active]]:text-foreground [&_[data-part=panel]]:text-sm [&_[data-part=panel]]:[line-height:var(--leading-normal)] [&>[data-part=item]]:contents grid [grid-template-rows:auto_auto] [grid-auto-flow:column] [grid-auto-columns:max-content] [align-items:start] gap-0 [&>[data-part=item]>input]:absolute [&>[data-part=item]>input]:[inline-size:1px] [&>[data-part=item]>input]:[block-size:1px] [&>[data-part=item]>input]:[margin:-1px] [&>[data-part=item]>input]:overflow-hidden [&>[data-part=item]>input]:[clip-path:inset(50%)] [&>[data-part=item]>input]:whitespace-nowrap [&>[data-part=item]>label]:inline-flex [&>[data-part=item]>label]:[grid-row:1] [&>[data-part=item]>label]:py-sm [&>[data-part=item]>label]:px-md [&>[data-part=item]>label]:[border-block-end-width:2px] [&>[data-part=item]>label]:[border-block-end-style:solid] [&>[data-part=item]>label]:[border-block-end-color:transparent] [&>[data-part=item]>label]:text-muted-foreground [&>[data-part=item]>label]:text-sm [&>[data-part=item]>label]:[line-height:var(--leading-normal)] [&>[data-part=item]>label]:font-[number:var(--weight-medium)] [&>[data-part=item]>label]:cursor-pointer [&>[data-part=item]>label:hover]:text-foreground [&>[data-part=item]:has(>input:checked)>label]:[border-block-end-color:var(--primary)] [&>[data-part=item]:has(>input:checked)>label]:text-foreground [&>[data-part=item]>input:focus-visible+label]:[outline:none] [&>[data-part=item]>input:focus-visible+label]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [&>[data-part=item]>[data-part=panel]]:[grid-column:1_/_-1] [&>[data-part=item]>[data-part=panel]]:[grid-row:2] [&>[data-part=item]>[data-part=panel]]:text-sm [&>[data-part=item]>[data-part=panel]]:[line-height:var(--leading-normal)] motion-safe:[&>[data-part=item]>[data-part=panel]]:[transition:opacity_var(--duration-fast)_var(--ease-out)] motion-safe:starting:[&>[data-part=item]>[data-part=control]:checked~[data-part=panel]]:opacity-0');

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

Tabs

PropTipoPredeterminadoDescripción
id*string—Id base que se usa para nombrar el grupo de botones de opción y vincular cada pestaña con su panel.
items*TabsItem[]—Pestañas que se renderizan, cada una con un valor, una etiqueta y un panel opcional que se muestra al seleccionar la pestaña.
defaultValuestring—Valor de la pestaña marcada en el marcado renderizado en el servidor, para que la pestaña inicial esté seleccionada antes de ejecutar JavaScript.
orientation"horizontal" | "vertical""horizontal"Dirección en la que se distribuye la barra de pestañas.
classNamestring—Clase aplicada al elemento raíz.
appendReactNode—Contenido renderizado al final de la barra de pestañas, después de las pestañas.

Teclado

TeclaAcción
← / →Moverse entre pestañas (horizontal)
Home / EndPrimera / última pestaña
Space / EnterActivar pestaña

Accesibilidad

Tablist + botones de opción nativos, o patrón de tabindex móvil con la mejora de cliente.

Puntos de estilo

data-state