Appearance
SidebarLayout
A responsive two-panel layout with a collapsible sidebar and main content area. On mobile, the sidebar becomes an overlay.
When the sidebar is collapsed it is marked inert, so its contents (including the collapse button) are removed from the tab order and the accessibility tree while visually hidden. The expand button is likewise inert while the sidebar is open. This keeps keyboard and screen-reader focus on whichever control is actually visible.
Demo
Tabs Demo (Router Mode)
Usage
vue
<SidebarLayout>
<template #sidebar>
<h2>Controls</h2>
<NumberInput v-model="value" label="Parameter" slider live />
</template>
<h1>Main Content</h1>
<p>Your charts and data go here.</p>
</SidebarLayout>Slots
| Slot | Description |
|---|---|
sidebar | Content rendered in the left sidebar panel |
sidebar-header-start | Left side of the sidebar's header band, e.g. a pane title |
sidebar-header-end | Right side of the sidebar's header band, beside the collapse button |
topbar-start | Left side of the main header: before the tabs, or alone without them |
topbar-end | Right side of the main header, beside the light/dark toggle (topbar still works as the old name) |
default | Main content area |
Buttons, selects and text fields placed in the three header slots take the header's control height (28px), so they line up with the collapse and light/dark toggles:
vue
<SidebarLayout :tabs="tabs">
<template #sidebar-header-start>
<h2>Parameters</h2>
</template>
<template #sidebar-header-end>
<Button variant="neutral" @click="reset">Reset</Button>
</template>
<template #topbar-end>
<Button variant="neutral" @click="exportCsv"><Icon icon="download" :size="16" /> Export</Button>
</template>
...
</SidebarLayout>Props
| Prop | Type | Default | Description |
|---|---|---|---|
hideTopbar | boolean | false | Hides the topbar that contains the light/dark toggle |
tabs | Tab[] | undefined | Array of tab definitions to render in the main area |
maxWidth | string | number | 1024px | Max width of the main content: a CSS length ("none" for full width) or pixels |
v-model:tab | string | undefined | The active tab value (two-way binding) |
Tab type
ts
interface Tab {
value: string; // unique identifier
label: string; // display text
to?: string; // optional route path for vue-router integration
}Tabs
When the tabs prop is provided, a tab bar renders at the top of the main content area. Tabs support two modes:
Local mode
Use v-model:tab to control which tab is active. Render content conditionally in the default slot.
vue
<script setup>
import { ref } from "vue";
const activeTab = ref("chart");
</script>
<SidebarLayout
v-model:tab="activeTab"
:tabs="[
{ value: 'chart', label: 'Chart' },
{ value: 'data', label: 'Data' },
]"
>
<template #sidebar>
<h2>Controls</h2>
</template>
<div v-if="activeTab === 'chart'">Chart content</div>
<div v-if="activeTab === 'data'">Data table</div>
</SidebarLayout>Router mode
When tabs include a to property and vue-router is installed, clicking a tab navigates to that route. The active tab is automatically determined from the current route.
vue
<SidebarLayout
:tabs="[
{ value: 'chart', label: 'Chart', to: '/model/chart' },
{ value: 'data', label: 'Data', to: '/model/data' },
]"
>
<template #sidebar>
<h2>Controls</h2>
</template>
<RouterView />
</SidebarLayout>