Resizable

A component that allows users to resize a container by dragging, supporting 2D resizing, aspect ratio locking, and panel splitting.

Component Features

  • ↔️ 8 Resize Directions: Supports top, bottom, left, right, and their corner combinations.
  • 📐 Aspect Ratio Lock: Supports locking aspect ratio during resize.
  • 📊 Grid Snapping: Supports grid-based snapping for precise alignment.
  • ⌨️ Keyboard Resize: Supports keyboard-based resize operations.
  • 📋 Panel Splitting: Supports drag-to-split panels with resize handles.
  • 📏 Size Constraints: Supports minWidth, maxWidth, minHeight, maxHeight constraints.
  • 🎨 Theme Customization: Based on BrConfigProvider for global theming and TailwindCSS local overrides.

Basic Usage

Supports resizing in horizontal or vertical directions, or simultaneously in multiple directions.

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrResizableHandle } from '@breezeui/vue'

const width = ref(300)
const height = ref(200)
</script>

<template>
  <BrResizable
    v-model:width="width"
    v-model:height="height"
    :min-width="150"
    :min-height="100"
    :max-width="500"
    :max-height="400"
    class="border border-border rounded-lg bg-muted/20 flex items-center justify-center relative shadow-sm"
  >
    <div class="text-center">
      <div class="font-medium text-foreground">Basic Resizable</div>
      <div class="text-sm text-muted-foreground">{{ Math.round(width) }}px x {{ Math.round(height) }}px</div>
    </div>
    <template #handle="{ onDragStart }">
      <BrResizableHandle direction="right" class="bg-primary/50 hover:bg-primary w-2" @drag-start="onDragStart($event, 'right')" />
      <BrResizableHandle direction="bottom" class="bg-primary/50 hover:bg-primary h-2" @drag-start="onDragStart($event, 'bottom')" />
      <BrResizableHandle direction="bottom-right" class="bg-primary/50 hover:bg-primary w-4 h-4 rounded-tl-lg" @drag-start="onDragStart($event, 'bottom-right')" />
    </template>
  </BrResizable>
</template>

Aspect Ratio Locking

You can lock the aspect ratio during resizing by setting the lock-aspect-ratio prop.

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable } from '@breezeui/vue'

const width = ref(300)
const height = ref(168.75) // 16:9 ratio
</script>

<template>
  <BrResizable
    v-model:width="width"
    v-model:height="height"
    :lock-aspect-ratio="16/9"
    :min-width="200"
    :max-width="600"
    class="border border-border rounded-lg overflow-hidden relative shadow-sm"
  >
    <img 
      src="https://images.unsplash.com/photo-1682687220063-4742bd7fd538?q=80&w=2070&auto=format&fit=crop" 
      class="w-full h-full object-cover pointer-events-none"
      alt="demo"
    />
    <div class="absolute inset-0 flex items-center justify-center bg-black/30 text-white font-medium">
      16:9 ({{ Math.round(width) }} x {{ Math.round(height) }})
    </div>
  </BrResizable>
</template>

Panel Splitting

Combine BrResizableSplit and BrResizableSplitPane to create a multi-panel split layout.

<script setup lang="ts">
import {
  BrResizableSplit,
  BrResizableSplitPane,
  BrResizableHandle
} from '@breezeui/vue'
</script>

<template>
  <BrResizableSplit direction="horizontal" class="h-[300px] w-full rounded-lg border shadow-sm">
    <BrResizableSplitPane :default-size="25" :min-size="15">
      <div class="flex h-full flex-col bg-muted/10">
        <div class="p-4 font-medium border-b">Sidebar</div>
        <div class="p-4 text-sm text-muted-foreground flex-1">Keyboard navigation supported</div>
      </div>
    </BrResizableSplitPane>
    
    <BrResizableHandle split with-handle />
    
    <BrResizableSplitPane :default-size="75">
      <BrResizableSplit direction="vertical">
        <BrResizableSplitPane :default-size="60">
          <div class="flex h-full items-center justify-center bg-background">
            <span class="font-medium text-foreground">Main Content</span>
          </div>
        </BrResizableSplitPane>
        
        <BrResizableHandle split with-handle class="hover:bg-primary/50 transition-colors" />
        
        <BrResizableSplitPane :default-size="40">
          <div class="flex h-full items-center justify-center bg-muted/20">
            <span class="font-medium text-muted-foreground">Terminal / Console</span>
          </div>
        </BrResizableSplitPane>
      </BrResizableSplit>
    </BrResizableSplitPane>
  </BrResizableSplit>
