Keyboard shortcut: Ctrl + K
Get started

Resizable

VResizable divides a space between two or more VResizablePanels and places a handle between each pair. A handle is dragged with the pointer, or focused and moved with the arrow keys.

Usage

vue
Navigation
Content
<script setup lang="ts">
import { VResizable, VResizablePanel } from 'vectis-ui'
</script>

<template>
  <VResizable class="group">
    <VResizablePanel class="pane" label="Navigation" :default-size="30">Navigation</VResizablePanel>
    <VResizablePanel class="pane">Content</VResizablePanel>
  </VResizable>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}
</style>

Examples

Vertical

orientation="vertical" stacks the panels. The group then needs a height. grip draws a grip on every handle.

vue
Editor
Terminal
<script setup lang="ts">
import { VResizable, VResizablePanel } from 'vectis-ui'
</script>

<template>
  <VResizable class="group" orientation="vertical" grip>
    <VResizablePanel class="pane" label="Editor">Editor</VResizablePanel>
    <VResizablePanel class="pane" label="Terminal" :default-size="35">Terminal</VResizablePanel>
  </VResizable>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}
</style>

Limits

minSize and maxSize take a number in percent or a CSS length such as 10rem. When a panel reaches its minimum, the handle takes the rest from the next panel along.

vue
Files: 10rem to 40%
Editor: at least 20%
Outline: at least 15%
<script setup lang="ts">
import { VResizable, VResizablePanel } from 'vectis-ui'
</script>

<template>
  <VResizable class="group">
    <VResizablePanel class="pane" label="Files" :default-size="25" min-size="10rem" max-size="40%">
      Files: 10rem to 40%
    </VResizablePanel>
    <VResizablePanel class="pane" label="Editor" :min-size="20"
      >Editor: at least 20%</VResizablePanel
    >
    <VResizablePanel class="pane" label="Outline" :default-size="25" :min-size="15">
      Outline: at least 15%
    </VResizablePanel>
  </VResizable>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}
</style>

Collapsible panels

A collapsible panel collapses when dragged past the middle of its minimum, or with Enter on its handle, and reopens at the size it had. v-model:collapsed follows it. collapsedSize keeps a strip such as an icon rail; at 0, the content is removed from the layout and the tab order.

vue

Drag the line to the edge, or press Enter on it.

Content
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VResizable, VResizablePanel } from 'vectis-ui'

const collapsed = ref(false)
</script>

<template>
  <VResizable class="group">
    <VResizablePanel
      v-model:collapsed="collapsed"
      class="pane"
      :class="{ rail: collapsed }"
      label="Sidebar"
      :default-size="30"
      :min-size="20"
      collapsible
      collapsed-size="3.5rem"
    >
      <VButton
        size="sm"
        variant="ghost"
        :aria-label="collapsed ? 'Expand the sidebar' : 'Collapse the sidebar'"
        @click="collapsed = !collapsed"
      >
        {{ collapsed ? '»' : '«' }}
      </VButton>
      <p v-if="!collapsed">Drag the line to the edge, or press Enter on it.</p>
    </VResizablePanel>
    <VResizablePanel class="pane">Content</VResizablePanel>
  </VResizable>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}

.rail {
  padding: var(--vectis-space-2);
}
</style>

Nested groups

A panel can hold a group of its own. Give the inner group height: 100%.

vue
Files
Editor
Terminal
<script setup lang="ts">
import { VResizable, VResizablePanel } from 'vectis-ui'
</script>

<template>
  <VResizable class="group" grip>
    <VResizablePanel class="pane" label="Files" :default-size="30">Files</VResizablePanel>
    <VResizablePanel>
      <VResizable class="inner" orientation="vertical" grip>
        <VResizablePanel class="pane" label="Editor">Editor</VResizablePanel>
        <VResizablePanel class="pane" label="Terminal" :default-size="35">Terminal</VResizablePanel>
      </VResizable>
    </VResizablePanel>
  </VResizable>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}

.inner {
  block-size: 100%;
}
</style>

Saving the sizes

The v-model holds the sizes in percent and changes during a drag. change fires once they settle, which is the moment to save them. For a server-rendered page, a cookie lets the server render the saved sizes at once.

vue
Resize, then reload the page.
Content

Sizes: 40 / 60

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { VResizable, VResizablePanel } from 'vectis-ui'

const KEY = 'docs-resizable-sizes'
const sizes = ref<number[]>([40, 60])

onMounted(() => {
  const saved = JSON.parse(localStorage.getItem(KEY) ?? 'null')
  if (Array.isArray(saved) && saved.length === 2) sizes.value = saved
})

function save(value: number[]) {
  localStorage.setItem(KEY, JSON.stringify(value))
}
</script>

<template>
  <div class="stack">
    <VResizable v-model="sizes" class="group" @change="save">
      <VResizablePanel class="pane">Resize, then reload the page.</VResizablePanel>
      <VResizablePanel class="pane">Content</VResizablePanel>
    </VResizable>
    <p class="sizes">Sizes: {{ sizes.map((size) => Math.round(size)).join(' / ') }}</p>
  </div>
</template>

<style scoped>
.group {
  block-size: 14rem;
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}

.pane {
  padding: var(--vectis-space-4);
}

.stack {
  display: grid;
  gap: var(--vectis-space-3);
}

.sizes {
  margin: 0;
}
</style>

API

Props

VResizable
PropTypeDefault
orientationResizableOrientation'horizontal' | 'vertical''horizontal'
Panels side by side, following the text direction, or stacked.
disabledbooleanfalse
Fixes the sizes. The handles can neither be dragged nor focused.
gripbooleanfalse
Draws a grip in the middle of every handle.
stepnumber5
How far an arrow key moves a handle, in percent.
v-modelnumber[]none
Size of each panel in percent of the shared space, adding up to 100. Unbound, panels start from their defaultSize.
VResizablePanel
PropTypeDefault
indexnumber0
Panel index assigned by the group. Do not set manually.
defaultSizenumbernone
Starting size in percent when the v-model gives none. Panels without one share the rest.
minSizeResizableLength0
Smallest size: a number in percent or a CSS length.
maxSizeResizableLengthnone
Largest size: a number in percent or a CSS length.
collapsiblebooleanfalse
Lets the panel collapse by dragging or with Enter on its handle.
collapsedSizeResizableLength0
Size of the collapsed panel, in percent or as a CSS length. At 0, the content is hidden.
labelstringnone
Accessible name of the handle that resizes the panel.
v-model:collapsedbooleanfalse
Collapsed state. Changing it collapses or reopens the panel.

Events

VResizable
EventType
change[sizes: number[]]
Emits the sizes once they settle: after a drag, a key or a collapse.

Slots

VResizable
SlotType
default{}
The VResizablePanels, two or more.
VResizablePanel
SlotType
default{}
Panel content.

Types

Types used in the API tables. Import exported types from vectis-ui.

export type ResizableLength = number | string

CSS variables

TokenValue
--vectis-control-size-resizable-hit0.75rem
--vectis-control-size-resizable-grip-length1.5rem
--vectis-control-size-resizable-grip-thickness0.5rem