---
title: "Splitter"
description: "Panels that share one area and are resized by dragging the divider between them."
canonical_url: "https://bitrix24.github.io/b24ui/docs/components/splitter"
---
# Splitter

> Panels that share one area and are resized by dragging the divider between them.

## Usage

Use the Splitter component to display a list of resizable panels separated by draggable handles.

```vue [SplitterExample.vue]
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'

const card = 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'

const items: SplitterItem[] = [
  { slot: 'left', minSize: 15, defaultSize: 25, class: card },
  { slot: 'main', minSize: 30, defaultSize: 50, class: card },
  { slot: 'right', minSize: 15, defaultSize: 25, class: card }
]
</script>

<template>
  <div class="w-full h-96">
    <B24Splitter id="splitter-example" :items="items">
      <template #left>
        Left
      </template>

      <template #main>
        Main
      </template>

      <template #right>
        Right
      </template>
    </B24Splitter>
  </div>
</template>
```

> [!NOTE]
> The Splitter fills the height of its container, so make sure a parent element defines one.

### Items

Use the `items` prop as an array of objects with the following properties:

- `defaultSize?: number`
- `minSize?: number`
- `maxSize?: number`
- `collapsible?: boolean`
- `collapsedSize?: number`
- `sizeUnit?: '%' | 'px'`
- `order?: number`
- `id?: string`
- `slot?: string`
- `class?: any`
- `b24ui?: { panel?: ClassNameValue }`

Use the `slot` key to fill the content of a panel and the `class` key to style it. Items without a `slot` key fall back to a `panel-{index}` slot. Sizes are percentages by default, set `sizeUnit: 'px'` on an item for pixel values.

> [!CAUTION]
> When rendering on the server, set the `id` prop and give `defaultSize` to all items or to none. Ids are generated automatically otherwise and the server and the client can disagree, which breaks the layout on hydration. An item without a `defaultSize` falls back to an equal share on the server, so mixing the two makes panels jump once hydrated. Pixel sizes are measured on the client and always shift a little.

```vue
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'

const items = ref<SplitterItem[]>([
  {
    slot: 'sidebar',
    minSize: 15,
    maxSize: 40,
    defaultSize: 25,
    class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'
  },
  {
    slot: 'main',
    defaultSize: 75,
    class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'
  }
])
</script>

<template>
  <B24Splitter id="splitter-items" :items="items">
  <template #sidebar>
    Sidebar
  </template>

  <template #main>
    Main
  </template>

</B24Splitter>
</template>
```

### Orientation

Use the `orientation` prop to change the direction of the splitter. Defaults to `horizontal`.

```vue
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'

const items = ref<SplitterItem[]>([
  {
    slot: 'first',
    class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'
  },
  {
    slot: 'second',
    class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'
  }
])
</script>

<template>
  <B24Splitter id="splitter-orientation" orientation="vertical" :items="items">
  <template #first>
    First
  </template>

  <template #second>
    Second
  </template>

</B24Splitter>
</template>
```

## Examples

### With collapsible panel

Set `collapsible: true` on an item to let it collapse past its `minSize`, and use `collapsedSize` to keep part of the panel visible when collapsed. The panel slot exposes `collapsed`, `collapse` and `expand` so you can control it programmatically, and the `collapse`, `expand` and `resize` events fire with the panel index.

```vue [SplitterCollapsibleExample.vue]
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'
import CloseChatIcon from '@bitrix24/b24icons-vue/outline/CloseChatIcon'
import OpenChatIcon from '@bitrix24/b24icons-vue/outline/OpenChatIcon'

const items: SplitterItem[] = [
  { slot: 'sidebar', sizeUnit: 'px', minSize: 150, defaultSize: 250, collapsible: true, collapsedSize: 48, class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md)' },
  { slot: 'main', class: 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)' }
]
</script>

<template>
  <div class="w-full h-96">
    <B24Splitter id="splitter-collapsible-example" :items="items">
      <template #sidebar="{ collapsed, collapse, expand }">
        <div class="flex-1 flex items-center justify-center p-2">
          <B24Button
            :icon="collapsed ? OpenChatIcon : CloseChatIcon"
            :label="collapsed ? undefined : 'Collapse'"
            :aria-label="collapsed ? 'Expand' : undefined"
            color="air-secondary"
            size="sm"
            @click="collapsed ? expand() : collapse()"
          />
        </div>
      </template>

      <template #main>
        Main
      </template>
    </B24Splitter>
  </div>
</template>
```

### With nested splitters

Nest a `Splitter` inside a panel to build two-dimensional, IDE-style layouts.

