Skip to content

Splitter

Alpha
A component that divides your layout into resizable sections.
Panel A
Panel B
Panel C
vue
<script setup lang="ts">
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'radix-vue'
</script>

<template>
  <div class="w-full h-64 px-8 text-green9 font-medium text-sm">
    <SplitterGroup
      id="splitter-group-1"
      direction="horizontal"
    >
      <SplitterPanel
        id="splitter-group-1-panel-1"
        :min-size="20"
        class="bg-white rounded-xl flex items-center justify-center"
      >
        Panel A
      </SplitterPanel>
      <SplitterResizeHandle
        id="splitter-group-1-resize-handle-1"
        class="w-2"
      />
      <SplitterPanel
        id="splitter-group-1-panel-2"
        :min-size="20"
      >
        <SplitterGroup
          id="splitter-group-2"
          direction="vertical"
        >
          <SplitterPanel
            id="splitter-group-2-panel-1"
            :min-size="20"
            class="bg-white rounded-xl flex items-center justify-center"
          >
            Panel B
          </SplitterPanel>
          <SplitterResizeHandle
            id="splitter-group-2-resize-handle-1"
            class="h-2"
          />
          <SplitterPanel
            id="splitter-group-2-panel-2"
            :min-size="20"
            class="bg-white rounded-xl flex items-center justify-center"
          >
            Panel C
          </SplitterPanel>
        </SplitterGroup>
      </SplitterPanel>
    </SplitterGroup>
  </div>
</template>

Credit

This component was heavily inspired by react-resizable-panels by Bryan Vaughn.

Features

  • Supports keyboard interaction.
  • Supports horizontal/vertical layout.
  • Supports nested layout.
  • Supports Right to Left direction.
  • Can resize across another panel.
  • Can be mounted conditionally.

Installation

Install the component from your command line.

sh
$ npm add radix-vue

Anatomy

Import all parts and piece them together.

vue
<script setup>
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'radix-vue'
</script>

<template>
  <SplitterGroup>
    <SplitterPanel />
    <SplitterResizeHandle />
  </SplitterGroup>
</template>

API Reference

Group

Contains all the parts of a Splitter.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

autoSaveId
null
string | null

Unique id used to auto-save group arrangement via localStorage.

direction*
'vertical' | 'horizontal'

The group orientation of splitter.

id
string | null

Group id; falls back to useId when not provided.

keyboardResizeBy
10
number | null

Step size when arrow key was pressed.

storage
defaultStorage
PanelGroupStorage

Custom storage API; defaults to localStorage

EmitPayload
layout
[val: number[]]

Event handler called when group layout changes

Slots (default)Payload
layout
number[]

Current size of layout

Data AttributeValue
[data-orientation]"vertical" | "horizontal"
[data-state]"collapsed" | "expanded" | "Present when collapsbile"

Panel

A collapsible section.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

collapsedSize
number

The size of panel when it is collapsed.

collapsible
boolean

Should panel collapse when resized beyond its minSize. When true, it will be collapsed to collapsedSize.

defaultSize
number

Initial size of panel (numeric value between 1-100)

id
string

Panel id (unique within group); falls back to useId when not provided

maxSize
number

The maximum allowable size of panel (numeric value between 1-100); defaults to 100

minSize
number

The minimum allowable size of panel (numeric value between 1-100); defaults to 10

order
number

The order of panel within group; required for groups with conditionally rendered panels

EmitPayload
collapse
[]

Event handler called when panel is collapsed.

expand
[]

Event handler called when panel is expanded.

resize
[size: number, prevSize: number]

Event handler called when panel is resized; size parameter is a numeric value between 1-100.

Slots (default)Payload
isCollapsed
boolean

Is the panel collapsed

isExpanded
boolean

Is the panel expanded

MethodsType
collapse
() => void

If panel is collapsible, collapse it fully.

expand
() => void

If panel is currently collapsed, expand it to its most recent size.

getSize
() => number

Gets the current size of the panel as a percentage (1 - 100).

resize
(size: number) => void

Resize panel to the specified percentage (1 - 100).

Resize Handle

Handle that use for resizing.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disabled
boolean

Disable drag handle

hitAreaMargins
PointerHitAreaMargins

Allow this much margin when determining resizable handle hit detection

id
string

