Panes
Resizable splits, with a keyboard-resizable 48 px separator between the panes.
On this page
Anchor watch
Depth alarm
Weather watch
Radius 40 m; the vessel is 12 m from the anchor.
Code
import { Group, Panel, Separator } from "@bosunkit/components/panes";
import { Box } from "@bosunkit/components/box";
import { Stack } from "@bosunkit/components/stack";
import { Text } from "@bosunkit/components/text";
import type { ReactElement } from "react";
export const description: string =
"A list and its detail, resized by a drag or the arrow keys on the 48 px separator between them.";
export default function Split(): ReactElement {
return (
<Group style={{ height: 260 }} orientation="horizontal">
<Panel defaultSize={35} minSize={20}>
<Box padding="md">
<Stack space="sm">
<Text color="text-dim">Anchor watch</Text>
<Text color="text-dim">Depth alarm</Text>
<Text color="text-dim">Weather watch</Text>
</Stack>
</Box>
</Panel>
<Separator />
<Panel minSize={30}>
<Box padding="md">
<Text>Radius 40 m; the vessel is 12 m from the anchor.</Text>
</Box>
</Panel>
</Group>
);
}
Panes are the resizable splits: Group, Panel and Separator for one axis, Grid, Cell and Gridline for
two. A separator is a real role="separator" with aria-valuenow, resized from the keyboard with the arrow keys;
its target is the kit's touch size while the line it draws is 2 px — the line is what the crew see, the target what
the thumb finds.
What a pane may never do. A pane holding readings never collapses to nothing: its minSize comes from the size
class, and collapsible stays for panes whose content is reachable elsewhere. The console's presets are locked by
default, unlocked by an explicit control, so a wet hand or a lurch cannot drag one. The top bar and the alert zone
are never inside a resizable group.
Layouts persist per device. createPanesStorage scopes every layout the library saves to its name and the
window class, so a split chosen on a tablet in landscape does not reshape the same page at window class compact;
hand it to useDefaultLayout with onlySaveAfterUserInteractions, so only a drag or a keystroke saves.
Anchor watch
Depth alarm
Weather watch
Radius 40 m; the vessel is 12 m from the anchor.
Code
import { Group, Panel, Separator } from "@bosunkit/components/panes";
import { Box } from "@bosunkit/components/box";
import { Stack } from "@bosunkit/components/stack";
import { Text } from "@bosunkit/components/text";
import type { ReactElement } from "react";
export const description: string =
"A list and its detail, resized by a drag or the arrow keys on the 48 px separator between them.";
export default function Split(): ReactElement {
return (
<Group style={{ height: 260 }} orientation="horizontal">
<Panel defaultSize={35} minSize={20}>
<Box padding="md">
<Stack space="sm">
<Text color="text-dim">Anchor watch</Text>
<Text color="text-dim">Depth alarm</Text>
<Text color="text-dim">Weather watch</Text>
</Stack>
</Box>
</Panel>
<Separator />
<Panel minSize={30}>
<Box padding="md">
<Text>Radius 40 m; the vessel is 12 m from the anchor.</Text>
</Box>
</Panel>
</Group>
);
}
Content owns the minimum size
Choose minimums from the content that must remain usable: controls, labels, essential readings and their units. Allow secondary reference material to collapse only when a clear control can restore it. Keep resizing separate from ordinary scrolling and verify both horizontal and vertical keyboard behavior.
Use distinct storage names for unrelated layouts and retain the screen/window scope. A saved split is a device preference; it does not belong in a shared vessel record. Check recovery after the viewport becomes smaller than the saved arrangement, including browser zoom and an on-screen keyboard.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | undefined | – | Panel and Separator components that comprise this group. |
| className | string | undefined | – | CSS class name. |
| defaultLayout | Layout | undefined | – | Default layout for the Group. ℹ️ This value allows layouts to be remembered between page reloads. ⚠️ Slight layout shift may occur when server-rendering panels with percentage-based default sizes. Refer to the documentation for suggestions on how to minimize the impact of this. |
| disableCursor | boolean | undefined | – | This library sets custom mouse cursor styles to indicate drag state. Use this prop to disable that behavior for Panels and Separators in this group. |
| disabled | boolean | undefined | – | Disable resize functionality. |
| elementRef | Ref<HTMLDivElement | null> | undefined | – | Ref attached to the root HTMLDivElement. |
| groupRef | Ref<GroupImperativeHandle | null> | undefined | – | Exposes the following imperative API:
- getLayout(): Layout
- setLayout(layout: Layout): void
ℹ️ The useGroupRef and useGroupCallbackRef hooks are exported for convenience use in TypeScript projects. |
| id | string | number | undefined | – | Uniquely identifies this group within an application.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes. |
| onLayoutChange | (layout: Layout) => void | undefined | – | Called when the Group's layout is changing.
⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead. |
| onLayoutChanged | (layout: Layout, meta: LayoutChangedMeta) => void | – | Called after the Group's layout has been changed.
ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
This method is recommended when saving layouts to some storage api.
ℹ️ The second argument contains meta information about the layout change.
The isUserInteraction attribute signals whether the resize was caused by direct user input.
It is true for resizes caused by pointer or keyboard input
and false for other triggers (e.g. imperative API calls, initial mount, etc.)
The requestedLayout attribute is the layout before constraints were applied for the current Group size;
prefer it when persisting layouts. |
| resizePreviewMode | ResizePreviewMode | undefined | – | Controls whether pointer dragging updates Panels sizes immediately,
or renders overlay separator previews until the pointer is released.
Defaults to "panel" (immediate resizing); "separator" defers resizing until release.
Customize previews using the SeparatorOverlay component. |
| resizeTargetMinimumSize | { coarse: number; fine: number; } | – | Minimum size of the resizable hit target area (either Separator or Panel edge)
This threshold ensures are large enough to avoid mis-clicks.
- Coarse inputs (typically a finger on a touchscreen) have reduced accuracy;
to ensure accessibility and ease of use, hit targets should be larger to prevent mis-clicks.
- Fine inputs (typically a mouse) can be smaller
ℹ️ [Apple interface guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility) suggest 20pt (27px) on desktops and 28pt (37px) for touch devices
In practice this seems to be much larger than many of their own applications use though. |
| orientation | "horizontal" | "vertical" | undefined | – | Specifies the resizable orientation ("horizontal" or "vertical"); defaults to "horizontal" |
| style | CSSProperties | undefined | – | CSS properties.
⚠️ The default inline styles cannot be overridden, except for [overflow](https://react-resizable-panels.vercel.app/examples/overflow). |
Also takes every attribute of GroupProps.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | – | CSS class name.
⚠️ Class is applied to nested HTMLDivElement to avoid styles that interfere with Flex layout. |
| collapsedSize | number | string | undefined | – | Panel size when collapsed; defaults to 0%. |
| collapsedThreshold | number | string | undefined | – | Distance a collapsible panel must be resized past its minSize to collapse,
or past its collapsedSize to expand.
Defaults to half the distance between collapsedSize and minSize.
For example if a panel declares collapsedSize="5%", collapsedThreshold="5%", and minSize="25%",
it will collapse when resized below 20% and expands when resized above 10%.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. minSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. minSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation |
| collapsible | boolean | undefined | – | This panel can be collapsed.
ℹ️ A collapsible panel will collapse when it's size is less than of the specified minSize |
| defaultSize | number | string | undefined | – | Default size of Panel within its parent group; default is auto-assigned based on the total number of Panels.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. defaultSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. defaultSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation
⚠️ Percentage based sizes may cause slight layout shift when server-rendering.
For more information see the documentation. |
| disabled | boolean | undefined | – | When disabled, a panel cannot be resized either directly or indirectly (by resizing another panel). |
| elementRef | Ref<HTMLDivElement | null> | undefined | – | Ref attached to the root HTMLDivElement. |
| groupResizeBehavior | "preserve-relative-size" | "preserve-pixel-size" | undefined | – | How should this Panel behave if the parent Group is resized?
Defaults to preserve-relative-size.
- preserve-relative-size: Retain the current relative size (as a percentage of the Group)
- preserve-pixel-size: Retain its current size (in pixels)
ℹ️ Panel min/max size constraints may impact this behavior.
⚠️ A Group must contain at least one Panel with preserve-relative-size resize behavior. |
| id | string | number | undefined | – | Uniquely identifies this panel within the parent group.
Falls back to useId when not provided.
ℹ️ This prop is used to associate persisted group layouts with the original panel.
ℹ️ This value will also be assigned to the id and data-testid attributes. |
| maxSize | number | string | undefined | – | Maximum size of Panel within its parent group; defaults to "100%".
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. maxSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. maxSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation |
| minSize | number | string | undefined | – | Minimum size of Panel within its parent group; defaults to 0%.
ℹ️ Interpretation rules:
- Numbers are interpreted as pixels (e.g. minSize={200} is 200 pixels)
- Strings without explicit units are interpreted as percentage (e.g. minSize="50" is 50 percent)
- Use explicit units (e.g. "px", "%", "em", "rem", "vh", or "vw") to change interpretation |
| onResize | ((panelSize: PanelSize, id: string | number | undefined, prevPanelSize: PanelSize | undefined) => void) | undefined | – | Called when panel sizes change. |
| panelRef | Ref<PanelImperativeHandle | null> | undefined | – | Exposes the following imperative API:
- collapse(): void
- expand(): void
- getSize(): number
- isCollapsed(): boolean
- resize(size: number): void
ℹ️ The usePanelRef and usePanelCallbackRef hooks are exported for convenience use in TypeScript projects. |
| style | CSSProperties | undefined | – | CSS properties.
⚠️ The default inline styles cannot be overridden, except for [overflow](https://react-resizable-panels.vercel.app/examples/overflow). |
Also takes every attribute of PanelProps.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | – | CSS class name.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles
⚠️ The following properties cannot be overridden: flex-grow, flex-shrink |
| disabled | boolean | undefined | – | When disabled, the separator cannot be used to resize its neighboring panels. ℹ️ The panels may still be resized indirectly (while other panels are being resized). To prevent a panel from being resized at all, it needs to also be disabled. |
| disableDoubleClick | boolean | – | When true, double-clicking this Separator will not reset its Panel to its default size. |
| elementRef | Ref<HTMLDivElement> | undefined | – | Ref attached to the root HTMLDivElement. |
| id | string | number | undefined | – | Uniquely identifies the separator within the parent group.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes. |
| preview | ReactNode | – | Overrides the Group default preview for this Separator when resizePreviewMode is "separator". |
| style | CSSProperties | undefined | – | CSS properties.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles
⚠️ The following properties cannot be overridden: flex-grow, flex-shrink |
Also takes every attribute of SeparatorProps.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | undefined | – | Cell and Gridline components that comprise this grid. ⚠️ Cell and Gridline elements must be direct DOM children of their parent Grid element. |
| className | string | undefined | – | CSS class name. |
| columnsrequired | number | GridTrackProps[] | – | Grid columns; either the number of columns or an array of size constraints (one per column). |
| defaultLayout | Partial<GridLayout> | undefined | – | Default layout for either or both of the Grid's axes. ℹ️ This value allows layouts to be remembered between page reloads. |
| disableCursor | boolean | undefined | – | This library sets custom mouse cursor styles to indicate drag state. Use this prop to disable that behavior for this grid. |
| disabled | boolean | undefined | – | Disable resize functionality. |
| elementRef | Ref<HTMLDivElement | null> | undefined | – | Ref attached to the root HTMLDivElement. |
| gridRef | Ref<GridImperativeHandle | null> | undefined | – | Exposes the following imperative API:
- getLayout(): GridLayout
- getTrackById(axis: "column" | "row", id: string | number): GridTrackImperativeHandle
- getTrackByIndex(axis: "column" | "row", index: number): GridTrackImperativeHandle
- setLayout(layout: Partial<GridLayout>): GridLayout
ℹ️ The useGridRef and useGridCallbackRef hooks are exported for convenience use in TypeScript projects. |
| id | string | number | undefined | – | Uniquely identifies this grid within an application.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes. |
| onLayoutChange | ((layout: GridLayout) => void) | undefined | – | Called when the Grid's layout is changing.
⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
For most cases, it is recommended to use the onLayoutChanged callback instead. |
| onLayoutChanged | ((layout: GridLayout, meta: LayoutChangedMeta<GridLayout>) => void) | undefined | – | Called after the Grid's layout has been changed. ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released. This method is recommended when saving layouts to some storage api. |
| resizeTargetMinimumSize | { coarse: number; fine: number; } | – | Minimum size of the resizable hit target area (either a Gridline or a Cell edge)
This threshold ensures targets are large enough to avoid mis-clicks.
ℹ️ Refer to the Group prop of the same name for more information. |
| rowsrequired | number | GridTrackProps[] | – | Grid rows; either the number of rows or an array of size constraints (one per row). |
| style | CSSProperties | undefined | – | CSS properties. ⚠️ Grid template and display properties are managed by the Grid and cannot be overridden. |
Also takes every attribute of GridProps.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | undefined | – | Cell contents. |
| className | string | undefined | – | CSS class name. |
| columnrequired | number | – | Index of the (first) column this cell occupies. |
| columnSpan | number | undefined | – | Number of columns this cell spans; defaults to 1. |
| elementRef | Ref<HTMLDivElement | null> | undefined | – | Ref attached to the root HTMLDivElement. |
| id | string | number | undefined | – | Uniquely identifies this cell within the parent grid.
Falls back to useId when not provided.
ℹ️ This value will also be assigned to the id and data-testid attributes. |
| rowrequired | number | – | Index of the (first) row this cell occupies. |
| rowSpan | number | undefined | – | Number of rows this cell spans; defaults to 1. |
| style | CSSProperties | undefined | – | CSS properties. ⚠️ Grid placement properties are managed by the Cell and cannot be overridden. |
Also takes every attribute of CellProps.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | undefined | – | Gridline contents (e.g. a drag handle icon). |
| className | string | undefined | – | CSS class name.
ℹ️ Use the data-separator attribute for custom _hover_ and _active_ styles |
| disabled | boolean | undefined | – | When disabled, the gridline cannot be used to resize its neighboring tracks. ℹ️ The tracks may still be resized indirectly (e.g. by an intersecting gridline or another boundary). To prevent a track from being resized at all, it needs to also be disabled. |
| elementRef | Ref<HTMLDivElement> | undefined | – | Ref attached to the root HTMLDivElement. |
| style | CSSProperties | undefined | – | CSS properties. ⚠️ Grid placement properties are managed by the gridline and cannot be overridden. |
| typerequired | "column" | – | Column gridlines are vertical; they resize the columns on either side of them. Row gridlines are horizontal; they resize the rows on either side of them. |
| column | number | – | When type is "column", the gridline is rendered along the leading (left) edge of this column
(between it and the previous column); must be greater than 0 and less than the number of columns.
When type is "row", index of the first column the gridline is rendered alongside of; defaults to 0. |
| row | number | undefined | – | When type is "column", index of the first row the gridline is rendered alongside of; defaults to 0.
When type is "row", the gridline is rendered along the leading (top) edge of this row
(between it and the previous row); must be greater than 0 and less than the number of rows. |