Tabs
Панели с вкладками и навигацией между ними с клавиатуры.
- Браузеры
- Chrome 123+Firefox 121+Safari 17.5+
- Уровень
- 0
- Сжатый размер
- 897 B
- Лимит принудительной компоновки
- 0 ms
- Лимит axe
- 0
- Совместим с SSR
- Да
- На основе
- elements/choice
- Публичный контракт
- Тестовый стенд
Песочница
Tabs
Автоматически
Выручка за месяцТолько что обновлено
$12,480Последние действия12 новых событий на этой неделе
Настройки рабочего пространстваУправление участниками и настройками
Вручную
Выручка за месяцТолько что обновлено
$12,480Последние действия12 новых событий на этой неделе
Настройки рабочего пространстваУправление участниками и настройками
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
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
id* | string | — | Базовый идентификатор для имени группы radio и связи каждой вкладки с её панелью. |
items* | TabsItem[] | — | Отображаемые вкладки: у каждой есть значение, подпись и необязательная панель, показываемая при выборе вкладки. |
defaultValue | string | — | Значение отмеченной вкладки в серверной разметке, чтобы начальная вкладка была выбрана до выполнения JavaScript. |
orientation | "horizontal" | "vertical" | "horizontal" | Направление расположения панели вкладок. |
className | string | — | Класс корневого элемента. |
append | ReactNode | — | Содержимое в конце панели вкладок, после вкладок. |
Клавиатура
| Клавиша | Действие |
|---|---|
| ← / → | Перемещаться между вкладками (горизонтально) |
| Home / End | Первая / последняя вкладка |
| Space / Enter | Активировать вкладку |
Доступность
Tablist и нативные поля radio; при улучшении — схема с перемещаемым tabindex.
Привязка стилей
data-state