Tab Panel
A tab panel is a content panel associated with a tab in a tab bar. It shows the content for the currently selected tab.
Use it as the content companion to a TabBar and TabBarButton. Each panel is labelled by its corresponding tab and is shown only when its tab is selected.
Example
Show demo markup
<div class="tab-panel" aria-label="a content panel associated with a tab in a tab bar">TabPanel</div>
Usage example
<script lang="ts">
import TabPanel from './TabPanel.svelte';
let current = $state('details');
</script>
<TabPanel
label="Details"
labelledBy="tab-details"
selected={current === 'details'}
>
<p>Details content…</p>
</TabPanel>
Show Svelte source
// In your Svelte component:
import TabPanel from "lily-design-system-svelte-headless/components/TabPanel/TabPanel.svelte";
<TabPanel>
<!-- content -->
</TabPanel>
Usage
<TabBar>
<TabBarButton id="tab-overview" controls="panel-overview" selected>Overview</TabBarButton>
<TabBarButton id="tab-details" controls="panel-details">Details</TabBarButton>
</TabBar>
<TabPanel id="panel-overview" labelledBy="tab-overview" selected={tab === 'overview'}>
Overview content...
</TabPanel>
<TabPanel id="panel-details" labelledBy="tab-details" selected={tab === 'details'}>
Details content...
</TabPanel>
Props
label: string (required) -- aria-label for the panel
labelledBy: string (optional) -- ID of the tab button that controls this panel; when provided, sets aria-labelledby and the panel does not set aria-label
selected: boolean (default false) -- whether the panel is the currently selected one
children: slot -- panel content
...restProps: any additional HTML attributes
Keyboard Interactions
- Tab: Move focus into and out of the panel
- (Provided by the parent
TabBar) Arrow keys to navigate between tabs
- Within the panel, the consumer provides any additional keyboard handling
ARIA
role="tabpanel" on the container
aria-labelledby={labelledBy} when controlled by a tab; otherwise aria-label={label}
hidden reflects !selected
tabindex="0" so the panel itself is keyboard-focusable
When to Use
- Use as the content companion to a
TabBar
- Use to switch among related views without navigating to a new page
- Use when only one view is shown at a time
When Not to Use
- Do not use without a corresponding tab in a
TabBar
- Do not show multiple selected panels at once
- Do not use for primary navigation between pages — use
NavigationMenu
Composition
Tab panels pair with tabs:
<TabBar label="Sections">
<TabBarButton id="t1" controls="p1" selected>One</TabBarButton>
<TabBarButton id="t2" controls="p2">Two</TabBarButton>
</TabBar>
<TabPanel id="p1" labelledBy="t1" selected>...</TabPanel>
<TabPanel id="p2" labelledBy="t2">...</TabPanel>
Headless
Renders a <div role="tabpanel"> linked to its tab via aria-labelledby. Selection state is controlled by the consumer via the selected prop; animation, lazy loading, and styling are entirely the consumer's responsibility.
Styles
Consumer CSS targets the tab-panel class. Use the hidden attribute selector or [aria-selected] on the parent tab to style transitions. Provide a clear focus indicator on the panel since it is keyboard-focusable.
Testing
- Verify the component renders a
<div> with role="tabpanel" and class tab-panel
- Verify
tabindex="0" is present
- Verify
aria-labelledby is set and aria-label is absent when labelledBy is provided
- Verify
aria-label is set and aria-labelledby is absent when labelledBy is not provided
- Verify
hidden is set when selected is false
- Verify
hidden is absent when selected is true
- Verify children are rendered inside the panel
Advice
- Designers: Keep tab labels short; communicate the selection clearly with both color and a non-color indicator (underline, weight).
- Developers: Use stable ids on tabs and panels so
aria-labelledby and aria-controls survive re-renders.
Implementation Notes
- Uses a
<div> with role="tabpanel" and class tab-panel
- When
labelledBy is provided, the panel uses aria-labelledby={labelledBy} and omits aria-label
- When
labelledBy is not provided, the panel uses aria-label={label} instead
- The panel is hidden via the
hidden attribute when selected is false
- The panel uses
tabindex="0" so keyboard users can scroll the panel content
Related components
tab-bar — a group of tabs for switching between content panels
tab-bar-button — one tab button in a tab group
References
- WAI-ARIA Tabs Pattern: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
- Adobe Spectrum Tabs: https://spectrum.adobe.com/page/tabs/
- WCAG 1.3.1 Info and Relationships: https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: tab-panel
- PascalCase: TabPanel
- Description: a content panel associated with a tab in a tab bar
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <div> with role="tabpanel"
- CSS class: .tab-panel
- Interactive: focusable container (tabindex="0")
ARIA
role="tabpanel" on the container aria-labelledby when controlled by a tab id, otherwise aria-label tabindex="0" so the panel is keyboard-focusable hidden reflects !selected
Keyboard
- Tab: Move focus into and out of the panel
- Parent TabBar provides arrow-key navigation between tabs
Props
label: string (REQUIRED) -- aria-label labelledBy: string (optional) -- if present, used as aria-labelledby; aria-label is omitted selected: boolean (default false) children: slot -- panel content ...restProps: any additional HTML attributes