Skip to main content
You are viewing Pre-Alpha documentation.
Vuetify0 Logo
Theme
Mode
Accessibility
Vuetify
Vuetify One

Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Splitter

A headless component for building resizable panel layouts with drag handles and full keyboard support.


Renders elementIntermediateMar 10, 2026

Usage

The Splitter provides resizable panels separated by draggable handles. Panel sizes are specified as percentages and must sum to 100.

Panel A
Panel B
<script setup lang="ts">
  import { Splitter } from '@vuetify/v0'
  import ResizeHandle from './resize-handle.vue'
</script>

<template>
  <Splitter.Root class="h-48 border border-divider rounded-lg overflow-hidden">
    <Splitter.Panel
      class="flex items-center justify-center bg-surface"
      :default-size="50"
      :min-size="20"
    >
      <span class="text-sm text-on-surface-variant">Panel A</span>
    </Splitter.Panel>

    <ResizeHandle label="Resize panels" />

    <Splitter.Panel
      class="flex items-center justify-center bg-surface"
      :default-size="50"
      :min-size="20"
    >
      <span class="text-sm text-on-surface-variant">Panel B</span>
    </Splitter.Panel>
  </Splitter.Root>
</template>

Anatomy

Anatomy
<script setup lang="ts">
  import { Splitter } from '@vuetify/v0'
</script>

<template>
  <Splitter.Root>
    <Splitter.Panel :default-size="50" />

    <Splitter.Handle />

    <Splitter.Panel :default-size="50" />
  </Splitter.Root>
</template>

Examples

Nested Layouts

Splitters compose naturally — place a Splitter.Root inside any panel to build complex layouts. Each nested splitter operates independently with its own sizes, handles, and orientation, while the outer splitter manages the top-level split.

This IDE-style workspace demonstrates the pattern with two levels of nesting: a horizontal splitter divides the sidebar from the main content area, and a vertical splitter inside the content panel separates the code editor from a live preview.

Playground Layout

An IDE workspace split across two files — the layout composition and a reusable handle component:

FileRole
playground.vueComposes the nested layout — horizontal sidebar split with a vertical editor/preview split inside
resize-handle.vueReusable styled handle that accepts an horizontal prop to adapt its cursor, dimensions, and grip indicator direction

Key patterns:

  • The outer Splitter.Root uses the default horizontal orientation for the sidebar/content split

  • The inner Splitter.Root sets orientation="vertical" to stack the editor above the preview

  • min-size and max-size constraints on the sidebar panel (15–30%) prevent it from collapsing or dominating the layout

  • ResizeHandle is a thin wrapper around Splitter.Handle — it takes a horizontal prop rather than reading context, making it portable across any splitter without coupling to a specific root

Explorer
App.vue
main.ts
style.css
App.vue
<template>
  <h1>Hello World</h1>
</template>
Preview

Hello World

Recipes

Orientation

Set orientation on the root to control layout direction. Defaults to horizontal.

vue
<template>
  <Splitter.Root orientation="vertical">
    <Splitter.Panel :default-size="50">Top</Splitter.Panel>
    <Splitter.Handle />
    <Splitter.Panel :default-size="50">Bottom</Splitter.Panel>
  </Splitter.Root>
</template>

Collapsible Panels

Panels can collapse to a minimum size. Set collapsible and optionally collapsed-size on the panel. The panel’s slot props provide collapse(), expand(), size, and isCollapsed — use these to build collapse controls inline. Keyboard users can press Home/End on the adjacent handle.

vue
<script setup lang="ts">
  import { Splitter } from '@vuetify/v0'
</script>

<template>
  <Splitter.Root>
    <Splitter.Panel
      v-slot="{ collapse, expand, isCollapsed }"
      :default-size="30"
      :min-size="15"
      :collapsed-size="0"
      collapsible
    >
      <button v-if="isCollapsed" @click="expand">Expand</button>
      <template v-else>
        <button @click="collapse">Collapse</button>
        Sidebar
      </template>
    </Splitter.Panel>

    <Splitter.Handle label="Resize sidebar" />

    <Splitter.Panel :default-size="70" :min-size="30">
      Content
    </Splitter.Panel>
  </Splitter.Root>
</template>

Controlled Collapse

Use v-model:collapsed for two-way binding of collapsed state. This lets you control collapse from outside the splitter — for example, a toolbar button or a shared ref.