</template>

Integration with Table

The Resizable component can be easily integrated with the Table component to create adaptive data grids.

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrCard, BrTable } from '@breezeui/vue'

const tableWidth = ref(450)
const tableHeight = ref(300)

const tableData = [
  { id: 1, name: 'John Doe', role: 'Developer' },
  { id: 2, name: 'Jane Smith', role: 'Designer' },
  { id: 3, name: 'Mike Johnson', role: 'Manager' },
]

const columns = [
  { key: 'id', title: 'ID' },
  { key: 'name', title: 'Name' },
  { key: 'role', title: 'Role' },
]
</script>

<template>
  <BrResizable
    v-model:width="tableWidth"
    v-model:height="tableHeight"
    :min-width="300"
    :min-height="200"
    class="flex flex-col"
  >
    <BrCard class="w-full h-full flex flex-col overflow-hidden">
      <div class="p-4 border-b font-medium bg-muted/30">Resizable Table Card</div>
      <div class="flex-1 overflow-auto p-4">
        <BrTable :data="tableData" :columns="columns" />
      </div>
    </BrCard>
  </BrResizable>
</template>

Integration with Card

Place a Card inside a resizable container and utilize internal scroll areas for more content.

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrCard, BrCardHeader, BrCardTitle, BrCardDescription, BrCardContent } from '@breezeui/vue'

const cardWidth = ref(400)
const cardHeight = ref(250)
</script>

<template>
  <BrResizable
    v-model:width="cardWidth"
    v-model:height="cardHeight"
    :min-width="250"
    :min-height="150"
    class="flex flex-col"
  >
    <BrCard class="w-full h-full flex flex-col overflow-hidden shadow-md">
      <BrCardHeader>
        <BrCardTitle>Resizable Card</BrCardTitle>
        <BrCardDescription>Drag the handle to resize this card component.</BrCardDescription>
      </BrCardHeader>
      <BrCardContent class="flex-1 overflow-auto text-muted-foreground">
        <p class="mb-4">
          This is a card component nested inside a resizable container.
          Notice how the card header stays fixed while the content area scrolls if it overflows.
        </p>
        <p>
          You can integrate the Resizable component with almost any other UI element to create dynamic layouts.
        </p>
      </BrCardContent>
    </BrCard>
  </BrResizable>
</template>

API

Resizable Props

NameDescriptionTypeDefault
asThe HTML element to renderstring'div'
width / v-model:widthContainer widthnumber | string-
height / v-model:heightContainer heightnumber | string-
minWidthMinimum widthnumber0
maxWidthMaximum widthnumberInfinity
minHeightMinimum heightnumber0
maxHeightMaximum heightnumberInfinity
directionsAllowed resize directions('top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right')[]['right', 'bottom', 'bottom-right']
lockAspectRatioLock aspect ratio. Can be a boolean (locks initial ratio) or a specific numeric ratioboolean | numberfalse
gridSnapping grid size. Can be a single number or a [x, y] arraynumber | [number, number]1
stepKeyboard resize stepnumber10
disabledWhether resizing is disabledbooleanfalse

Resizable Emits

NameDescriptionCallback Parameters
update:widthFired when width changes(width: number) => void
update:heightFired when height changes(height: number) => void
resizeStartFired when resizing starts(event: { width: number; height: number; direction: string }) => void
resizeFired during resizing(event: { width: number; height: number; direction: string }) => void
resizeEndFired when resizing ends(event: { width: number; height: number; direction: string }) => void

ResizableHandle Props

NameDescriptionTypeDefault
directionThe direction the handle controls (used in non-Split mode)string-
splitWhether it acts as a handle for Split modebooleanfalse
withHandleWhether to show the built-in icon in Split modebooleanfalse

ResizableSplit Props

Based on SplitterGroup from radix-vue. Passes through all its props.

NameDescriptionTypeDefault
directionSplit direction'horizontal' | 'vertical'-

ResizableSplitPane Props

Based on SplitterPanel from radix-vue. Passes through all its props.

NameDescriptionTypeDefault
defaultSizeDefault size percentagenumber-
minSizeMinimum size percentagenumber0
maxSizeMaximum size percentagenumber100