Resizable
Draggable resizable panel groups for building adjustable split-view layouts.
The Resizable component provides a panel group system for building split-view layouts with draggable handles. Built on react-native-gesture-handler and react-native-reanimated, it supports horizontal and vertical directions, percentage-based sizing, and an optional grip handle indicator.
Preview
Installation
npx novaui-cli add resizableImport
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from 'novaui-components'Basic Usage
The Resizable is a compound component with these parts:
ResizablePanelGroup— Root container withflex-1 w-full h-full overflow-hiddenand direction-based flex (flex-roworflex-col)ResizablePanel— Individual panel withoverflow-hiddenand percentage-based width/heightResizableHandle— Draggable divider withrelative flex items-center justify-center z-10 bg-transparentand an optional grip icon
import { View, Text } from 'react-native'
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from 'novaui-components'
export function BasicResizable() {
return (
<ResizablePanelGroup
direction="horizontal"
className="h-48 w-full rounded-lg border border-border"
>
<ResizablePanel defaultSize={50}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Panel One</Text>
</View>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Panel Two</Text>
</View>
</ResizablePanel>
</ResizablePanelGroup>
)
}Vertical Direction
Set direction="vertical" for a top-and-bottom split layout:
import { View, Text } from 'react-native'
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from 'novaui-components'
export function VerticalResizable() {
return (
<ResizablePanelGroup
direction="vertical"
className="h-64 w-full rounded-lg border border-border"
>
<ResizablePanel defaultSize={40}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Top Panel</Text>
</View>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={60}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Bottom Panel</Text>
</View>
</ResizablePanel>
</ResizablePanelGroup>
)
}Three-Panel Layout
Create multi-panel layouts by adding more panels and handles:
import { View, Text } from 'react-native'
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from 'novaui-components'
export function ThreePanelLayout() {
return (
<ResizablePanelGroup
direction="horizontal"
className="h-48 w-full rounded-lg border border-border"
>
<ResizablePanel defaultSize={25}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Sidebar</Text>
</View>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={50}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Content</Text>
</View>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={25}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Details</Text>
</View>
</ResizablePanel>
</ResizablePanelGroup>
)
}Without Handle Grip
Omit the withHandle prop for a minimal divider line:
<ResizablePanelGroup
direction="horizontal"
className="h-48 w-full rounded-lg border border-border"
>
<ResizablePanel defaultSize={50}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Left</Text>
</View>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize={50}>
<View className="flex-1 items-center justify-center">
<Text className="text-sm font-medium text-foreground">Right</Text>
</View>
</ResizablePanel>
</ResizablePanelGroup>Props API
ResizablePanelGroup Props
Extends React.ComponentPropsWithoutRef<typeof View>:
| Prop | Type | Default | Description |
|---|---|---|---|
direction | 'horizontal' | 'vertical' | 'horizontal' | Layout direction of the panels (flex-row or flex-col) |
className | string | - | Additional CSS classes (base: flex-1 w-full h-full overflow-hidden) |
ResizablePanel Props
Extends React.ComponentPropsWithoutRef<typeof View>:
| Prop | Type | Default | Description |
|---|---|---|---|
defaultSize | number | Auto-distributed | Initial panel size as a percentage (0–100). Undefined sizes split the remaining space equally |
minSize | number | 0 | Minimum panel size as a percentage |
maxSize | number | 100 | Maximum panel size as a percentage |
className | string | - | Additional CSS classes (base: overflow-hidden) |
ResizableHandle Props
Extends React.ComponentPropsWithoutRef<typeof View>:
| Prop | Type | Default | Description |
|---|---|---|---|
withHandle | boolean | false | Show a grip icon indicator on the handle (uses GripVertical or GripHorizontal from lucide) |
className | string | - | Additional CSS classes (horizontal base: w-8 h-full -mx-4, vertical base: h-8 w-full -my-4) |
Best Practices
-
Sizes should sum to 100: Panel
defaultSizevalues should add up to 100. If they don't, the component normalizes them proportionally. -
Use
withHandlefor discoverability: The grip icon signals to users that the divider is draggable. Omit it only in minimal or advanced layouts. -
Set min/max constraints: Use
minSizeandmaxSizeon panels to prevent them from collapsing to zero or taking over the entire layout. -
Gesture handler setup: This component requires
react-native-gesture-handlerandreact-native-reanimated. Ensure your app is wrapped in aGestureHandlerRootView. -
Negative margins on handles: The handle uses
-mx-4(horizontal) or-my-4(vertical) for a larger touch target. Keep this in mind when styling adjacent content.
Related Components
- Use
ScrollAreafor scrollable content within panels - Combine with
Cardto place structured content in each panel - Use
Separatorfor non-interactive visual dividers