vue
<script setup lang="ts">
  import { Splitter } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const collapsed = shallowRef(false)
</script>

<template>
  <button @click="collapsed = !collapsed">
    {{ collapsed ? 'Show' : 'Hide' }} Sidebar
  </button>

  <Splitter.Root>
    <Splitter.Panel
      v-model:collapsed="collapsed"
      :default-size="30"
      :min-size="15"
      :collapsed-size="0"
      collapsible
    >
      Sidebar
    </Splitter.Panel>

    <Splitter.Handle label="Resize sidebar" />

    <Splitter.Panel :default-size="70" :min-size="30">
      Content
    </Splitter.Panel>
  </Splitter.Root>
</template>

The model syncs in both directions — setting the ref collapses/expands the panel, and drag-to-collapse or keyboard Home/End updates the ref.

Events

The root emits @layout with all panel sizes at the end of each resize interaction. Panels emit @resize with their individual size.

vue
<template>
  <Splitter.Root @layout="sizes => console.log('layout', sizes)">
    <Splitter.Panel :default-size="50" @resize="size => console.log('panel', size)">
      Left
    </Splitter.Panel>
    <Splitter.Handle />
    <Splitter.Panel :default-size="50">Right</Splitter.Panel>
  </Splitter.Root>
</template>

Programmatic Sizing

Use distribute() from the root’s slot props to set all panel sizes at once. Values are clamped to each panel’s min/max constraints. Place controls inside a panel to keep them out of the root’s flex layout.

vue
<script setup lang="ts">
  import { Splitter } from '@vuetify/v0'
</script>

<template>
  <Splitter.Root v-slot="{ distribute }">
    <Splitter.Panel :default-size="50" :min-size="20">
      <button @click="distribute([30, 70])">30 / 70</button>
      <button @click="distribute([50, 50])">50 / 50</button>
      Left
    </Splitter.Panel>
    <Splitter.Handle />
    <Splitter.Panel :default-size="50" :min-size="20">Right</Splitter.Panel>
  </Splitter.Root>
</template>

Disabled State

Disable all resize interactions via the disabled prop on the root, or disable individual handles.

vue
<template>
  <!-- Disable all handles -->
  <Splitter.Root disabled>
    ...
  </Splitter.Root>

  <!-- Disable a single handle -->
  <Splitter.Root>
    <Splitter.Panel :default-size="33" />
    <Splitter.Handle disabled />
    <Splitter.Panel :default-size="34" />
    <Splitter.Handle />
    <Splitter.Panel :default-size="33" />
  </Splitter.Root>
</template>

Accessibility

The Splitter implements the WAI-ARIA Window Splitter↗ pattern.

  • Each handle has role="separator" with aria-valuenow, aria-valuemin, and aria-valuemax

  • aria-orientation is set perpendicular to the layout direction (a horizontal layout produces vertical separators)

  • aria-controls links each handle to the panel it precedes

  • Use the label prop on handles to provide an aria-label (e.g., label="Resize sidebar")

  • Disabled handles set tabindex="-1" and aria-disabled="true"

Keyboard Navigation

KeyAction
Arrow Left / Arrow UpShrink preceding panel by 1%
Arrow Right / Arrow DownGrow preceding panel by 1%
Page UpShrink preceding panel by 10%
Page DownGrow preceding panel by 10%
HomeCollapse preceding panel (if collapsible) or shrink to minimum
EndExpand preceding panel (if collapsed) or grow to maximum
EnterToggle collapse state of preceding panel (if collapsible)

Arrow direction follows the layout orientation — horizontal splitters use Left/Right, vertical splitters use Up/Down.

API Reference

The following API details are for all variations of the Splitter component.

Splitter.Root

Props

orientation

SplitterOrientation

Default: "horizontal"

disabled

boolean

Default: false

Events

layout

[sizes: number[]]

Slots

default

SplitterRootSlotProps

Splitter.Handle

Props

disabled

boolean

Default: false

label

string

Slots

default

SplitterHandleSlotProps

Splitter.Panel

Props

minSize

number

Default: 0

maxSize

number

Default: 100

collapsible

boolean

Default: false

collapsedSize

number

Default: 0

collapsed

boolean

Default: undefined

Events

resize

[size: number]

Slots

default

SplitterPanelSlotProps
Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/