Layout
SplitView
A pane beside the content, inline or as an overlay, that can shrink to a strip of icons when closed.
WAI-ARIA tested
Import
main.ts
import { SplitView } from '@vitral/vue';NavigationView shell
The hamburger toggles the pane. Switch the display mode to compare: in the compact modes the closed pane keeps its icons.
Home
Display mode compactOverlay, pane on the left. Open, the pane floats over this content; press outside it or Escape to close.
Home 1
Home 2
Home 3
Home 4
Home 5
Home 6
1<script setup lang="ts">2import { Button, Icon, Label, Select, SplitView, StackPanel } from '@vitral/vue';3import { computed, ref } from 'vue';4 5type Mode = 'inline' | 'overlay' | 'compactInline' | 'compactOverlay';6 7const modes = [8 { label: 'Compact overlay', value: 'compactOverlay' },9 { label: 'Compact inline', value: 'compactInline' },10 { label: 'Overlay', value: 'overlay' },11 { label: 'Inline', value: 'inline' }12];13const placements = [14 { label: 'Left', value: 'left' },15 { label: 'Right', value: 'right' }16];17 18const mode = ref<Mode>('compactOverlay');19const placement = ref<'left' | 'right'>('left');20const open = ref(false);21const compactMode = computed(() => mode.value === 'compactInline' || mode.value === 'compactOverlay');22const floating = computed(() => mode.value === 'overlay' || mode.value === 'compactOverlay');23 24const items = [25 { id: 'home', label: 'Home', icon: 'home' },26 { id: 'documents', label: 'Documents', icon: 'folder' },27 { id: 'favorites', label: 'Favorites', icon: 'star' },28 { id: 'people', label: 'People', icon: 'user' },29 { id: 'alerts', label: 'Notifications', icon: 'bell' }30];31const settings = { id: 'settings', label: 'Settings', icon: 'sliders' };32const page = ref('home');33const current = computed(() => [...items, settings].find((item) => item.id === page.value)!);34 35function go(id: string, close: () => void) {36 page.value = id;37 // A floating pane gets out of the way once a page is chosen, as NavigationView's does.38 if (floating.value) close();39}40</script>41 42<template>43 <div class="controls">44 <StackPanel spacing="0.375rem" style="min-width: 16rem">45 <Label for="sv-mode">Display mode</Label>46 <Select id="sv-mode" v-model="mode" :options="modes" option-label="label" option-value="value" size="small" />47 </StackPanel>48 <StackPanel spacing="0.375rem" style="min-width: 16rem">49 <Label for="sv-placement">Placement</Label>50 <Select id="sv-placement" v-model="placement" :options="placements" option-label="label" option-value="value" size="small" />51 </StackPanel>52 </div>53 54 <SplitView v-model:open="open" class="app-frame" :display-mode="mode" :placement="placement" :open-pane-length="248" pane-label="Main navigation">55 <template #pane="{ paneId, toggle, close, open: isOpen }">56 <div class="nav">57 <Button58 icon="menu"59 variant="text"60 severity="secondary"61 class="nav-toggle"62 :aria-label="isOpen ? 'Close navigation' : 'Open navigation'"63 :aria-controls="paneId"64 :aria-expanded="isOpen ? 'true' : 'false'"65 @click="toggle"66 />67 <nav aria-label="Pages" class="nav-list">68 <button v-for="item in items" :key="item.id" type="button" class="nav-item" :aria-current="page === item.id ? 'page' : undefined" @click="go(item.id, close)">69 <Icon :icon="item.icon" />70 <span class="nav-label">{{ item.label }}</span>71 </button>72 </nav>73 <button type="button" class="nav-item nav-footer" :aria-current="page === settings.id ? 'page' : undefined" @click="go(settings.id, close)">74 <Icon :icon="settings.icon" />75 <span class="nav-label">{{ settings.label }}</span>76 </button>77 </div>78 </template>79 80 <template #default="{ paneId, toggle, open: isOpen }">81 <div class="page">82 <header class="page-bar">83 <Button84 v-if="!compactMode && !isOpen"85 icon="menu"86 variant="text"87 severity="secondary"88 aria-label="Open navigation"89 :aria-controls="paneId"90 aria-expanded="false"91 @click="toggle"92 />93 <h4>{{ current.label }}</h4>94 </header>95 <p class="page-text">96 Display mode <code>{{ mode }}</code>, pane on the {{ placement }}.97 {{ floating ? 'Open, the pane floats over this content; press outside it or Escape to close.' : 'Open, the pane pushes this content aside.' }}98 </p>99 <div class="cards">100 <div v-for="n in 6" :key="n" class="card" :class="`c${((n - 1) % 5) + 1}`">{{ current.label }} {{ n }}</div>101 </div>102 </div>103 </template>104 </SplitView>105</template>106 107<style scoped>108.controls {109 display: flex;110 flex-wrap: wrap;111 gap: 0.75rem;112 width: 100%;113}114 115.controls .demo-field {116 min-width: 12rem;117}118 119.app-frame {120 width: 100%;121 height: 24rem;122 background: var(--vt-content-background);123 border: 1px solid var(--vt-content-border-color);124 border-radius: var(--vt-border-radius-lg);125}126 127.nav {128 display: flex;129 flex-direction: column;130 height: 100%;131 padding: 0.25rem 0;132}133 134/* Centred on the same 24px line as the nav icons, in the middle of the 48px strip. */135.nav-toggle {136 margin-inline-start: calc(24px - var(--vt-control-min-height) / 2);137 margin-bottom: 0.25rem;138}139 140.nav-list {141 display: flex;142 flex-direction: column;143 gap: 2px;144}145 146.nav-item {147 position: relative;148 display: flex;149 align-items: center;150 gap: 1rem;151 width: calc(100% - 0.5rem);152 min-height: 2.25rem;153 margin: 0 0.25rem;154 /* The icon sits centred in the 48px compact strip. */155 padding: 0 0 0 calc(24px - 0.25rem - 0.5rem);156 font: inherit;157 text-align: start;158 color: var(--vt-navigation-item-color);159 background: transparent;160 border: 0;161 border-radius: var(--vt-navigation-item-border-radius);162 cursor: pointer;163 white-space: nowrap;164}165 166.nav-item:hover {167 background: var(--vt-navigation-item-focus-background);168}169 170.nav-item[aria-current='page'] {171 background: var(--vt-navigation-item-active-background);172 color: var(--vt-navigation-item-active-color);173}174 175.nav-item[aria-current='page']::before {176 content: '';177 position: absolute;178 left: 0;179 top: 50%;180 width: 3px;181 height: 1rem;182 border-radius: 3px;183 background: var(--vt-primary-color);184 transform: translateY(-50%);185}186 187.nav-item:focus-visible {188 outline: var(--vt-focus-ring-width) var(--vt-focus-ring-style) var(--vt-focus-ring-color);189 outline-offset: -2px;190}191 192.nav-footer {193 margin-top: auto;194}195 196.page {197 display: flex;198 flex-direction: column;199 gap: 0.75rem;200 padding: 0.75rem 1rem 1rem;201}202 203.page-bar {204 display: flex;205 align-items: center;206 gap: 0.5rem;207 min-height: 2rem;208}209 210.page-bar h4 {211 margin: 0;212 margin-inline-end: auto;213 font-size: 1.125rem;214 font-weight: 600;215}216 217.page-text {218 margin: 0;219 font-size: 0.8125rem;220 color: var(--vt-text-muted-color);221}222 223.cards {224 display: grid;225 grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));226 gap: 0.5rem;227}228 229.card {230 --c: var(--vt-chart-1);231 min-height: 4.5rem;232 padding: 0.5rem 0.75rem;233 font-size: 0.8125rem;234 color: var(--vt-text-color);235 background: color-mix(in srgb, var(--c) 16%, transparent);236 border: 1px solid color-mix(in srgb, var(--c) 50%, transparent);237 border-radius: var(--vt-border-radius-md);238}239 240.c1 {241 --c: var(--vt-chart-1);242}243 244.c2 {245 --c: var(--vt-chart-2);246}247 248.c3 {249 --c: var(--vt-chart-3);250}251 252.c4 {253 --c: var(--vt-chart-4);254}255 256.c5 {257 --c: var(--vt-chart-5);258}259</style>Inline, on the right
A details pane: display-mode="inline" placement="right", open. Closing it gives the list the whole width.
Files
file 1
file 2
file 3
file 4
1<script setup lang="ts">2import { Button, SplitView } from '@vitral/vue';3import { ref } from 'vue';4 5const details = ref(true);6</script>7 8<template>9 <SplitView v-model:open="details" class="app-frame" display-mode="inline" placement="right" :open-pane-length="220" pane-label="Details">10 <template #pane="{ close }">11 <div class="details">12 <header class="page-bar">13 <h4>Details</h4>14 <Button icon="close" variant="text" severity="secondary" size="small" aria-label="Close details" @click="close" />15 </header>16 <dl>17 <dt>Name</dt>18 <dd>report-2026.pdf</dd>19 <dt>Size</dt>20 <dd>1.2 MB</dd>21 <dt>Modified</dt>22 <dd>Yesterday</dd>23 </dl>24 </div>25 </template>26 <template #default="{ paneId, toggle, open: isOpen }">27 <div class="page">28 <header class="page-bar">29 <h4>Files</h4>30 <Button :label="isOpen ? 'Hide details' : 'Show details'" icon="sidebar" severity="secondary" size="small" :aria-controls="paneId" :aria-expanded="isOpen ? 'true' : 'false'" @click="toggle" />31 </header>32 <div class="cards">33 <div v-for="n in 4" :key="n" class="card" :class="`c${n}`">file {{ n }}</div>34 </div>35 </div>36 </template>37 </SplitView>38</template>39 40<style scoped>41.app-frame {42 width: 100%;43 height: 15rem;44 background: var(--vt-content-background);45 border: 1px solid var(--vt-content-border-color);46 border-radius: var(--vt-border-radius-lg);47}48 49.page {50 display: flex;51 flex-direction: column;52 gap: 0.75rem;53 padding: 0.75rem 1rem 1rem;54}55 56.page-bar {57 display: flex;58 align-items: center;59 gap: 0.5rem;60 min-height: 2rem;61}62 63.page-bar h4 {64 margin: 0;65 margin-inline-end: auto;66 font-size: 1.125rem;67 font-weight: 600;68}69 70.cards {71 display: grid;72 grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));73 gap: 0.5rem;74}75 76.card {77 --c: var(--vt-chart-1);78 min-height: 4.5rem;79 padding: 0.5rem 0.75rem;80 font-size: 0.8125rem;81 color: var(--vt-text-color);82 background: color-mix(in srgb, var(--c) 16%, transparent);83 border: 1px solid color-mix(in srgb, var(--c) 50%, transparent);84 border-radius: var(--vt-border-radius-md);85}86 87.c1 {88 --c: var(--vt-chart-1);89}90 91.c2 {92 --c: var(--vt-chart-2);93}94 95.c3 {96 --c: var(--vt-chart-3);97}98 99.c4 {100 --c: var(--vt-chart-4);101}102 103.details {104 padding: 0.75rem 1rem;105}106 107.details dl {108 display: grid;109 grid-template-columns: auto 1fr;110 gap: 0.375rem 0.75rem;111 margin: 0.75rem 0 0;112 font-size: 0.8125rem;113}114 115.details dt {116 color: var(--vt-text-muted-color);117}118 119.details dd {120 margin: 0;121}122</style>API
Read from packages/vue/src/components/SplitView/types.ts, so it says what the component actually accepts.
Props
| Name | Type | Description |
|---|---|---|
| displayMode | 'inline' | 'overlay' | 'compactInline' | 'compactOverlay' | - `inline`: the open pane pushes the content aside; closed, it is gone. - `overlay` (the default): the open pane floats over the content and closes on a press outside or Escape. - `compactInline`: like `inline`, but closed it keeps a strip `compactPaneLength` wide, room for icons. - `compactOverlay`: the strip stays beside the content, and the open pane floats over it. |
| placement | 'left' | 'right' | The side the pane is on. `left` is the start side: it follows the writing direction. |
| openPaneLength | number | string | Width of the open pane: pixels, or any CSS length. |
| compactPaneLength | number | string | Width of the closed pane in the compact modes. |
| paneLabel | string | The pane's accessible name: it is an `<aside>`, a complementary landmark. |
| paneId | string | The pane's id, for a toggle's `aria-controls`. Generated when left out; the slots receive it either way. |
| as | string | Component | — |
Plus pt, dt and unstyled from BaseProps, see pass-through and unstyled mode.
Slots
| Name | Slot props | Description |
|---|---|---|
| pane | (props: SplitViewSlotProps) | — |
| default | (props: SplitViewSlotProps) | — |
Related components
- DockPanelDocks regions to its top, bottom, left and right edges and gives what is left to the fill; the order decides who wins the corners.
- GridA grid with star sizing: rows and columns sized to content, as shares of what is left, or in pixels, with children placed by row and column.
- SplitterPanels with a gutter between each pair that resizes them, by dragging or from the keyboard, within each panel's minimum.
- StackPanelArranges its children in one line, stacked or side by side, with even spacing between them.