Window Splitter
Resize adjacent panes with a quantized, keyboard-accessible separator.
Usage
Horizontal
Use horizontal movement while preserving the same value and boundary rules.
<script setup lang="ts">
const release = 'stable'
</script>
<template>
<ReleaseCard :channel="release" />
</template><!-- Window Splitter / Horizontal -->
<script setup lang="ts">
import { ref } from 'vue'
import { FileCode2, Folder, Search } from '@lucide/vue'
import { WindowSplitterHandle, WindowSplitterPane, WindowSplitterRoot } from '@sectile/vue/window-splitter'
const size = ref('34')
const editorSource = `<script setup lang="ts">
const release = 'stable'
<\/script>
<template>
<ReleaseCard :channel="release" />
</template>`
</script>
<template>
<WindowSplitterRoot v-model="size" orientation="horizontal" :min="22" :max="72" :step="1">
<WindowSplitterPane side="before">
<header><strong>Project</strong><Search :size="16" aria-hidden="true" /></header>
<nav aria-label="Project files">
<span><Folder :size="16" aria-hidden="true" /> src</span>
<span><FileCode2 :size="16" aria-hidden="true" /> App.vue</span>
<span><FileCode2 :size="16" aria-hidden="true" /> tokens.ts</span>
</nav>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize project and editor panes" />
<WindowSplitterPane side="after">
<header><strong>App.vue</strong><span>Saved</span></header>
<pre><code>{{ editorSource }}</code></pre>
</WindowSplitterPane>
</WindowSplitterRoot>
</template>Vertical
Use vertical movement while preserving the same pane-size rules.
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')$ pnpm test 18 passed
<!-- Window Splitter / Vertical -->
<script setup lang="ts">
import { ref } from 'vue'
import { SquareTerminal } from '@lucide/vue'
import { WindowSplitterHandle, WindowSplitterPane, WindowSplitterRoot } from '@sectile/vue/window-splitter'
const size = ref('56')
</script>
<template>
<WindowSplitterRoot v-model="size" orientation="vertical" :min="32" :max="76" :step="1">
<WindowSplitterPane side="before">
<header><strong>Editor</strong><span>main.ts</span></header>
<pre><code>import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')</code></pre>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize editor and terminal panes" />
<WindowSplitterPane side="after">
<header><span><SquareTerminal :size="15" aria-hidden="true" /> Terminal</span><span>zsh</span></header>
<p><span>$</span> pnpm test <span>18 passed</span></p>
</WindowSplitterPane>
</WindowSplitterRoot>
</template>Controlled
Let the parent own the current value and apply accepted changes back to the component.
const layout = {
sidebar: '28%',
editor: '68%'
}<!-- Window Splitter / Controlled -->
<script setup lang="ts">
import { ref } from 'vue'
import { CheckCircle2, FileCode2, Folder, Search } from '@lucide/vue'
import { WindowSplitterHandle, WindowSplitterPane, WindowSplitterRoot } from '@sectile/vue/window-splitter'
const sidebarSize = ref('28')
const editorSize = ref('68')
// Parent state remains the source of truth for both separators.
</script>
<template>
<WindowSplitterRoot v-model="sidebarSize" orientation="horizontal" :min="22" :max="46" :step="1">
<WindowSplitterPane side="before">
<header><strong>Workspace</strong><Search :size="16" aria-hidden="true" /></header>
<nav aria-label="Workspace files">
<span><Folder :size="16" aria-hidden="true" /> components</span>
<span><FileCode2 :size="16" aria-hidden="true" /> SplitPane.vue</span>
<span><Folder :size="16" aria-hidden="true" /> tests</span>
</nav>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize workspace and main panes" />
<WindowSplitterPane side="after">
<WindowSplitterRoot v-model="editorSize" orientation="vertical" :min="42" :max="78" :step="1">
<WindowSplitterPane side="before">
<header><strong>SplitPane.vue</strong><span>TypeScript</span></header>
<pre><code>const layout = {
sidebar: {{ sidebarSize }}%,
editor: {{ editorSize }}%
}</code></pre>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize editor and preview panes" />
<WindowSplitterPane side="after">
<span><CheckCircle2 :size="17" aria-hidden="true" /> Preview ready</span>
<small>Both separators remain independently adjustable.</small>
</WindowSplitterPane>
</WindowSplitterRoot>
</WindowSplitterPane>
</WindowSplitterRoot>
</template>Examples
Mixed orientation
Combine a resizable sidebar with an independently adjustable editor and preview stack.
const layout = {
sidebar: '28%',
editor: '68%'
}<!-- Window Splitter / Nested Layout -->
<script setup lang="ts">
import { ref } from 'vue'
import { CheckCircle2, FileCode2, Folder, Search } from '@lucide/vue'
import { WindowSplitterHandle, WindowSplitterPane, WindowSplitterRoot } from '@sectile/vue/window-splitter'
const sidebarSize = ref('28')
const editorSize = ref('68')
</script>
<template>
<WindowSplitterRoot v-model="sidebarSize" orientation="horizontal" :min="22" :max="46" :step="1">
<WindowSplitterPane side="before">
<header><strong>Workspace</strong><Search :size="16" aria-hidden="true" /></header>
<nav aria-label="Workspace files">
<span><Folder :size="16" aria-hidden="true" /> components</span>
<span><FileCode2 :size="16" aria-hidden="true" /> SplitPane.vue</span>
<span><Folder :size="16" aria-hidden="true" /> tests</span>
</nav>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize workspace and main panes" />
<WindowSplitterPane side="after">
<WindowSplitterRoot v-model="editorSize" orientation="vertical" :min="42" :max="78" :step="1">
<WindowSplitterPane side="before">
<header><strong>SplitPane.vue</strong><span>TypeScript</span></header>
<pre><code>const layout = {
sidebar: {{ sidebarSize }}%,
editor: {{ editorSize }}%
}</code></pre>
</WindowSplitterPane>
<WindowSplitterHandle aria-label="Resize editor and preview panes" />
<WindowSplitterPane side="after">
<span><CheckCircle2 :size="17" aria-hidden="true" /> Preview ready</span>
<small>Both separators remain independently adjustable.</small>
</WindowSplitterPane>
</WindowSplitterRoot>
</WindowSplitterPane>
</WindowSplitterRoot>
</template>API
Vue package: @sectile/vue/window-splitter
WindowSplitterRootWindowSplitterPaneWindowSplitterHandle
Props
WindowSplitterRootProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
defaultValueInitial value used when the component owns its state.
disabledWhether interaction is unavailable.
formID of the native form associated with the control.
formatValueFormats a value for visible text.
labelAccessible name announced for the control.
maxLargest value accepted by the component.
minSmallest value accepted by the component.
modelValueCurrent value when state is controlled by the parent.
nameName used for native form submission.
orientationAxis used for layout and keyboard movement.
pageStepLarger increment used by Page Up and Page Down.
requiredWhether the control must contain a valid value before submission.
stepSmallest value increment accepted by the component.
WindowSplitterPaneProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
sidePreferred side of the anchor for positioned content.
Events
WindowSplitterRoot
update:modelValueEmitted when the component requests a new controlled value.
Other types
WindowSplitterValueFormatter
type WindowSplitterValueFormatter = NonNullable<WindowSplitterRootProps['formatValue']>WindowSplitterValueChangeHandler
type WindowSplitterValueChangeHandler = (value: string) => voidParts
Shared scope: [data-scope="window-splitter"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
root | [data-part="root"] | Defines the component boundary and owns its composed parts. | — |
pane | [data-part="pane"] | Contains one resizable region. | — |
handle | [data-part="handle"] | Resizes adjacent panes. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow Left / Arrow Right | Make the leading pane one step smaller or larger in a horizontal layout. |
| Arrow Up / Arrow Down | Make the leading pane one step smaller or larger in a vertical layout. |
| Home / End | Move the separator to its minimum or maximum boundary. |
| Page Up / Page Down | Resize by the configured page step. |
Accessibility
The handle exposes separator orientation and current, minimum, and maximum pane size.
See the corresponding WAI-ARIA pattern for the host accessibility contract.
