Tabs — derived() computes the selected tab, each/key renders the tablist, onMount picks the initial tab.
propstatederivedactiononMounteachkeyifelsegroupon:clickinterpolationThis recipe doesn't have a hydrated island in the demo gallery yet. An in-browser, WASM-compiled playground (edit this source and re-render live) is planned as a follow-up — read the real source below.
// tabs — prop tabs + action selectTab, derived selected (active tab)
@state {
const tabs = prop({
default: [
{ id: 'overview', label: 'Overview', content: 'Overview panel content.' },
{ id: 'details', label: 'Details', content: 'Details panel content.' },
{ id: 'settings', label: 'Settings', content: 'Settings panel content.' },
],
})
let activeId = state('')
const selected = derived(() => tabs.find(t => t.id === activeId) ?? tabs[0] ?? null)
onMount(() => {
if (tabs.length > 0) activeId = tabs[0].id
})
const selectTab = action((id: string) => { activeId = id })
}
@template {
<div class="tabs-component">
<div class="tab-list" role="tablist">
<group each={tab of tabs} key={tab.id}>
<button
role="tab"
class={['tab-btn', activeId === tab.id && 'active']}
aria-selected={activeId === tab.id}
on:click={() => selectTab(tab.id)}
>
{tab.label}
</button>
</group>
</div>
<div class="tab-panel" role="tabpanel">
<group if={selected}>
<p>{selected.content}</p>
</group><group else>
<p class="empty">No tab selected.</p>
</group>
</div>
</div>
}
@style {
.tabs-component { max-width: 32rem; border: 1px solid var(--border, #ccc); border-radius: 6px; overflow: hidden; }
.tab-list { display: flex; border-bottom: 1px solid var(--border, #ccc); background: var(--tab-bar-bg, #f5f5f5); }
.tab-btn { flex: 1; padding: 0.6rem 0.75rem; background: none; border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 0.9rem; }
.tab-btn.active { border-bottom-color: var(--accent, #0066cc); color: var(--accent, #0066cc); font-weight: 600; }
.tab-panel { padding: 1rem; }
.tab-panel p { margin: 0; }
.empty { color: var(--muted, #888); font-style: italic; }
}statea11y