---
title: "Splitter"
description: "A two-pane layout with a draggable divider."
category: Layout
status: beta
since: 0.3.4
tags: [layout, resize, panels]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
The panes split the space left after the separator track by the `v-model` ratio; `direction="vertical"` stacks them.
::::TuffDemoWrapper{demo="SplitterSplitterDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const ratio = ref(0.42)
  const verticalRatio = ref(0.58)
  </script>

  <template>
    <div style="height: 220px;">
      <TxSplitter v-model="ratio" :min="0.25" :max="0.75" :snap="0.05">
        <template #a>Navigation ({{ ratio.toFixed(2) }})</template>
        <template #b>Content</template>
      </TxSplitter>
    </div>
    <div style="height: 220px;">
      <TxSplitter v-model="verticalRatio" direction="vertical" :bar-size="12">
        <template #a>Preview</template>
        <template #b>Logs</template>
      </TxSplitter>
    </div>
  </template>
---
::::

### Best Practices

- Put the splitter in a container with explicit width and height; without a bounded box the panes can't resize predictably.
- Keep `min` and `max` far enough apart that neither pane shrinks to unreadable content.
- Use `snap` for inspector and sidebar layouts that need stable ratios; leave it `0` for free-form canvases.
- Put scroll containers inside the pane slots, not around the root, so resizing doesn't fight page scroll.
- Disable the splitter during modal interactions or drag-and-drop to avoid competing pointer capture.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | `number` | `0.5` | Ratio of pane A; non-finite values become `0.5`, and rendering clamps to `0..1`. |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | `horizontal` splits left/right, `vertical` top/bottom; invalid values become `horizontal`. |
| `min` | `number` | `0.1` | Smallest ratio emitted by pointer or keyboard resizing. |
| `max` | `number` | `0.9` | Largest ratio emitted by pointer or keyboard resizing. |
| `disabled` | `boolean` | `false` | Blocks resizing; disabling mid-drag ends the drag. |
| `barSize` | `number` | `6` | Separator track size in px, at least 6; the visible grip is 2px thick. |
| `snap` | `number` | `0` | Ratio step; `0` turns snapping off. |

### Events

| Event | Payload | Description |
|------|---------|-------------|
| `update:modelValue` | `number` | Emits the next clamped and snapped ratio. |
| `change` | `number` | Fires together with `update:modelValue`. |
| `drag-start` | `()` | Fires when a pointer drag starts. |
| `drag-end` | `()` | Fires when a drag ends, or is cancelled by disabling or unmounting. |

### Slots

| Slot | Props | Description |
|------|-------|-------------|
| `a` | - | First pane: left when horizontal, top when vertical. |
| `b` | - | Second pane: right when horizontal, bottom when vertical. |

## Overview

- Pointer drags map through the root's bounding box: `clientX / width` horizontally, `clientY / height` vertically.
- Arrow keys (← / → horizontally, ↑ / ↓ vertically) step by `0.02`, then clamp and snap.
- The separator is a `role="separator"` with `aria-label="Resize"` and an `aria-orientation` perpendicular to the split; `aria-valuenow` / `aria-valuemin` / `aria-valuemax` are integer percentages (`0..100`).
- When disabled, the separator gets `tabindex="-1"` and ignores pointer and keyboard input.
- Callers must keep `min <= max`. The ratio isn't persisted; store the `v-model` value to remember it.

## Technologies

- The root writes `--tx-splitter-ratio` and `--tx-splitter-bar-size`, and the grid columns are computed from them.
- Source: `packages/tuffex/packages/components/src/splitter/`.

<TuffDocSourceLink />
