The Resizable follows a strict pattern using reusable components (NResizablePanelGroup
, NResizablePanel
, NResizableHandle
) listed in the Components section, similar to libraries like shadcn/ui
. This atomic way is necessary due to complex nesting requirements of resizable layers.
Examples
Basic
Prop | Default | Type | Description |
---|---|---|---|
autoSaveId | null | string , null | Unique id used to auto-save group arrangement via localStorage . |
direction* | - | vertical , horizontal | The group orientation of resizable; required prop |
id | - | string , null | Group id; falls back to useId when not provided. |
keyboardResizeBy | 10 | number , null | Step size when arrow key was pressed. |
storage | defaultStorage | PanelGroupStorage | Custom storage API; defaults to localStorage |
You can use the autoSaveId
prop to persist the layout data in localStorage
. Try changing the layout and then refresh the page to see the layout persist.
Preview
Code
Read more in Reka Splitter Root API.
Handle
Prop | Default | Type | Description |
---|---|---|---|
icon | i-lucide-grip-vertical | boolean , string | Add an icon to the resizable handle, falls back to i-lucide-grip-vertical when true . |
resizableHandle | solid-gray | {variant}-{color} | Custom handle color of resizable handle. Note that this does not apply to the icon. |
disabled | - | boolean | Disable drag handle |
id | - | string | Resize handle id (unique within group); falls back to useId when not provided. |
Variant | Description |
---|---|
solid | Uses border to create a solid handle. |
outline | Uses ring to create an outline handle. |
~ | The unstyle or base variant |
Preview
Code
Read more in Reka Splitter Handle API.
Panel
Prop | Default | Type | Description |
---|---|---|---|
collapsedSize | - | number | The size of panel when it is collapsed. |
collapsible | - | boolean | Should panel collapse when resized beyond its minSize . When true , it will be collapsed to collapsedSize . |
id | - | string , null | Panel id (unique within group); falls back to useId when not provided. |
defaultSize | - | number | Initial size of panel (numeric value between 1-100) |
maxSize | - | number | The maximum allowable size of panel (numeric value between 1-100); defaults to 100 |
minSize | - | number | The minimum allowable size of panel (numeric value between 1-100); defaults to 10 |
order | - | number | The order of panel within group; required for groups with conditionally rendered panels |
Preview
Code
Read more in Reka Splitter Panel API.
Presets
shortcuts/resizable.ts
type ResizablePrefix = 'resizable'
export const staticResizable: Record<`${ResizablePrefix}-${string}`, string> = {
// config
'resizable-handle-icon-name': 'i-lucide-grip-vertical',
// base
'resizable-panel-group': 'flex h-full w-full data-[orientation=vertical]:flex-col',
'resizable-panel': '',
'resizable-handle': 'relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:ring-offset-base focus-visible:outline-none data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full data-[orientation=vertical]:after:left-0 data-[orientation=vertical]:after:h-1 data-[orientation=vertical]:after:w-full data-[orientation=vertical]:after:-translate-y-1/2 data-[orientation=vertical]:after:translate-x-0 [&[data-orientation=vertical]>div]:rotate-90',
'resizable-handle-icon-wrapper': 'bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border',
'resizable-handle-icon': 'square-2.5',
// static variants
'resizable-handle-solid-gray': 'bg-border focus-visible:ring-foreground/58',
'resizable-handle-solid-black': 'bg-inverted focus-visible:ring-foreground/58',
'resizable-handle-outline-gray': 'ring-1 ring-base focus-visible:ring-foreground/58',
}
export const dynamicResizable: [RegExp, (params: RegExpExecArray) => string][] = [
[/^resizable-handle-solid(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-200 dark:bg-${c}-700/58 focus-visible:ring-${c}-200 dark:focus-visible:ring-${c}-700/58`],
[/^resizable-handle-outline(-(\S+))?$/, ([, , c = 'gray']) => `ring-1 ring-${c}-200 dark:ring-${c}-700/58 focus-visible:ring-${c}-200 dark:focus-visible:ring-${c}-700/58`],
]
export const resizable = [
...dynamicResizable,
staticResizable,
]
Props
types/resizable.ts
import type { SplitterGroupProps, SplitterPanelProps, SplitterResizeHandleProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
interface BaseExtension {
/** CSS class for the component */
class?: HTMLAttributes['class']
}
export interface NResizablePanelGroupProps extends SplitterGroupProps, BaseExtension {
/** Additional properties for the una component */
una?: Pick<NResizableUnaProps, 'resizablePanelGroup'>
}
export interface NResizablePanelProps extends SplitterPanelProps, BaseExtension {
/** Additional properties for the una component */
una?: Pick<NResizableUnaProps, 'resizablePanel'>
}
export interface NResizableHandleProps extends SplitterResizeHandleProps, BaseExtension {
/**
* Allows you to add `UnaUI` resizable handle preset properties,
* Think of it as a shortcut for adding options or variants to the preset if available.
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/resizable.ts
* @example
* resizable-handle="outline-yellow"
*/
resizableHandle?: string
/**
* Custom handle icon
* @example
* icon="i-lucide-grip-vertical"
*/
icon?: boolean | HTMLAttributes['class']
/** Additional properties for the una component */
una?: Pick<NResizableUnaProps, 'resizableHandle' | 'resizableHandleIconWrapper' | 'resizableHandleIcon'>
}
interface NResizableUnaProps {
/** CSS class for the resizable panel */
resizablePanel?: HTMLAttributes['class']
/** CSS class for the resizable panel group */
resizablePanelGroup?: HTMLAttributes['class']
/** CSS class for the resizable handle */
resizableHandle?: HTMLAttributes['class']
/** CSS class for the resizable handle icon wrapper */
resizableHandleIconWrapper?: HTMLAttributes['class']
/** CSS class for the resizable handle icon */
resizableHandleIcon?: HTMLAttributes['class']
}
Components
ResizablePanelGroup.vue
ResizableHandle.vue
ResizablePanel.vue
<script setup lang="ts">
import type { SplitterGroupEmits } from 'reka-ui'
import type { NResizablePanelGroupProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { SplitterGroup, useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NResizablePanelGroupProps>()
const emits = defineEmits<SplitterGroupEmits>()
const delegatedProps = reactiveOmit(props, ['class', 'una'])
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SplitterGroup
v-slot="{ ...slotProps }"
data-slot="resizable-panel-group"
v-bind="forwarded"
:class="cn(
'resizable-panel-group',
props.una?.resizablePanelGroup,
props.class,
)"
>
<slot v-bind="slotProps" />
</SplitterGroup>
</template>