Components
Tabs
The Tabs are radio inputs and the Panels are just divs that show and hide
based on the radio inputs' :checked state.
Full support Supported since v123. Full support Supported since v120. Full support Supported since v17.5.
Basics
<script setup lang="ts">import { Tabs, TabsItem, TabsPanel, TabsTab } from "opui-css/vue"</script>
<template> <Tabs> <Tabs.Item open> <TabsTab>Profile</TabsTab> <TabsPanel>Profile settings and information.</TabsPanel> </Tabs.Item> <Tabs.Item> <TabsTab>Settings</TabsTab> <TabsPanel>General account settings.</TabsPanel> </Tabs.Item> <Tabs.Item> <TabsTab>Notifications</TabsTab> <TabsPanel>Manage your notifications.</TabsPanel> </Tabs.Item> </Tabs></template><div class="ui-tabs" role="tablist"><!--[--><!----><!----><!----><!--]--></div>Accessibility
The tab system uses standard radio inputs and labels, so we get group management and keyboard support for free!
Tab List
| Element | Attribute | Description |
|---|---|---|
.ui-tabs | role="tablist" | Identifies the element as a container for a set of tabs. |
input | name | Groups the radio buttons together for exclusive selection. |
label | role="tab" | Identifies the element as a tab to assistive technology. |
Tab Panel
The content area associated with a tab:
| Attribute | Value | Description |
|---|---|---|
role | "tabpanel" | Identifies the element as a tab panel. |
aria-labelledby | string | Links the panel to its trigger ID. |
Keyboard Interaction
- Tab: Moves focus to the active tab trigger (the radio button). Pressing Tab again moves focus out of the tab list to the next focusable element.
- Right Arrow / Down Arrow: Moves focus to the next tab and activates it.
- Left Arrow / Up Arrow: Moves focus to the previous tab and activates it.
API
Tabs
The main container for the tab items.
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional class names. |
name | string | "tabs-XXXX" | A unique name for the exclusive group. Shared with all children. |
Tabs.Item
A logical grouping for a tab trigger and its content. Handles state synchronization.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether this tab is initially active. |
Tabs.Tab
The visible trigger for the tab.
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Optional class name. |
Tabs.Panel
The content area for the tab.
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Optional class name. |
Browser support
Full support Supported since v123. Full support Supported since v120. Full support Supported since v17.5.
See also the full browser support guide.
Installation
@layer components.root { :where(.ui-tabs) { --_accent-color: var(--primary); --_bg-color: transparent;
align-items: flex-start; display: flex; flex-wrap: wrap; gap: 0 var(--size-1); position: relative;
& > .ui-tab-input[type="radio"] { /* utils > .ui-sr-only */ /* TODO: use mixin for this when available */ block-size: 1px; clip-path: inset(50%); inline-size: 1px; overflow: hidden; position: absolute; white-space: nowrap;
&:checked { /* Checked Tab */ & + [role="tab"], & + .ui-tab-label { border-block-end-color: var(--_accent-color); color: var(--_accent-color);
/* Checked Panel */ & + [role="tabpanel"], & + .ui-tab-panel { display: block; } } }
&:focus-visible { & + [role="tab"], & + .ui-tab-label { --focus-ring-color: var(--text-muted); --focus-ring-offset: calc(-1 * var(--size-2)); outline: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } } }
/* Tab */ & > [role="tab"], & > .ui-tab-label { align-items: center; background-color: transparent; border-bottom: 2px solid transparent; border-radius: 0; color: var(--text-muted); cursor: pointer; display: inline-flex; font-weight: 600; justify-content: center; line-height: var(--font-lineheight-4); order: 1; padding: var(--size-2) var(--size-3); position: relative; transition: color 0.1s; user-select: none;
&:hover { background-color: light-dark( oklch(from var(--_accent-color) calc(l * 0.75) none h / 5%), oklch(from var(--_accent-color) calc(l * 1.25) none h / 5%) ); } }
/* Tab Panel */ & > [role="tabpanel"], & > .ui-tab-panel { display: none; inline-size: 100%; order: 2; } }}