Tabs
A row of tabs that shows one panel at a time.
import '@usezeno/tokens/css';
import '@usezeno/core/web-components';
Examples
Basic
Tabs are set with the tabs property. Each child of the element is a panel, and panels are matched to tabs by their order. Set value to the tab that is open at the start.
Overview: 3 projects, 12 open tasks.
Activity: the last change was made today.
Settings: choose who can see this page.
<zeno-tabs value="overview">
<div><p>Overview: 3 projects, 12 open tasks.</p></div>
<div><p>Activity: the last change was made today.</p></div>
<div><p>Settings: choose who can see this page.</p></div>
</zeno-tabs>
document.querySelector('zeno-tabs').tabs = [
{ value: 'overview', label: 'Overview' },
{ value: 'activity', label: 'Activity' },
{ value: 'settings', label: 'Settings' },
];
Variants
Line: tasks for today.
Line: tasks for this week.
Pills: tasks for today.
Pills: tasks for this week.
Card: tasks for today.
Card: tasks for this week.
<zeno-tabs variant="line" value="day">
<div><p>Line: tasks for today.</p></div>
<div><p>Line: tasks for this week.</p></div>
</zeno-tabs>
<zeno-tabs variant="pills" value="day">
<div><p>Pills: tasks for today.</p></div>
<div><p>Pills: tasks for this week.</p></div>
</zeno-tabs>
<zeno-tabs variant="card" value="day">
<div><p>Card: tasks for today.</p></div>
<div><p>Card: tasks for this week.</p></div>
</zeno-tabs>
document.querySelectorAll('zeno-tabs').forEach(tabs => {
tabs.tabs = [
{ value: 'day', label: 'Day' },
{ value: 'week', label: 'Week' },
];
});
Sizes and full width
full-width makes the tabs share the whole width of the element.
Small tabs.
Shared files.
Large tabs.
Shared files.
Full width tabs.
Shared files.
<zeno-tabs size="sm" value="files">
<div><p>Small tabs.</p></div>
<div><p>Shared files.</p></div>
</zeno-tabs>
<zeno-tabs size="lg" value="files">
<div><p>Large tabs.</p></div>
<div><p>Shared files.</p></div>
</zeno-tabs>
<zeno-tabs full-width value="files">
<div><p>Full width tabs.</p></div>
<div><p>Shared files.</p></div>
</zeno-tabs>
document.querySelectorAll('zeno-tabs').forEach(tabs => {
tabs.tabs = [
{ value: 'files', label: 'My files' },
{ value: 'shared', label: 'Shared' },
];
});
Vertical
Profile: your name and photo.
Password: change your password.
Notifications: choose what we send you.
<zeno-tabs vertical value="profile">
<div><p>Profile: your name and photo.</p></div>
<div><p>Password: change your password.</p></div>
<div><p>Notifications: choose what we send you.</p></div>
</zeno-tabs>
document.querySelector('zeno-tabs').tabs = [
{ value: 'profile', label: 'Profile' },
{ value: 'password', label: 'Password' },
{ value: 'notifications', label: 'Notifications' },
];
Icons, badges, a disabled tab and the change event
A tab can have an icon, a badge and disabled. The change event carries the new value in event.detail.
Home
Favourites
Account
Admin
Selected: home
<zeno-tabs value="home">
<div><p>Home</p></div>
<div><p>Favourites</p></div>
<div><p>Account</p></div>
<div><p>Admin</p></div>
</zeno-tabs>
<p data-result>Selected: home</p>
const tabs = document.querySelector('zeno-tabs');
tabs.tabs = [
{ value: 'home', label: 'Home', icon: 'home' },
{ value: 'favourites', label: 'Favourites', icon: 'star', badge: 4 },
{ value: 'account', label: 'Account', icon: 'user' },
{ value: 'admin', label: 'Admin', icon: 'settings', disabled: true },
];
tabs.addEventListener('change', event => {
document.querySelector('[data-result]').textContent = 'Selected: ' + event.detail.value;
});
Attributes and properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
tabs | tabs | TabItem[] | [] | |
value | value | string | '' | |
variant | variantreflects | 'line' | 'pills' | 'card' | 'line' | |
size | sizereflects | 'sm' | 'md' | 'lg' | 'md' | |
vertical | verticalreflects | boolean | false | |
fullWidth | full-widthreflects | boolean | false | |
testId | string | Test ID for automated testing From ZenoElement. |
Methods
| Method | Description |
|---|---|
selectTab(value: string): void | Select a tab |
focus(): void | Focus the component or its first focusable element From ZenoElement. |
blur(): void | Blur the component or its focused element From ZenoElement. |
Events
| Event | Detail | Description |
|---|---|---|
change | { value: string; tab: TabItem } | A different tab was selected. |
Slots
| Slot | Description |
|---|---|
| Default | The panels, one per tab. |
CSS parts
| Part |
|---|
badge |
base |
icon |
label |
list |
panels |
tab |
CSS custom properties
This element documents no custom properties of its own. Style it through its parts and through the tokens it reads.
The element's own source reads no tokens.
Types
interface TabItem {
value: string;
label: string;
icon?: string;
disabled?: boolean;
badge?: string | number;
}
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the first tab that is not disabled, then to each next tab, then into the page after the tabs. |
| ArrowRightArrowDown | Moves focus to the next tab that is not disabled and selects it. From the last tab it goes to the first. |
| ArrowLeftArrowUp | Moves focus to the previous tab that is not disabled and selects it. From the first tab it goes to the last. |
| HomeEnd | Moves focus to the first or last tab that is not disabled and selects it. |
| EnterSpace | Selects the focused tab. |
Forms
This element is not a form control. It adds nothing to a form's data.
Notes
- The row of tabs is a
<div role="tablist">. Each tab is a native<button>withrole="tab"andaria-selected. A disabled tab has the nativedisabledattribute. - Each child of the element gets
role="tabpanel". Every panel except the selected one gets thehiddenattribute. - Selection follows focus: an arrow key selects the tab it moves to.
- With
prefers-reduced-motion: reducethe tab transitions are turned off.
Tests and known gaps
- No tests of its own. The only check is that the element registers and renders (
smoke.test.ts). - Not checked with axe-core.
- Has no Storybook story.
13 of the 19 properties, methods, events, slots and parts above have no description in the source
- the property
tabs - the property
value - the property
variant - the property
size - the property
vertical - the property
fullWidth - the part
badge - the part
base - the part
icon - the part
label - the part
list - the part
panels - the part
tab
Known gaps:
- Without
value, no tab is selected and every panel is hidden. The code picks the first tab, but the next step sets the active tab back to the emptyvalue. Always setvalue. - A
valuethat matches no tab also leaves every panel hidden and no tab selected. - Every enabled tab is a tab stop. The tab list does not use a roving
tabindex, so the Tab key goes through all tabs before it reaches the panel. - Each tab has
aria-controls="panel-<value>", but no panel is given thatid. - Each panel has
aria-labelledby="tab-<value>". The tab with thatidis inside the shadow root, so the reference does not resolve and the panel has no name. - A
verticaltab list has noaria-orientation="vertical". - The tab list has no accessible name, and there is no attribute to give it one.
- In the
linevariant the line under the selected tab is not painted. The tab list clips it, so the selected tab is shown by text colour only. - The fade-in style for panels is never used, because slotted panels do not get the
tabs__panelclass.