Resize handle id (unique within group); falls back to useId when not provided

tabindex
0
number

Tabindex for the handle

EmitPayload
dragging
[isDragging: boolean]

Event handler called when dragging the handler.

Data AttributeValue
[data-state]"drag" | "hover" | "inactive"
[data-disabled]Present when disabled
[data-orientation]"vertical" | "horizontal"

Examples

Collapsible

Use the collapsible prop to allow the panel to collapse into collapsedSize when minSize is reached.

(collapsedSize and minSize props are required.)

vue
<template>
  <SplitterGroup>
    <SplitterPanel
      collapsible
      :collapsed-size="10"
      :min-size="35"
    >
      Panel A
    </SplitterPanel>
    <SplitterResizeHandle />
    <SplitterPanel>
      Panel B
    </SplitterPanel>
  </SplitterGroup>
</template>

Persist in localStorage

Use the autoSaveId prop to save the layout data into localStorage.

vue
<template>
  <SplitterGroup auto-save-id="any-id">

  </SplitterGroup>
</template>

Persist layout with SSR

By default, Splitter uses localStorage to persist layouts. With server rendering, this can cause a flicker when the default layout (rendered on the server) is replaced with the persisted layout (in localStorage). The way to avoid this flicker is to also persist the layout with a cookie like so:

vue
<!-- with Nuxt -->
<script setup lang="ts">
const layout = useCookie<number[]>('splitter:layout')
</script>

<template>
  <SplitterGroup
    direction="horizontal"
    @layout="layout = $event"
  >
    <SplitterPanel :default-size="layout[0]">

    </SplitterPanel>
    <SplitterResizeHandle />
    <SplitterPanel :default-size="layout[1]">

    </SplitterPanel>
  </SplitterGroup>
</template>

Collapse/Expand programmatically

Sometimes panels need to resize or collapse/expand in response to user actions. SplitterPanel exposes the collapse and expand methods to achieve this.

vue
<script setup lang="ts">
const panelRef = ref<InstanceType<typeof SplitterPanel>>()
</script>

<template>
  <button
    @click="panelRef?.isCollapsed ? panelRef?.expand() : panelRef?.collapse() "
  >
    {{ panelRef?.isCollapsed ? 'Expand' : 'Collapse' }}
  </button>

  <SplitterGroup>
    <SplitterPanel
      ref="panelRef"
      collapsible
      :collapsed-size="10"
      :min-size="35"
    >

    </SplitterPanel>
    <SplitterResizeHandle />
    <SplitterPanel>

    </SplitterPanel>
  </SplitterGroup>
</template>

Custom handle

Customize the handle by passing any element as the slot.

vue
<template>
  <SplitterGroup>
    <SplitterPanel>

    </SplitterPanel>
    <SplitterResizeHandle>
      <Icon icon="radix-icons-drag-handle-dots-2" />
    </SplitterResizeHandle>
    <SplitterPanel>

    </SplitterPanel>
  </SplitterGroup>
</template>

SSR

Splitter component heavily relies on unique id, however for Vue<3.4 we don't have a reliable way of generating SSR-friendly id.

Thus, if you are using Nuxt or other SSR framework, you are required to manually add the id for all Splitter components. Alternatively, you can wrap the component with <ClientOnly>.

vue
<template>
  <SplitterGroup id="group-1">
    <SplitterPanel id="group-1-panel-1">

    </SplitterPanel>
    <SplitterResizeHandle id="group-1-resize-1">
      <Icon icon="radix-icons-drag-handle-dots-2" />
    </SplitterResizeHandle>
    <SplitterPanel id="group-1-panel-2">

    </SplitterPanel>
  </SplitterGroup>
</template>

Accessibility

Adheres to the Window Splitter WAI-ARIA design pattern.

Keyboard Interactions

KeyDescription
Enter
If the primary pane is not collapsed, collapses the pane. If the pane is collapsed, restores the splitter to its previous position.
ArrowDown
Moves a horizontal splitter down.
ArrowUp
Moves a horizontal splitter up.
ArrowRight
Moves a vertical splitter to the right.
ArrowLeft
Moves a vertical splitter to the left.
Home
Moves splitter to the position that gives the primary pane its smallest allowed size.
End
Moves splitter to the position that gives the primary pane its largest allowed size.