Tabs
<gui-tabs> shows one panel of content at a time, under a strip of tabs. You write the tabs and
the panels; the element adds the behavior: the roles and ARIA links, the keyboard, and which panel
shows. It never creates or removes your content.
import { GuiCheckbox, GuiTab, GuiTabList, GuiTabPanel, GuiTabs, GuiTextinput,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TabsBasic() { return ( <GuiTabs> <GuiTabList aria-label="Profile"> <GuiTab panel="personal">Personal</GuiTab> <GuiTab panel="address">Address</GuiTab> <GuiTab panel="preferences">Preferences</GuiTab> </GuiTabList> <GuiTabPanel name="personal"> <GuiTextinput label="Full name" value="Ada Lovelace" /> </GuiTabPanel> <GuiTabPanel name="address"> <GuiTextinput label="City" value="London" /> </GuiTabPanel> <GuiTabPanel name="preferences"> <GuiCheckbox label="Email me a weekly summary" /> </GuiTabPanel> </GuiTabs> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/textinput';
@Component({ selector: 'app-tabs-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs> <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> <gui-tab panel="preferences">Preferences</gui-tab> </gui-tab-list> <gui-tab-panel name="personal"> <gui-textinput label="Full name" value="Ada Lovelace"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="address"> <gui-textinput label="City" value="London"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="preferences"> <gui-checkbox label="Email me a weekly summary"></gui-checkbox> </gui-tab-panel> </gui-tabs> `,})export class TabsBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/checkbox';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';</script>
<template> <gui-tabs> <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> <gui-tab panel="preferences">Preferences</gui-tab> </gui-tab-list> <gui-tab-panel name="personal"> <gui-textinput label="Full name" value="Ada Lovelace"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="address"> <gui-textinput label="City" value="London"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="preferences"> <gui-checkbox label="Email me a weekly summary"></gui-checkbox> </gui-tab-panel> </gui-tabs></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/checkbox';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/textinput';import '@golemui/gui-components/index.css';
@customElement('tabs-basic')export class TabsBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-tabs> <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> <gui-tab panel="preferences">Preferences</gui-tab> </gui-tab-list> <gui-tab-panel name="personal"> <gui-textinput label="Full name" value="Ada Lovelace"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="address"> <gui-textinput label="City" value="London"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="preferences"> <gui-checkbox label="Email me a weekly summary"></gui-checkbox> </gui-tab-panel> </gui-tabs> `; }}<gui-tabs> <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> <gui-tab panel="preferences">Preferences</gui-tab> </gui-tab-list> <gui-tab-panel name="personal"> <gui-textinput label="Full name" value="Ada Lovelace"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="address"> <gui-textinput label="City" value="London"></gui-textinput> </gui-tab-panel> <gui-tab-panel name="preferences"> <gui-checkbox label="Email me a weekly summary"></gui-checkbox> </gui-tab-panel></gui-tabs>The element’s entry point is @golemui/gui-components/tabs, which registers <gui-tabs> and its
parts, <gui-tab-list>, <gui-tab> and <gui-tab-panel>, and nothing else. To install the
package and load the stylesheet, see Installation.
The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.
import { GuiTabs, GuiTabList, GuiTab, GuiTabPanel } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TabsExample() { return ( <GuiTabs active="address" > <GuiTabList aria-label="Profile"> <GuiTab panel="personal">Personal</GuiTab> <GuiTab panel="address">Address</GuiTab> </GuiTabList> <GuiTabPanel name="personal">…</GuiTabPanel> <GuiTabPanel name="address">…</GuiTabPanel> </GuiTabs> );}Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, and the stylesheet to styles in angular.json.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/tabs';
@Component({ selector: 'app-tabs-example', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs active="address" > <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> </gui-tab-list> <gui-tab-panel name="personal">…</gui-tab-panel> <gui-tab-panel name="address">…</gui-tab-panel> </gui-tabs> `,})export class TabsExampleComponent {}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';</script>
<template> <gui-tabs active="address" > <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> </gui-tab-list> <gui-tab-panel name="personal">…</gui-tab-panel> <gui-tab-panel name="address">…</gui-tab-panel> </gui-tabs></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
@customElement('tabs-example')export class TabsExample extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-tabs active="address" > <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> </gui-tab-list> <gui-tab-panel name="personal">…</gui-tab-panel> <gui-tab-panel name="address">…</gui-tab-panel> </gui-tabs> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-tabs active="address"> <gui-tab-list aria-label="Profile"> <gui-tab panel="personal">Personal</gui-tab> <gui-tab panel="address">Address</gui-tab> </gui-tab-list> <gui-tab-panel name="personal">…</gui-tab-panel> <gui-tab-panel name="address">…</gui-tab-panel></gui-tabs>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';Each gui-tab points at its panel with panel, the name of a gui-tab-panel. Your framework
renders the parts like any other markup, so loops and conditions work as usual: the element
follows when you add, remove or rename a tab.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
active | string | – | The panel of the selected tab. Defaults to the first tab. |
Events
Every event bubbles and is composed, so you can listen on any ancestor.
| Event | Description |
|---|---|
gui-tab-change | The user selected a tab. detail.value is its panel. Cancel it to keep the current tab. |
Examples
Section titled “Examples”The selected tab
Section titled “The selected tab”The first tab is selected unless you set active to the panel of another tab. An active that
matches no tab selects the first one too.
import { GuiTab, GuiTabList, GuiTabPanel, GuiTabs,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TabsActive() { return ( <GuiTabs active="annual"> <GuiTabList aria-label="Billing period"> <GuiTab panel="monthly">Monthly</GuiTab> <GuiTab panel="annual">Annual</GuiTab> </GuiTabList> <GuiTabPanel name="monthly"> Team plan: $12 per seat, billed every month. </GuiTabPanel> <GuiTabPanel name="annual"> Team plan: $10 per seat, billed once a year. </GuiTabPanel> </GuiTabs> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';
@Component({ selector: 'app-tabs-active', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs active="annual"> <gui-tab-list aria-label="Billing period"> <gui-tab panel="monthly">Monthly</gui-tab> <gui-tab panel="annual">Annual</gui-tab> </gui-tab-list> <gui-tab-panel name="monthly"> Team plan: $12 per seat, billed every month. </gui-tab-panel> <gui-tab-panel name="annual"> Team plan: $10 per seat, billed once a year. </gui-tab-panel> </gui-tabs> `,})export class TabsActiveComponent {}<script setup lang="ts">import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';</script>
<template> <gui-tabs active="annual"> <gui-tab-list aria-label="Billing period"> <gui-tab panel="monthly">Monthly</gui-tab> <gui-tab panel="annual">Annual</gui-tab> </gui-tab-list> <gui-tab-panel name="monthly"> Team plan: $12 per seat, billed every month. </gui-tab-panel> <gui-tab-panel name="annual"> Team plan: $10 per seat, billed once a year. </gui-tab-panel> </gui-tabs></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
@customElement('tabs-active')export class TabsActive extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-tabs active="annual"> <gui-tab-list aria-label="Billing period"> <gui-tab panel="monthly">Monthly</gui-tab> <gui-tab panel="annual">Annual</gui-tab> </gui-tab-list> <gui-tab-panel name="monthly" >Team plan: $12 per seat, billed every month.</gui-tab-panel > <gui-tab-panel name="annual" >Team plan: $10 per seat, billed once a year.</gui-tab-panel > </gui-tabs> `; }}<gui-tabs active="annual"> <gui-tab-list aria-label="Billing period"> <gui-tab panel="monthly">Monthly</gui-tab> <gui-tab panel="annual">Annual</gui-tab> </gui-tab-list> <gui-tab-panel name="monthly">Team plan: $12 per seat, billed every month.</gui-tab-panel> <gui-tab-panel name="annual">Team plan: $10 per seat, billed once a year.</gui-tab-panel></gui-tabs>Responding to a change
Section titled “Responding to a change”When the user selects a tab, the element fires gui-tab-change with the tab’s panel in
detail.value, then shows the panel. The event is cancelable: call preventDefault() to keep the
current tab, for example while a panel has unsaved changes. Try the Settings tab below. With the
arrow keys, focus still moves to the tab the user reached, while the selected tab stays.
import { useState } from 'react';import { GuiTab, GuiTabList, GuiTabPanel, GuiTabs,} from '@golemui/gui-components/react';import type { GuiTabChangeEventDetail } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
export function TabsEvents() { const [status, setStatus] = useState('');
const onTabChange = (event: CustomEvent<GuiTabChangeEventDetail>) => { if (event.detail.value === 'settings') { // Keeps the current tab. event.preventDefault(); setStatus('Ask Ada, the project owner, for access to the settings.'); return; } setStatus(`Showing ${event.detail.value}.`); };
return ( <> <GuiTabs id="project-tabs" onGuiTabChange={onTabChange}> <GuiTabList aria-label="Project"> <GuiTab panel="overview">Overview</GuiTab> <GuiTab panel="activity">Activity</GuiTab> <GuiTab panel="settings">Settings</GuiTab> </GuiTabList> <GuiTabPanel name="overview"> Analytical Engine: 12 open tasks, due 10 December. </GuiTabPanel> <GuiTabPanel name="activity"> Charles Babbage closed "Punch card reader" an hour ago. </GuiTabPanel> <GuiTabPanel name="settings"> Only the project owner can change these settings. </GuiTabPanel> </GuiTabs> <p id="project-tabs-status" role="status"> {status} </p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiTabsEvents } from '@golemui/gui-components';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';
type TabsEvent<K extends keyof typeof GuiTabsEvents> = GuiEventMap< typeof GuiTabsEvents>[K];
@Component({ selector: 'app-tabs-events', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs id="project-tabs" (gui-tab-change)="onTabChange($event)"> <gui-tab-list aria-label="Project"> <gui-tab panel="overview">Overview</gui-tab> <gui-tab panel="activity">Activity</gui-tab> <gui-tab panel="settings">Settings</gui-tab> </gui-tab-list> <gui-tab-panel name="overview"> Analytical Engine: 12 open tasks, due 10 December. </gui-tab-panel> <gui-tab-panel name="activity"> Charles Babbage closed "Punch card reader" an hour ago. </gui-tab-panel> <gui-tab-panel name="settings"> Only the project owner can change these settings. </gui-tab-panel> </gui-tabs> <p id="project-tabs-status" role="status">{{ status() }}</p> `,})export class TabsEventsComponent { status = signal('');
onTabChange(event: Event) { const { value } = (event as TabsEvent<'gui-tab-change'>).detail; if (value === 'settings') { // Keeps the current tab. event.preventDefault(); this.status.set( 'Ask Ada, the project owner, for access to the settings.', ); return; } this.status.set(`Showing ${value}.`); }}<script setup lang="ts">import { ref } from 'vue';import type { GuiTabChangeEventDetail } from '@golemui/gui-components';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
const status = ref('');
function onTabChange(event: CustomEvent<GuiTabChangeEventDetail>) { if (event.detail.value === 'settings') { // Keeps the current tab. event.preventDefault(); status.value = 'Ask Ada, the project owner, for access to the settings.'; return; } status.value = `Showing ${event.detail.value}.`;}</script>
<template> <gui-tabs id="project-tabs" @gui-tab-change="onTabChange"> <gui-tab-list aria-label="Project"> <gui-tab panel="overview">Overview</gui-tab> <gui-tab panel="activity">Activity</gui-tab> <gui-tab panel="settings">Settings</gui-tab> </gui-tab-list> <gui-tab-panel name="overview"> Analytical Engine: 12 open tasks, due 10 December. </gui-tab-panel> <gui-tab-panel name="activity"> Charles Babbage closed "Punch card reader" an hour ago. </gui-tab-panel> <gui-tab-panel name="settings"> Only the project owner can change these settings. </gui-tab-panel> </gui-tabs> <p id="project-tabs-status" role="status">{{ status }}</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiTabsEvents } from '@golemui/gui-components';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
type TabsEvent<K extends keyof typeof GuiTabsEvents> = GuiEventMap< typeof GuiTabsEvents>[K];
@customElement('tabs-events')export class TabsEvents extends LitElement { @state() private status = '';
protected override createRenderRoot() { return this; }
private onTabChange(event: TabsEvent<'gui-tab-change'>) { if (event.detail.value === 'settings') { // Keeps the current tab. event.preventDefault(); this.status = 'Ask Ada, the project owner, for access to the settings.'; return; } this.status = `Showing ${event.detail.value}.`; }
override render() { return html` <gui-tabs id="project-tabs" @gui-tab-change=${this.onTabChange}> <gui-tab-list aria-label="Project"> <gui-tab panel="overview">Overview</gui-tab> <gui-tab panel="activity">Activity</gui-tab> <gui-tab panel="settings">Settings</gui-tab> </gui-tab-list> <gui-tab-panel name="overview"> Analytical Engine: 12 open tasks, due 10 December. </gui-tab-panel> <gui-tab-panel name="activity"> Charles Babbage closed "Punch card reader" an hour ago. </gui-tab-panel> <gui-tab-panel name="settings"> Only the project owner can change these settings. </gui-tab-panel> </gui-tabs> <p id="project-tabs-status" role="status">${this.status}</p> `; }}<gui-tabs id="project-tabs"> <gui-tab-list aria-label="Project"> <gui-tab panel="overview">Overview</gui-tab> <gui-tab panel="activity">Activity</gui-tab> <gui-tab panel="settings">Settings</gui-tab> </gui-tab-list> <gui-tab-panel name="overview">Analytical Engine: 12 open tasks, due 10 December.</gui-tab-panel> <gui-tab-panel name="activity">Charles Babbage closed "Punch card reader" an hour ago.</gui-tab-panel> <gui-tab-panel name="settings">Only the project owner can change these settings.</gui-tab-panel></gui-tabs><p id="project-tabs-status" role="status"></p>
<script type="module"> const tabs = document.querySelector('#project-tabs'); const status = document.querySelector('#project-tabs-status');
tabs.addEventListener('gui-tab-change', (event) => { if (event.detail.value === 'settings') { // Keeps the current tab. event.preventDefault(); status.textContent = 'Ask Ada, the project owner, for access to the settings.'; return; } status.textContent = `Showing ${event.detail.value}.`; });</script>Selecting a tab from your code
Section titled “Selecting a tab from your code”Set active to select a tab yourself, such as from a “Next step” button. Setting it fires no
gui-tab-change. When the user selects a tab, active holds its panel, so it is always the
selected tab.
import { useState } from 'react';import { GuiButton, GuiTab, GuiTabList, GuiTabPanel, GuiTabs,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
const steps = ['cart', 'shipping', 'payment'];
export function TabsControlled() { const [active, setActive] = useState<string>();
const next = () => setActive(steps[(steps.indexOf(active ?? 'cart') + 1) % steps.length]);
return ( <> <GuiTabs id="checkout-tabs" active={active} onGuiTabChange={(event) => setActive(event.detail.value)} > <GuiTabList aria-label="Checkout"> <GuiTab panel="cart">1. Cart</GuiTab> <GuiTab panel="shipping">2. Shipping</GuiTab> <GuiTab panel="payment">3. Payment</GuiTab> </GuiTabList> <GuiTabPanel name="cart"> Two items: a notebook and a fountain pen. </GuiTabPanel> <GuiTabPanel name="shipping"> Ship to 12 Marylebone Road, London. </GuiTabPanel> <GuiTabPanel name="payment"> Pay with the card ending in 4242. </GuiTabPanel> </GuiTabs> <GuiButton id="checkout-next" label="Next step" onClick={next} /> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiTabsEvents } from '@golemui/gui-components';import '@golemui/gui-components/button';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';
type TabsEvent<K extends keyof typeof GuiTabsEvents> = GuiEventMap< typeof GuiTabsEvents>[K];
@Component({ selector: 'app-tabs-controlled', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs id="checkout-tabs" [active]="active()" (gui-tab-change)="onTabChange($event)" > <gui-tab-list aria-label="Checkout"> <gui-tab panel="cart">1. Cart</gui-tab> <gui-tab panel="shipping">2. Shipping</gui-tab> <gui-tab panel="payment">3. Payment</gui-tab> </gui-tab-list> <gui-tab-panel name="cart"> Two items: a notebook and a fountain pen. </gui-tab-panel> <gui-tab-panel name="shipping"> Ship to 12 Marylebone Road, London. </gui-tab-panel> <gui-tab-panel name="payment"> Pay with the card ending in 4242. </gui-tab-panel> </gui-tabs> <gui-button id="checkout-next" label="Next step" (click)="next()" ></gui-button> `,})export class TabsControlledComponent { steps = ['cart', 'shipping', 'payment']; active = signal<string | undefined>(undefined);
onTabChange(event: Event) { this.active.set((event as TabsEvent<'gui-tab-change'>).detail.value); }
next() { const current = this.steps.indexOf(this.active() ?? 'cart'); this.active.set(this.steps[(current + 1) % this.steps.length]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
const steps = ['cart', 'shipping', 'payment'];const active = ref<string>();
function next() { const current = steps.indexOf(active.value ?? 'cart'); active.value = steps[(current + 1) % steps.length];}</script>
<template> <gui-tabs id="checkout-tabs" :active="active" @gui-tab-change="active = $event.detail.value" > <gui-tab-list aria-label="Checkout"> <gui-tab panel="cart">1. Cart</gui-tab> <gui-tab panel="shipping">2. Shipping</gui-tab> <gui-tab panel="payment">3. Payment</gui-tab> </gui-tab-list> <gui-tab-panel name="cart"> Two items: a notebook and a fountain pen. </gui-tab-panel> <gui-tab-panel name="shipping"> Ship to 12 Marylebone Road, London. </gui-tab-panel> <gui-tab-panel name="payment" >Pay with the card ending in 4242.</gui-tab-panel > </gui-tabs> <gui-button id="checkout-next" label="Next step" @click="next"></gui-button></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiTabsEvents } from '@golemui/gui-components';import '@golemui/gui-components/button';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
type TabsEvent<K extends keyof typeof GuiTabsEvents> = GuiEventMap< typeof GuiTabsEvents>[K];
const steps = ['cart', 'shipping', 'payment'];
@customElement('tabs-controlled')export class TabsControlled extends LitElement { @state() private active?: string;
protected override createRenderRoot() { return this; }
private onTabChange(event: TabsEvent<'gui-tab-change'>) { this.active = event.detail.value; }
private next() { const current = steps.indexOf(this.active ?? 'cart'); this.active = steps[(current + 1) % steps.length]; }
override render() { return html` <gui-tabs id="checkout-tabs" .active=${this.active} @gui-tab-change=${this.onTabChange} > <gui-tab-list aria-label="Checkout"> <gui-tab panel="cart">1. Cart</gui-tab> <gui-tab panel="shipping">2. Shipping</gui-tab> <gui-tab panel="payment">3. Payment</gui-tab> </gui-tab-list> <gui-tab-panel name="cart"> Two items: a notebook and a fountain pen. </gui-tab-panel> <gui-tab-panel name="shipping"> Ship to 12 Marylebone Road, London. </gui-tab-panel> <gui-tab-panel name="payment"> Pay with the card ending in 4242. </gui-tab-panel> </gui-tabs> <gui-button id="checkout-next" label="Next step" @click=${this.next} ></gui-button> `; }}<gui-tabs id="checkout-tabs"> <gui-tab-list aria-label="Checkout"> <gui-tab panel="cart">1. Cart</gui-tab> <gui-tab panel="shipping">2. Shipping</gui-tab> <gui-tab panel="payment">3. Payment</gui-tab> </gui-tab-list> <gui-tab-panel name="cart">Two items: a notebook and a fountain pen.</gui-tab-panel> <gui-tab-panel name="shipping">Ship to 12 Marylebone Road, London.</gui-tab-panel> <gui-tab-panel name="payment">Pay with the card ending in 4242.</gui-tab-panel></gui-tabs><gui-button id="checkout-next" label="Next step"></gui-button>
<script type="module"> const tabs = document.querySelector('#checkout-tabs'); const steps = ['cart', 'shipping', 'payment'];
document.querySelector('#checkout-next').addEventListener('click', () => { const current = steps.indexOf(tabs.active ?? 'cart'); tabs.active = steps[(current + 1) % steps.length]; });</script>Many tabs
Section titled “Many tabs”When the tabs don’t fit, the strip scrolls sideways, and a shadow marks each side with tabs out of view. The strip scrolls to the selected tab when it first shows.
import { GuiTab, GuiTabList, GuiTabPanel, GuiTabs,} from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TabsOverflow() { return ( <GuiTabs active="oct"> <GuiTabList aria-label="Month"> <GuiTab panel="jan">January</GuiTab> <GuiTab panel="feb">February</GuiTab> <GuiTab panel="mar">March</GuiTab> <GuiTab panel="apr">April</GuiTab> <GuiTab panel="may">May</GuiTab> <GuiTab panel="jun">June</GuiTab> <GuiTab panel="jul">July</GuiTab> <GuiTab panel="aug">August</GuiTab> <GuiTab panel="sep">September</GuiTab> <GuiTab panel="oct">October</GuiTab> <GuiTab panel="nov">November</GuiTab> <GuiTab panel="dec">December</GuiTab> </GuiTabList> <GuiTabPanel name="jan">January: 31 orders.</GuiTabPanel> <GuiTabPanel name="feb">February: 28 orders.</GuiTabPanel> <GuiTabPanel name="mar">March: 40 orders.</GuiTabPanel> <GuiTabPanel name="apr">April: 37 orders.</GuiTabPanel> <GuiTabPanel name="may">May: 45 orders.</GuiTabPanel> <GuiTabPanel name="jun">June: 52 orders.</GuiTabPanel> <GuiTabPanel name="jul">July: 61 orders.</GuiTabPanel> <GuiTabPanel name="aug">August: 58 orders.</GuiTabPanel> <GuiTabPanel name="sep">September: 49 orders.</GuiTabPanel> <GuiTabPanel name="oct">October: 66 orders.</GuiTabPanel> <GuiTabPanel name="nov">November: 70 orders.</GuiTabPanel> <GuiTabPanel name="dec">December: 84 orders.</GuiTabPanel> </GuiTabs> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';
@Component({ selector: 'app-tabs-overflow', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-tabs active="oct"> <gui-tab-list aria-label="Month"> <gui-tab panel="jan">January</gui-tab> <gui-tab panel="feb">February</gui-tab> <gui-tab panel="mar">March</gui-tab> <gui-tab panel="apr">April</gui-tab> <gui-tab panel="may">May</gui-tab> <gui-tab panel="jun">June</gui-tab> <gui-tab panel="jul">July</gui-tab> <gui-tab panel="aug">August</gui-tab> <gui-tab panel="sep">September</gui-tab> <gui-tab panel="oct">October</gui-tab> <gui-tab panel="nov">November</gui-tab> <gui-tab panel="dec">December</gui-tab> </gui-tab-list> <gui-tab-panel name="jan">January: 31 orders.</gui-tab-panel> <gui-tab-panel name="feb">February: 28 orders.</gui-tab-panel> <gui-tab-panel name="mar">March: 40 orders.</gui-tab-panel> <gui-tab-panel name="apr">April: 37 orders.</gui-tab-panel> <gui-tab-panel name="may">May: 45 orders.</gui-tab-panel> <gui-tab-panel name="jun">June: 52 orders.</gui-tab-panel> <gui-tab-panel name="jul">July: 61 orders.</gui-tab-panel> <gui-tab-panel name="aug">August: 58 orders.</gui-tab-panel> <gui-tab-panel name="sep">September: 49 orders.</gui-tab-panel> <gui-tab-panel name="oct">October: 66 orders.</gui-tab-panel> <gui-tab-panel name="nov">November: 70 orders.</gui-tab-panel> <gui-tab-panel name="dec">December: 84 orders.</gui-tab-panel> </gui-tabs> `,})export class TabsOverflowComponent {}<script setup lang="ts">import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';</script>
<template> <gui-tabs active="oct"> <gui-tab-list aria-label="Month"> <gui-tab panel="jan">January</gui-tab> <gui-tab panel="feb">February</gui-tab> <gui-tab panel="mar">March</gui-tab> <gui-tab panel="apr">April</gui-tab> <gui-tab panel="may">May</gui-tab> <gui-tab panel="jun">June</gui-tab> <gui-tab panel="jul">July</gui-tab> <gui-tab panel="aug">August</gui-tab> <gui-tab panel="sep">September</gui-tab> <gui-tab panel="oct">October</gui-tab> <gui-tab panel="nov">November</gui-tab> <gui-tab panel="dec">December</gui-tab> </gui-tab-list> <gui-tab-panel name="jan">January: 31 orders.</gui-tab-panel> <gui-tab-panel name="feb">February: 28 orders.</gui-tab-panel> <gui-tab-panel name="mar">March: 40 orders.</gui-tab-panel> <gui-tab-panel name="apr">April: 37 orders.</gui-tab-panel> <gui-tab-panel name="may">May: 45 orders.</gui-tab-panel> <gui-tab-panel name="jun">June: 52 orders.</gui-tab-panel> <gui-tab-panel name="jul">July: 61 orders.</gui-tab-panel> <gui-tab-panel name="aug">August: 58 orders.</gui-tab-panel> <gui-tab-panel name="sep">September: 49 orders.</gui-tab-panel> <gui-tab-panel name="oct">October: 66 orders.</gui-tab-panel> <gui-tab-panel name="nov">November: 70 orders.</gui-tab-panel> <gui-tab-panel name="dec">December: 84 orders.</gui-tab-panel> </gui-tabs></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/tab';import '@golemui/gui-components/tab-list';import '@golemui/gui-components/tab-panel';import '@golemui/gui-components/tabs';import '@golemui/gui-components/index.css';
@customElement('tabs-overflow')export class TabsOverflow extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-tabs active="oct"> <gui-tab-list aria-label="Month"> <gui-tab panel="jan">January</gui-tab> <gui-tab panel="feb">February</gui-tab> <gui-tab panel="mar">March</gui-tab> <gui-tab panel="apr">April</gui-tab> <gui-tab panel="may">May</gui-tab> <gui-tab panel="jun">June</gui-tab> <gui-tab panel="jul">July</gui-tab> <gui-tab panel="aug">August</gui-tab> <gui-tab panel="sep">September</gui-tab> <gui-tab panel="oct">October</gui-tab> <gui-tab panel="nov">November</gui-tab> <gui-tab panel="dec">December</gui-tab> </gui-tab-list> <gui-tab-panel name="jan">January: 31 orders.</gui-tab-panel> <gui-tab-panel name="feb">February: 28 orders.</gui-tab-panel> <gui-tab-panel name="mar">March: 40 orders.</gui-tab-panel> <gui-tab-panel name="apr">April: 37 orders.</gui-tab-panel> <gui-tab-panel name="may">May: 45 orders.</gui-tab-panel> <gui-tab-panel name="jun">June: 52 orders.</gui-tab-panel> <gui-tab-panel name="jul">July: 61 orders.</gui-tab-panel> <gui-tab-panel name="aug">August: 58 orders.</gui-tab-panel> <gui-tab-panel name="sep">September: 49 orders.</gui-tab-panel> <gui-tab-panel name="oct">October: 66 orders.</gui-tab-panel> <gui-tab-panel name="nov">November: 70 orders.</gui-tab-panel> <gui-tab-panel name="dec">December: 84 orders.</gui-tab-panel> </gui-tabs> `; }}<gui-tabs active="oct"> <gui-tab-list aria-label="Month"> <gui-tab panel="jan">January</gui-tab> <gui-tab panel="feb">February</gui-tab> <gui-tab panel="mar">March</gui-tab> <gui-tab panel="apr">April</gui-tab> <gui-tab panel="may">May</gui-tab> <gui-tab panel="jun">June</gui-tab> <gui-tab panel="jul">July</gui-tab> <gui-tab panel="aug">August</gui-tab> <gui-tab panel="sep">September</gui-tab> <gui-tab panel="oct">October</gui-tab> <gui-tab panel="nov">November</gui-tab> <gui-tab panel="dec">December</gui-tab> </gui-tab-list> <gui-tab-panel name="jan">January: 31 orders.</gui-tab-panel> <gui-tab-panel name="feb">February: 28 orders.</gui-tab-panel> <gui-tab-panel name="mar">March: 40 orders.</gui-tab-panel> <gui-tab-panel name="apr">April: 37 orders.</gui-tab-panel> <gui-tab-panel name="may">May: 45 orders.</gui-tab-panel> <gui-tab-panel name="jun">June: 52 orders.</gui-tab-panel> <gui-tab-panel name="jul">July: 61 orders.</gui-tab-panel> <gui-tab-panel name="aug">August: 58 orders.</gui-tab-panel> <gui-tab-panel name="sep">September: 49 orders.</gui-tab-panel> <gui-tab-panel name="oct">October: 66 orders.</gui-tab-panel> <gui-tab-panel name="nov">November: 70 orders.</gui-tab-panel> <gui-tab-panel name="dec">December: 84 orders.</gui-tab-panel></gui-tabs>Server rendering
Section titled “Server rendering”gui-tabs hides the inactive panels when it upgrades in the browser. Until then, in a page
rendered on the server, every panel shows. Put hidden on the inactive panels in your markup to
keep them out of sight from the start: the element manages the attribute once it upgrades. See
Server rendering.
gui-tab-list
Section titled “gui-tab-list”<gui-tab-list> is the strip of tabs. It renders nothing of its own: it holds your gui-tabs,
gets the tablist role and scrolls when the tabs don’t fit. Name it with aria-label when the
page has more than one set of tabs. It only works inside a gui-tabs.
gui-tab
Section titled “gui-tab”<gui-tab> is a tab. Its content is its label: text, or text with an icon. panel names the
panel it shows. It only works inside a gui-tabs, which gives it its id, its state and its
keyboard.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
panel | string | – | The name of the gui-tab-panel the tab shows. |
gui-tab-panel
Section titled “gui-tab-panel”<gui-tab-panel> holds the content of a tab. gui-tabs only hides it while its tab isn’t
selected: its content stays in the page, so the fields in a hidden panel keep their values. To
render a panel’s content only while it shows, leave it out yourself when its tab isn’t selected,
from your framework’s state.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | string | – | The name the panel's gui-tab points to with its panel. |
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-tabs> and its parts render nothing of their own: the anatomy is your markup, with the
roles, ids and states the element adds:
<gui-tabs> <gui-tab-list aria-label="Profile" role="tablist"> <gui-tab panel="personal" role="tab" id="gui-tab-3" aria-selected="true" aria-controls="gui-tab-panel-1" tabindex="0" >Personal</gui-tab > <gui-tab panel="address" role="tab" id="gui-tab-4" aria-selected="false" aria-controls="gui-tab-panel-2" tabindex="-1" >Address</gui-tab > </gui-tab-list> <gui-tab-panel name="personal" role="tabpanel" id="gui-tab-panel-1" aria-labelledby="gui-tab-3" tabindex="0" > … </gui-tab-panel> <gui-tab-panel name="address" role="tabpanel" id="gui-tab-panel-2" aria-labelledby="gui-tab-4" tabindex="0" hidden > … </gui-tab-panel></gui-tabs>The parts have no classes. Style them by tag, and by the states the element sets:
| Selector | Part |
|---|---|
gui-tabs | The whole set. |
gui-tabs[overflow-start] | The set while tabs are scrolled out of view at the start: shows the shadow there. |
gui-tabs[overflow-end] | The set while tabs are out of view at the end: shows the shadow there. |
gui-tab-list | The strip of tabs. |
gui-tab | A tab. |
gui-tab[aria-selected='true'] | The selected tab. |
gui-tab-panel | A panel. |
gui-tab-panel[hidden] | A panel whose tab isn’t selected. |
The ids are generated, such as gui-tab-3, unless your parts have ids of their own, which the
element keeps. The overflow-start and overflow-end attributes appear on gui-tabs only while
tabs are out of view.
Tokens
Section titled “Tokens”The tabs read these design tokens. Set them on :root to change every set of tabs, or on any
element to change the tabs inside it:
| Token | What it styles |
|---|---|
--gui-bg-default | The background of the tabs and panels, and of the scroll shadows. |
--gui-border-default | The borders of the tabs and panels. |
--gui-text-default | The text, and the outline of the focused tab. |
--gui-radius-md | The corners of the tabs and panels. |
--gui-space-3 | The padding of the tabs and panels, and the space around the set. |
--gui-widget-size | The height of the scroll shadows. |
--gui-font-family | The font. |
--gui-font-sm | The text size. |
--gui-space-2 | The padding of the tabs in a narrow container. |
--gui-font-xs | The text size of the tabs in a narrow container. |
In a narrow container, below 480px wide, the tabs take less room.
/* Panels on a raised surface, with rounder tabs. */.settings { --gui-bg-default: #fbfaf7; --gui-radius-md: 12px;}To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.
Accessibility
Section titled “Accessibility”- Roles. The strip has the
tablistrole, each tab thetabrole and each panel thetabpanelrole. A part you give a role of your own keeps it. - Links. Each tab points at its panel with
aria-controls, and each panel is named by its tab througharia-labelledby. - State. The selected tab has
aria-selected="true"; the othersfalse. - Name. Name the strip with
aria-labelongui-tab-list, such as “Profile”, when the page has more than one set of tabs. - Focus. Only the selected tab is in the tab order. Each panel is focusable too, so keyboard
users reach a panel with no focusable content inside; give it
tabindex="-1"when its first element is focusable.
| Key | Action |
|---|---|
| Tab | Moves focus to the selected tab, then into its panel. |
| → | Selects the next tab and moves focus to it. |
| ← | Selects the previous tab and moves focus to it. |
| Home | Selects the first tab. |
| End | Selects the last tab. |
In a right-to-left page, → and ← swap. The arrows stop at the first and last tabs.
What every element shares is in Accessibility.