```vue [SplitterNestedExample.vue]
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'

const card = 'bg-(--ui-color-bg-content-secondary) border border-(--ui-color-divider-default) rounded-(--ui-border-radius-md) items-center justify-center text-description font-(--ui-font-weight-medium)'

const items: SplitterItem[] = [
  { slot: 'left', minSize: 20, class: card },
  { slot: 'right', minSize: 20 }
]

const nested: SplitterItem[] = [
  { slot: 'top', minSize: 20, class: card },
  { slot: 'bottom', minSize: 20, class: card }
]
</script>

<template>
  <div class="w-full h-96">
    <B24Splitter id="splitter-nested-example" :items="items">
      <template #left>
        Left
      </template>

      <template #right>
        <B24Splitter id="splitter-nested-example-inner" orientation="vertical" :items="nested">
          <template #top>
            Top
          </template>

          <template #bottom>
            Bottom
          </template>
        </B24Splitter>
      </template>
    </B24Splitter>
  </div>
</template>
```

### With custom handle

The handle is invisible by default. Use the `b24ui` prop to restyle it, for example as a visible divider for flush layouts, and the `resize-handle` slot to render content inside it like a grip.

```vue [SplitterCustomHandleExample.vue]
<script setup lang="ts">
import type { SplitterItem } from '@bitrix24/b24ui-nuxt'

const panel = 'items-center justify-center text-description font-(--ui-font-weight-medium)'

const items: SplitterItem[] = [
  { slot: 'left', minSize: 20, defaultSize: 30, class: panel },
  { slot: 'right', defaultSize: 70, class: panel }
]
</script>

<template>
  <div class="w-full h-96">
    <B24Splitter
      id="splitter-custom-handle-example"
      :items="items"
      :b24ui="{ handle: 'data-[orientation=horizontal]:w-px data-[orientation=vertical]:h-px bg-(--ui-color-divider-default) transition-colors data-[state=hover]:bg-(--ui-color-accent-main-primary) data-[state=drag]:bg-(--ui-color-accent-main-primary)' }"
      class="rounded-(--ui-border-radius-md) border border-(--ui-color-divider-default) overflow-hidden"
    >
      <template #left>
        Left
      </template>

      <template #right>
        Right
      </template>
    </B24Splitter>
  </div>
</template>
```

### With persistence

Provide an `auto-save-id` to persist the layout to `localStorage` and restore it on reload.

```vue
<template>
  <B24Splitter id="my-layout" auto-save-id="my-layout" :items="items">
    <!-- ... -->
  </B24Splitter>
</template>
```

## API

### Props

```ts
/**
 * Props for the Splitter component
 */
interface SplitterProps {
  /**
   * The element or component this component should render as.
   * @default 'div'
   */
  as?: any;
  /**
   * A unique id for the group, also used to derive the ids of its panels and handles.
   * Set it when rendering on the server, auto-generated ids can differ between the server and the client and break resizing on hydration.
   */
  id?: string | undefined;
  /**
   * The orientation of the splitter.
   * @default 'horizontal'
   */
  orientation?: "horizontal" | "vertical" | undefined;
  items?: T[] | undefined;
  /**
   * Whether the resize handles are disabled, locking the current layout.
   * @default false
   */
  disabled?: boolean | undefined;
  b24ui?: { root?: SlotClass; panel?: SlotClass; handle?: SlotClass; } | undefined;
  /**
   * Unique id used to auto-save group arrangement via `localStorage`.
   */
  autoSaveId?: null | string | undefined;
  /**
   * Step size when arrow key was pressed.
   */
  keyboardResizeBy?: null | number | undefined;
  /**
   * Custom storage API; defaults to localStorage
   */
  storage?: PanelGroupStorage | undefined;
  /**
   * Allow this much margin when determining resizable handle hit detection
   */
  hitAreaMargins?: PointerHitAreaMargins | undefined;
}
```

### Slots

```ts
/**
 * Slots for the Splitter component
 */
interface SplitterSlots {
  resize-handle(): any;
}
```

### Emits

```ts
/**
 * Emitted events for the Splitter component
 */
interface SplitterEmits {
  layout: (payload: [val: number[]]) => void;
  collapse: (payload: [index: number]) => void;
  expand: (payload: [index: number]) => void;
  resize: (payload: [index: number, size: number, prevSize?: number | undefined]) => void;
  dragging: (payload: [index: number, dragging: boolean]) => void;
}
```

## Theme

```ts [app.config.ts]
export default defineAppConfig({
  b24ui: {
    splitter: {
      slots: {
        root: '',
        panel: 'flex',
        handle: 'group relative shrink-0 outline-transparent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ui-color-design-outline-focused-stroke) data-[panel-resize-handle-enabled=false]:cursor-default'
      },
      variants: {
        orientation: {
          horizontal: {
            handle: 'w-2 cursor-col-resize'
          },
          vertical: {
            handle: 'h-2 cursor-row-resize'
          }
        }
      }
    }
  }
})
```

## Changelog

<component-changelog>

</component-changelog>

## Sitemap

See the full [sitemap](/b24ui/sitemap.md) for all pages.
