Tabs
ui-tabs holds a ui-tabs-list of ui-tabs-trigger buttons and one ui-tabs-content panel per trigger. A trigger
and a panel belong together when they share a value. Only the selected panel is visible. The others get the
hidden attribute. The tabs follow the ARIA tabs pattern with automatic activation, so moving focus with the arrow
keys also selects the tab.
The tabs fill the width of their container. The list itself is as wide as its triggers.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
<style>
.demo-narrow {
width: min(100%, calc(var(--spacing) * 88));
}
</style>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/card.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/field.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<div class="demo-narrow">
<ui-tabs value="account">
<ui-tabs-list>
<ui-tabs-trigger value="account">Account</ui-tabs-trigger>
<ui-tabs-trigger value="password">Password</ui-tabs-trigger>
</ui-tabs-list>
<ui-tabs-content value="account">
<ui-card>
<ui-card-header>
<ui-card-title>Account</ui-card-title>
<ui-card-description>Make changes to your account here.</ui-card-description>
</ui-card-header>
<ui-card-content>
<ui-field><ui-label for="tabs-name">Name</ui-label><ui-input id="tabs-name" value="Pedro Duarte"></ui-input></ui-field>
</ui-card-content>
</ui-card>
</ui-tabs-content>
<ui-tabs-content value="password">
<ui-card>
<ui-card-header>
<ui-card-title>Password</ui-card-title>
<ui-card-description>Change your password here.</ui-card-description>
</ui-card-header>
<ui-card-content>
<ui-field><ui-label for="tabs-password">Current password</ui-label><ui-input id="tabs-password" type="password"></ui-input></ui-field>
</ui-card-content>
</ui-card>
</ui-tabs-content>
</ui-tabs>
</div>
</body>
</html>Examples
Disabled tab
A disabled trigger is dimmed, cannot be selected and is skipped by the arrow keys.
Revenue is up 12% this month.
Visitors by country and device.
Reports are being generated.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
<style>
.demo-narrow {
width: min(100%, calc(var(--spacing) * 88));
}
.demo-text {
margin: 0;
font-size: var(--text-sm);
}
</style>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<div class="demo-narrow">
<ui-tabs value="overview">
<ui-tabs-list>
<ui-tabs-trigger value="overview">Overview</ui-tabs-trigger>
<ui-tabs-trigger value="analytics">Analytics</ui-tabs-trigger>
<ui-tabs-trigger value="reports" disabled>Reports</ui-tabs-trigger>
</ui-tabs-list>
<ui-tabs-content value="overview"><p class="demo-text">Revenue is up 12% this month.</p></ui-tabs-content>
<ui-tabs-content value="analytics"><p class="demo-text">Visitors by country and device.</p></ui-tabs-content>
<ui-tabs-content value="reports"><p class="demo-text">Reports are being generated.</p></ui-tabs-content>
</ui-tabs>
</div>
</body>
</html>If value is missing, unknown or names a disabled trigger, the first enabled trigger is selected.
Vertical
orientation="vertical" stacks the triggers in a column beside the panels. Up and Down arrows move between them.
General settings for the workspace.
Invite people and manage roles.
Plan, invoices and payment method.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
<style>
.demo-narrow {
width: min(100%, calc(var(--spacing) * 88));
}
.demo-text {
margin: 0;
font-size: var(--text-sm);
}
</style>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<div class="demo-narrow">
<ui-tabs orientation="vertical" value="general">
<ui-tabs-list>
<ui-tabs-trigger value="general">General</ui-tabs-trigger>
<ui-tabs-trigger value="members">Members</ui-tabs-trigger>
<ui-tabs-trigger value="billing">Billing</ui-tabs-trigger>
</ui-tabs-list>
<ui-tabs-content value="general"><p class="demo-text">General settings for the workspace.</p></ui-tabs-content>
<ui-tabs-content value="members"><p class="demo-text">Invite people and manage roles.</p></ui-tabs-content>
<ui-tabs-content value="billing"><p class="demo-text">Plan, invoices and payment method.</p></ui-tabs-content>
</ui-tabs>
</div>
</body>
</html>Icons in triggers
Icons inside a trigger are sized to calc(var(--spacing) * 4).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<ui-tabs value="preview">
<ui-tabs-list>
<ui-tabs-trigger value="preview">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7S2 12 2 12z" /><circle cx="12" cy="12" r="3" /></svg>
Preview
</ui-tabs-trigger>
<ui-tabs-trigger value="code">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m16 18 6-6-6-6M8 6l-6 6 6 6" /></svg>
Code
</ui-tabs-trigger>
</ui-tabs-list>
</ui-tabs>
</body>
</html>Tabs as a filter
A ui-tabs without ui-tabs-content works as a segmented control. Listen for change and read value. The
dashboard example uses this to switch the data of a chart.
Showing data for:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabs demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/tabs.html"></import-element>
<import-element from="./tabs-filter-demo.html"></import-element>
<tabs-filter-demo></tabs-filter-demo>
</body>
</html><custom-element name="tabs-filter-demo">
<script>
const filter = useState({ range: "7d" });
function changed(tabs) {
filter.range = tabs.value;
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 2);
justify-items: center;
}
p {
margin: 0;
font-size: var(--text-sm);
color: var(--muted-foreground);
}
</style>
<ui-tabs value="7d" onchange="changed(this)">
<ui-tabs-list>
<ui-tabs-trigger value="24h">24 hours</ui-tabs-trigger>
<ui-tabs-trigger value="7d">7 days</ui-tabs-trigger>
<ui-tabs-trigger value="30d">30 days</ui-tabs-trigger>
</ui-tabs-list>
</ui-tabs>
<p>Showing data for: <slot name="#filter.range"></slot></p>
</custom-element>API
Parts
| Element | Fills | Renders |
|---|---|---|
ui-tabs |
A flex container for the list and the panels. Fills its container's width. | |
ui-tabs-list |
the default slot of ui-tabs |
A role="tablist" pill around the triggers, with aria-orientation and arrow-key navigation. |
ui-tabs-trigger |
the default slot of ui-tabs-list |
A <button role="tab"> with aria-selected and a roving tabindex. |
ui-tabs-content |
the default slot of ui-tabs |
The panel, with role="tabpanel". Hidden unless its value is selected. |
Attributes
ui-tabs
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | The value of the selected trigger. If it is missing, unknown or names a disabled trigger, the first enabled trigger is selected. Updated when the user selects a tab. |
|
orientation |
"horizontal" | "vertical" |
"horizontal" |
Lays the list out as a row above the panels or a column beside them, and sets which arrow keys move between tabs. |
ui-tabs-trigger
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | Identifies the tab. Selecting it shows the ui-tabs-content with the same value. |
|
disabled |
boolean | false |
Disables the tab. |
ui-tabs-content
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | The value of the trigger that shows this panel. |
ui-tabs-list has no attributes.
Events
| Event | Element | Description |
|---|---|---|
change |
ui-tabs |
Fires when the user selects a different tab, by click or keyboard. Read the new value from the element. Bubbles. |
Child elements
ui-tabs watches its ui-tabs-list and ui-tabs-content children and the value and disabled attributes of
the triggers and panels, so tabs can be added, removed or renamed at any time.
CSS parts
| Part | Element | Description |
|---|---|---|
list |
ui-tabs-list |
The role="tablist" container. |
trigger |
ui-tabs-trigger |
The tab button. The selected one has aria-selected="true". |
Accessibility
- The list has
role="tablist"andaria-orientation. Each trigger renders a<button role="tab">witharia-selected. - Each
ui-tabs-contenthasrole="tabpanel"and is labelled by its trigger. - Only the selected tab is in the Tab order. Tab moves from it into the visible panel.
- Left and Right arrows (Up and Down when vertical) move to the previous or next tab and select it, wrapping at the ends. Home and End select the first and last tab. Disabled tabs are skipped.
- In a right-to-left context, such as inside Direction with
dir="rtl", Left and Right are swapped.