Tabs
Tabs switch between views that sit at the same level of one screen. Primary tabs sit at the top of the content, secondary tabs divide what is inside one of those views.
Usage
Each <Tabs> holds several <Tab> children. Set <Tabs> to the primary or the secondary variant.
Primary tabs
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
</script>
<Tabs value="videos">
<Tab badge badgeLabel="2" value="videos">Videos</Tab>
<Tab value="theme">Theme</Tab>
<Tab badge value="settings">Settings</Tab>
</Tabs>
Icons
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Tabs value="videos">
<Tab badge badgeLabel="2" value="videos">
{#snippet icon()}<Icon>videocam</Icon>{/snippet}Videos
</Tab>
<Tab value="theme">{#snippet icon()}<Icon>palette</Icon>{/snippet}Theme</Tab>
<Tab badge value="settings">{#snippet icon()}<Icon>settings</Icon>{/snippet}Settings</Tab>
</Tabs>
Secondary tabs
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
</script>
<Tabs variant="secondary" value="videos">
<Tab badge badgeLabel="2" value="videos">Videos</Tab>
<Tab value="theme">Theme</Tab>
<Tab badge value="settings">Settings</Tab>
</Tabs>
Icons
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
import { Icon } from 'noph-ui/icons'
</script>
<Tabs variant="secondary" value="videos">
<Tab badge badgeLabel="2" value="videos">
{#snippet icon()}<Icon>videocam</Icon>{/snippet}Videos
</Tab>
<Tab value="theme">{#snippet icon()}<Icon>palette</Icon>{/snippet}Theme</Tab>
<Tab badge value="settings">{#snippet icon()}<Icon>settings</Icon>{/snippet}Settings</Tab>
</Tabs>
Scrollable tabs
By default the tabs share the full width equally. When the tabs or labels do not fit, set scrollable. Each tab is then as wide as its label and the strip scrolls sideways. The
first tab sits 52dp from the leading edge, so the cut off tabs at the end show that there is more.
Selecting a tab scrolls it towards the middle of the strip.
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
const breeds = [
'Akita',
'Alaskan Malamute',
'Australian Shepherd',
'Azawakh',
'Barbet',
'Basenji',
'Beagle',
'Bernese Mountain Dog',
]
let value = $state('Akita')
</script>
<div style="width:100%;max-width:400px">
<Tabs bind:value scrollable aria-label="Dog breeds">
{#each breeds as breed (breed)}
<Tab value={breed}>{breed}</Tab>
{/each}
</Tabs>
</div>
Selection
The value of <Tabs> sets the selected tab: the <Tab> with the same value is selected. Each <Tab> needs a unique value within its <Tabs>.
Use bind:value on <Tabs> to track the selected tab in your state.
<script lang="ts">
import { Tab, Tabs } from 'noph-ui'
let value = $state('videos')
</script>
<div>
<Tabs bind:value>
<Tab value="videos">Videos</Tab>
<Tab value="theme">Theme</Tab>
<Tab value="settings">Settings</Tab>
</Tabs>
<div style="margin-top:1rem;font-size:0.825rem">Selected tab: {value}</div>
</div>
Links
Give a <Tab> an href to render it as a link to another page or
route. The value prop still tracks the selected tab across pages, which helps with SSR
and deep linking. Clicking a link tab selects it at once, and Space follows the link like Enter.
<script lang="ts">
import { page } from '$app/state'
import { Tab, Tabs } from 'noph-ui'
</script>
<div>
<Tabs variant="secondary" value={page.url.searchParams.get('tab') || 'videos'}>
<Tab data-sveltekit-noscroll href="/components/tabs?tab=videos" value="videos">Videos</Tab>
<Tab data-sveltekit-noscroll href="/components/tabs?tab=theme" value="theme">Theme</Tab>
<Tab data-sveltekit-noscroll href="/components/tabs?tab=settings" value="settings">
Settings
</Tab>
</Tabs>
</div>
Theming
Tabs are drawn from the color roles: --np-color-primary for the indicator and the
selected label, --np-color-on-surface-variant for the rest, and --np-color-secondary for the focus ring.
| Custom property | Default value |
|---|---|
--np-tabs-indicator-radius | --np-shape-corner-full on primary tabs, 0 on secondary tabs |
Accessibility
The strip renders role="tablist" and each tab role="tab" with aria-selected. The tabs share one tab stop: the left and right arrow keys move
between them, Home and End jump to the ends, Space and Enter select the focused tab, and Tab
leaves the strip for the panel.
Where a tab reveals a panel on the same page, give the panel an id, point at it with controls and mark the panel role="tabpanel". Tabs that navigate take an href instead and stay real links. A badge on a tab is read after the tab's name: the
count, or New notification for a dot. Pass badgeAriaLabel to say what the number counts.
API
Tabs
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
value | number | string | Value of the current tab. | |
variant | 'primary' | 'secondary' | 'primary' | The style of the tabs: 'primary' or 'secondary'. |
scrollable | boolean | false | Tabs as wide as their labels that scroll sideways, the first one 52dp from the leading edge. Without it the tabs share the width equally. |
element | HTMLElement | A reference to the root DOM element of the component. |
Tab
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
inlineIcon | boolean | false | Only affects the primary variant. |
badge | boolean | false | |
badgeLabel | string | number | undefined | undefined | Text shown inside the badge. |
badgeAriaLabel | string | undefined | undefined | Read after the label, for example "2 new videos". Without it the count is
read, or New notification for a dot. |
controls | string | undefined | undefined | The id of the panel this tab controls, rendered as aria-controls. |
value | number | string | undefined | Identifies the tab. The tab is selected when it matches the value of <Tabs>. |
icon | Snippet | undefined | undefined | Icon shown before the label, as a snippet such as an <Icon>. |
href | string | undefined | Renders the tab as a link to this URL. |
element | HTMLElement | A reference to the root DOM element of the component. |