OTabs
OTabs renders accessible tabs as pills grouped in one track, the selected one raised on a filled background, or a bottom bar with an accent indicator within thumb reach on mobile, driven by v-model so an application can bind them to its URL. Each tab is an OTabPane child that holds its own content, such as a component.
Usage
Step by step
- Import
OTabsandOTabPanefrom theooko-ui/tabspublic subpath. - Bind the selected
valuewithv-modeland name the list witharia-label. - Add one
OTabPaneper tab with a uniquevalueand alabel, and put the tab's content, such as a component, in its default slot. Mark a panelazyto mount its content only while it is selected, orlazy-onceto mount it the first time it is selected and keep it afterwards. - Bind
v-modelto a route query or path to keep the selection in the URL;OTabsnever uses a router.
Example
The rendered example and its source come from the same Vue single-file component.
Content of the overview tab.
Interactive playground
Change the selected tab and the variant, and disable a tab to see keyboard navigation skip it.
Content of the overview tab.
Content of the activity tab.
Content of the settings tab.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | undefined | Value of the selected tab. |
variant | OTabsVariant | "line" | Underlined tabs above the panel, or a bottom bar below it within thumb reach on mobile. |
ariaLabel | string | undefined | Accessible name of the tab list. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | Requests a new selected tab. |
Slots
| Slot | Props | Description |
|---|---|---|
default | none | OTabPane children, one per tab, in display order. |
Related types
| Type | Definition |
|---|---|
OTabsVariant | type OTabsVariant = "line" | "bottom-bar"; |
OTabPaneProps | interface OTabPaneProps { /** Unique value bound to the parent OTabsv-model when the tab is selected. */ value: string; /** Visible text and accessible name of the tab. */ label: string; /** Prevents selecting the tab and skips it during keyboard navigation. */ disabled?: boolean; /** Mounts the content only while the tab is selected, and unmounts it when another tab is selected. */ lazy?: boolean; /** Mounts the content the first time the tab is selected, then keeps it mounted and hidden. */ lazyOnce?: boolean; } |
OTabPaneSlots | interface OTabPaneSlots { /** Content of the panel; without it, the tab has no panel. */ default?: () => unknown; } |
CSS custom properties
| Custom property | Default | Description |
|---|---|---|
--o-tabs-track-background | var(--o-background-track) | Sets the background of the track that groups the tabs. |
--o-tabs-track-border | 1px solid var(--o-border-color-subtle) | Sets the border shorthand of the track that groups the tabs. |
--o-tabs-track-padding | var(--o-space-1) | Sets the space between the track edge and its tabs. |
--o-tabs-selected-background | var(--o-background-track-thumb) | Sets the background of the selected tab pill, raised above the track. |
--o-tabs-selected-border-color | var(--o-border-color-subtle) | Sets the border color of the selected tab pill. |
--o-tabs-tab-border-radius | var(--o-radius-round) | Sets the corner radius of each tab. |
--o-tabs-gap | var(--o-space-0-5) | Sets the space between tabs. |
--o-tabs-indicator-color | var(--o-border-primary-default) | Sets the accent color of the selected bottom-bar tab indicator. |
--o-tabs-indicator-thickness | 2px | Sets the thickness of the selected bottom-bar tab indicator. |
--o-tabs-tab-padding | var(--o-space-1) var(--o-space-4) | Sets the padding inside each tab. |
--o-tabs-bottom-bar-height | 3.5rem | Sets the minimum height of bottom-bar tabs, sized for touch. |
Override example
css
:root {
--o-tabs-selected-background: var(--o-background-primary-subtle);
--o-tabs-indicator-color: var(--o-border-secondary-default);
}Accessibility
- The list uses the
tablistrole and each tab is a native button with thetabrole andaria-selected. Name the list witharia-label. - Each pane with content renders a panel with the
tabpanelrole, labelled by its tab and referenced by the tab'saria-controls. Panels of unselected tabs arehidden. The visible panel is focusable so keyboard users can reach content without controls. - Only one tab is in the Tab sequence (roving
tabindex). Left and Right arrows move to the previous or next enabled tab and wrap; Home and End move to the first and last enabled tab. Arrow directions follow the writing direction in right-to-left layouts. - Selection follows focus: moving with the keyboard requests the newly focused tab.
- Disabled tabs use the native
disabledattribute and are skipped by keyboard navigation. - The selected tab is marked by its filled pill and border (the indicator bar in the bottom bar), not by color alone, and forced-colors mode draws that border with the system highlight color. Focus receives a visible outline.
Behavior and edge cases
- The selection is controlled:
OTabsemitsupdate:modelValueand renders the change only once the parent updatesmodelValue. Selecting the current or a disabled tab emits nothing. - When
modelValuematches no enabled tab, the first enabled tab receives keyboard focus and no panel is visible until a valid value is bound. - By default every pane's content stays mounted and unselected panels are hidden, so components keep their local state.
lazyunmounts the content when another tab is selected;lazy-oncedefers mounting until the first selection. The selected pane always renders, including during SSR. OTabsreads itsOTabPanechildren directly, including those produced byv-forandv-if; other children are ignored. Give each pane a uniquevalue.- A pane without a default slot renders no panel; use this when the content lives elsewhere, such as a
RouterView. - The
bottom-barvariant renders the panels before the list, stretches tabs to equal widths, sticks the list to the bottom of its scrolling container, and pads it for the device safe area. Give the parent a height to keep the bar at the bottom when the content is short. - The
linevariant scrolls the list horizontally when tabs overflow. - Native attributes and listeners are forwarded to the root element.