SvSplitter

Two resizable panes divided by a draggable WAI-ARIA separator, with pointer drag and arrow-key resize.

SvSplitter renders a start and an end snippet on either side of a gutter you can drag or resize with the keyboard. It is controlled through fraction (the first pane's share of the container, 0..1) and onChange. The clamp, keyboard, ARIA, and RTL logic live in a headless createSplitter core; the component owns only the container measurement. Nest splitters for IDE- or dashboard-style layouts.

Related: SvTabs · SvScrollArea · Layout & composite overview

Installation

Add it with the CLI - this drops a ready-to-edit SvSplitter starter into your app:

Prefer to see it first? npx @svgrid/ui try splitter opens it in a throwaway sandbox - no project needed.

Or install the package and import it directly. SvSplitter ships free in @svgrid/grid (dependency-free):

The examples on this page import from @svgrid/grid:

<script lang="ts">
  import { SvScrollArea, SvSplitter } from '@svgrid/grid'
</script>
import { SvSplitter } from '@svgrid/grid'

Example

Open the live example: Splitter (Layout)

<script lang="ts">
  import { SvSplitter } from '@svgrid/grid'
  let fraction = $state(0.3)
</script>

<div style="height: 320px">
  <SvSplitter {fraction} onChange={(f) => (fraction = f)} ariaLabel="Sidebar width">
    {#snippet start()}<nav>…tree…</nav>{/snippet}
    {#snippet end()}<main>…content…</main>{/snippet}
  </SvSplitter>
</div>

Props

Prop Type Default Description
fraction number 0.5 First pane size as a fraction of the container (bindable).
onChange (fraction: number) => void - Fired as the separator moves.
orientation SplitterOrientation (horizontal | vertical) horizontal horizontal = left/right panes; vertical = top/bottom.
min number 0.1 Minimum fraction for the first pane.
max number 0.9 Maximum fraction for the first pane.
step number 0.02 Keyboard resize step as a fraction (2%).
disabled boolean false Locks the separator.
dir EditorDir (ltr | rtl | auto) - Text direction. rtl flips the horizontal drag direction.
ariaLabel string - Accessible name for the separator.
start Snippet - Content of the first pane.
end Snippet - Content of the second pane.

SplitterOrientation

type SplitterOrientation = 'horizontal' | 'vertical'

Examples

Bounded sidebar

Constrain the drag range so a sidebar cannot be collapsed away or dragged too wide:

<SvSplitter {fraction} min={0.15} max={0.4}
  onChange={(f) => (fraction = f)} ariaLabel="Sidebar" />

Nested (IDE layout)

Put a vertical splitter inside one pane of a horizontal one for an editor plus a stacked panel and terminal. Each splitter targets its own separator, so a nested splitter of the opposite orientation sizes correctly.

Resizable master / detail

A record list on the left and the selected record on the right, with a divider the user can drag. Keep fraction in $state and let each pane scroll on its own:

<script lang="ts">
  import { SvSplitter, SvScrollArea } from '@svgrid/grid'
  let split = $state(0.32)
  let selected = $state(rows[0])
</script>

<div style="height: 420px">
  <SvSplitter fraction={split} min={0.2} max={0.5}
    onChange={(f) => (split = f)} ariaLabel="List width">
    {#snippet start()}
      <SvScrollArea height="100%">
        {#each rows as row}
          <button onclick={() => (selected = row)}>{row.name}</button>
        {/each}
      </SvScrollArea>
    {/snippet}
    {#snippet end()}
      <SvScrollArea height="100%"><RecordDetail record={selected} /></SvScrollArea>
    {/snippet}
  </SvSplitter>
</div>

Tip: the separator is keyboard-resizable - arrow keys move it by step (default 2%) and Home / End jump to min / max - so users who cannot drag can still size the panes.

Accessibility

A resizable pane

fraction is a 0-1 split rather than a pixel width, so the panes keep their proportion when the window resizes. min and max stop either side being dragged to nothing.

<script lang="ts">
  import { SvSplitter, SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = { name: string; city: string }

  const people: Person[] = [
    { name: 'Ada Lovelace', city: 'London' },
    { name: 'Grace Hopper', city: 'New York' },
  ]

  const columns: GridColumns<Person> = [
    { field: 'name', header: 'Name', width: 160 },
    { field: 'city', header: 'City', width: 120 },
  ]

  let split = $state(0.6)
</script>

<div style="height: 260px;">
  <SvSplitter fraction={split} onChange={(f) => (split = f)} min={0.25} max={0.8}>
    {#snippet start()}
      <SvGrid data={people} {columns} />
    {/snippet}
    {#snippet end()}
      <div style="padding: 12px;">Detail pane - {Math.round(split * 100)}% / {Math.round((1 - split) * 100)}%</div>
    {/snippet}
  </SvSplitter>
</div>

See also