Playground
A segmented tab bar with keyboard support. Labels come from a JSON attribute. Each panel is a slotted element whose slot is the label in kebab-case.
Examples
With panels
A light, fast shuttle built for orbital hops.
Payload 4t · Δv 9.4 km/s · 2 engines
Commander, pilot and one very good dog.
<sb-tabs labels='["Overview","Specs","Crew"]' style="inline-size: 100%">
<p slot="overview">A light, fast shuttle built for orbital hops.</p>
<p slot="specs">Payload 4t · Δv 9.4 km/s · 2 engines</p>
<p slot="crew">Commander, pilot and one very good dog.</p>
</sb-tabs>
Follow the selection
<div data-signals:_tab="'Home'">
<sb-tabs labels='["Home","Docs","API"]' data-on:sb-tab-change="$_tab = evt.detail.label"></sb-tabs>
<p data-text="'Current: ' + $_tab"></p>
</div>
With commands
Give it a name, and it emits sb-change with { name, value } when a value is committed: ready to post as a command. With confirm, it sets :state(pending) until the server's re-rendered attribute matches, and revert() goes back to the server's value when a command is rejected. See Commands and components and the Showcase.
Accessibility
It follows the WAI-ARIA tabs pattern: role="tablist", tab and tabpanel, a roving tabindex, and arrow keys, Home and End for navigation.
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<sb-tabs labels='["Home","Docs","API"]'></sb-tabs>
<!-- In production, pin today's catalog instead of the latest: the browser then
refuses any file that changed. Add "integrity" to the import map above: the
hashes from https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
"integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/autoloader.js" integrity="sha384-IvSCUjEWPS+wwXunU+48x96KmyWDx/Y4LeCwCL8cWFqJ/zHmTsTOsSMrzTOcpW3D"></script>
-->
<!-- Or load just this component, pinned to this version: -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/tabs@e659fa4b2a85/tabs.js" integrity="sha384-Oa6wz6gtcqerLG7hl8S0Cf0ufDU9E2VV/QYEjKL53+Ly4RZGxNg3UoWrJ82BBcwd"></script> -->Size
Each file compressed on its own, the way it is served (gzip -9, brotli -11). Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli |
|---|---|---|---|
tabs.js | 5.2 kB | 2.2 kB | 1.9 kB |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
labels | array | [
"Home",
"Docs",
"API"
] | Tab labels, as a JSON array. |
selected | number | 0 | Index of the selected tab. A new index from the server wins. |
confirm | boolean | false | Server-confirmed value: :state(pending) while the local value differs from the server's value attribute (see revert()). |
name | string | "" | Name reported in sb-change (e.g. the field of a command). |
Slots
| Name | Description |
|---|---|
<label-slug> | Panel content per tab, e.g. slot="docs" for a tab labelled "Docs". |
Events
| Name | Description |
|---|---|
sb-tab-change | detail: { index, label }. |
sb-change | Same moment. detail: { name, value } (value is the index): ready for a command. |