rc-splitter
Resizable pane splitter with pointer, keyboard, and collapse/restore controls, following the WAI-ARIA Window Splitter pattern.
- Package
@rcarls/rc-splitter- Element
<rc-splitter>- Native dependency
- Pane content slots
- State model
- Controlled or uncontrolled pane size
- Main events
rc-splitter-change- Related
- Theme previews
Installation
- npm
- Yarn
npm install @rcarls/rc-splitter
yarn add @rcarls/rc-splitter
import '@rcarls/rc-splitter/define';
Live demo
Theming
The default demo mode shows the component without a package theme. Use the shared preview controls on this page to compare inherited, light, and dark color schemes or to apply the optional Material theme only inside the demo frames.
Fixed pane mode
Set mode="fixed" when the primary pane should be a clampable pixel width
while the secondary pane remains fluid. This is useful for canonical
list-detail layouts that start with a fixed list pane but still allow user
resize:
<rc-splitter mode="fixed" min="360" max="412" value="360">
<section>List</section>
<section slot="secondary">Detail</section>
</rc-splitter>
The legacy fixed boolean still means “disable resizing” and is distinct from
mode="fixed".
Anchored settling
Set snap-points to ascending whitespace-separated values in the active mode's
units. Slow releases settle to the nearest point, while a swipe of at least
swipe-velocity (default 500 px/s) moves to the next point in that direction.
Omit snap-points to keep pointer resizing continuous.
<rc-splitter mode="percent" snap-points="25 50 75" value="50">
<section>Primary</section>
<section slot="secondary">Secondary</section>
</rc-splitter>
Use snapTo(index, 'animated') to settle programmatically or
snapTo(index, 'instant') without animation. Animated settling uses
--rc-splitter-snap-duration (default 200ms) and respects reduced motion.
Collapsible panes
Set the collapsible attribute to render a toggle button on the separator that collapses or expands the primary pane:
<rc-splitter collapsible>
<div>Primary</div>
<div slot="secondary">Secondary</div>
</rc-splitter>
The button is also keyboard-accessible: while the separator handle has focus, Ctrl+Arrow (left/right for horizontal, up/down for vertical) toggles collapse. The Enter key on the handle itself also toggles collapse independently of the collapsible attribute.
Style the button via ::part(collapse-button). Its position and size are configurable through --rc-splitter-collapse-button-size (default 20px), --rc-splitter-collapse-button-offset, and the button color/background tokens.
Touch targets
A 24 px diameter transparent circle is always present on the drag handle as a ::after pseudo-element. It is non-interactive for mouse and keyboard. On touch devices (any-pointer: coarse) it becomes hittable, providing a WCAG 2.5.8-compliant touch target without widening the visual separator. The visual indicator and touch target are intentionally decoupled.
The #separator uses z-index: 1 so the touch target circle stacks above pane content when it extends past the separator strip edge, ensuring touch events are captured correctly.
Visual indicator
The default drag indicator is a 3-dot grip, rendered as a ::before pseudo-element inside the handle so the touch target (the handle element itself) and the visual remain independent. Hover highlights are scoped to the visual indicator only — they do not highlight the full separator strip.
Customise the indicator via CSS custom properties:
rc-splitter {
/* Switch from dots to a solid line or pill */
--rc-splitter-handle-pattern: none;
--rc-splitter-handle-fill: ButtonBorder;
--rc-splitter-handle-border-radius: 999px; /* pill shape */
--rc-splitter-handle-hover-fill: Highlight; /* hover state on indicator only */
--rc-splitter-handle-transition: 150ms ease;
}
Material 3 theme
When rc-theme-material is applied, the splitter uses an M3-style drag indicator modelled after the M3 bottom sheet drag handle:
- Strip width: 24 px (
1.5rem) — meets WCAG 2.5.8 in the cross axis (combined with the 24 px touch target circle on touch devices) - Indicator: 4 px × 32 px fully-rounded pill (
--rc-splitter-handle-pattern: none,--rc-splitter-handle-fill,--rc-splitter-handle-border-radius: 999px) - Color:
on-surface-variantat 40 % opacity - Hover:
primaryat 8 % opacity — on the pill only, not the strip - Transition: 150 ms with M3 standard easing
All tokens are CSS custom properties so individual values can be overridden without replacing the whole theme layer.
API
Properties
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
label | label | string | 'Splitter' | Accessible label for this splitter. Default label is 'Splitter'. |
orientation | orientation | SplitterOrientation | 'horizontal' | Splitter orientation, for keyboard navigation and initial sizing. |
mode | mode | SplitterMode | 'length' | Determines value units and pane sizing behavior. - `length` uses px for the primary pane size. - `percent` uses percentage points. - `fixed` uses px for a clampable fixed primary pane while the secondary pane flexes. |
step | step | number | 1 | The step size for resizing, in either pixels or percentage points depending on `mode`. |
min | min | number | 0 | Minimum size of the primary pane in current mode units. Defaults to 0. |
max | max | number | undefined | undefined | Maximum size of the primary pane in current mode units. Defaults to the full container size. |
value | value | Unknown | Not specified | The current splitter value, corresponding to the separator position, in either pixels or percentage points depending on `mode`. |
defaultValue | default-value | Unknown | Not specified | Initial uncontrolled splitter value. |
fixed | fixed | boolean | false | Disables pointer and keyboard resizing without changing pane size. |
collapsible | collapsible | boolean | false | Renders a collapse/expand toggle button on the separator. |
snapPoints | snap-points | string | '' | Ascending whitespace-separated snap points in the current mode's units. An empty value keeps pointer resizing continuous. |
swipeVelocity | swipe-velocity | number | 500 | Minimum release velocity, in px/s, that qualifies as a swipe. |
valueText | JS property only | Unknown | Not specified | A human-readable string representation of the value. |
Methods
| Method | Description |
|---|---|
snapTo(index: number, behavior: 'animated' | 'instant') | Settles at a declared snap point by zero-based index. Finite fractional indices are truncated and clamped. Calls without valid snap points or with a non-finite index have no effect. |
Events
| Event | Detail type | Description |
|---|---|---|
rc-splitter-change | CustomEvent | Fired when the separator position changes; `detail: { value, valueText }` |
Slots
| Name | Description |
|---|---|
(default) | Primary pane contents |
secondary | Secondary pane contents (optional) |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-splitter-separator-size | 6px | Thickness of the separator bar |
--rc-splitter-separator-handle-size | 100% | Length of the drag handle area within the separator (also the length of the visible indicator) |
--rc-splitter-separator-color | color-mix(in srgb, ButtonBorder 35%, Canvas 65%) | Separator background color |
--rc-splitter-keyline | 1px solid ButtonBorder | Shared separator keyline border |
--rc-splitter-handle-color | ButtonBorder | Color of the dot grip indicators (default visual) |
--rc-splitter-handle-thickness | 4px | Cross-axis size of the visual indicator (dot column width or pill/line thickness) |
--rc-splitter-handle-border-radius | 0 | Border-radius of the visual indicator; set to a large value (e.g. 999px) for a pill shape |
--rc-splitter-handle-pattern | <3-dot radial-gradient> | background-image for the visual indicator; set to `none` to use a solid fill via --rc-splitter-handle-fill instead |
--rc-splitter-handle-fill | transparent | background-color of the visual indicator; effective when --rc-splitter-handle-pattern is none (e.g. for a solid pill) |
--rc-splitter-handle-hover-fill | transparent | background-color of the visual indicator on hover; scoped to the indicator element only, not the full separator strip |
--rc-splitter-handle-transition | 0ms | CSS transition duration/easing for the visual indicator's background-color changes |
--rc-splitter-snap-duration | 200ms | Duration of anchored settling after pointer release or `snapTo()` |
--rc-splitter-collapse-button-size | 20px | Diameter of the collapse/expand toggle button |
--rc-splitter-collapse-button-offset | 8px | Distance from the start edge of the separator to the collapse button center |
--rc-splitter-collapse-button-bg | Canvas | Collapse button background color |
--rc-splitter-collapse-button-hover-bg | ButtonFace | Collapse button background color on hover |
--rc-splitter-collapse-button-border | ButtonBorder | Collapse button border color |
--rc-splitter-collapse-button-color | ButtonText | Collapse button icon color |
--rc-splitter-separator-border-inline-start | 1px solid ButtonBorder | Inline-start border |
--rc-splitter-separator-border-inline-end | 1px solid ButtonBorder | Inline-end border |
--rc-splitter-separator-border-block-start | 1px solid ButtonBorder | Block-start border (vertical orientation) |
--rc-splitter-separator-border-block-end | 1px solid ButtonBorder | Block-end border (vertical orientation) |
CSS Parts
| Part | Description |
|---|---|
primary | Primary pane container |
secondary | Secondary pane container |
separator | The separator bar |
separator-handle | The focusable drag handle |
collapse-button | The collapse/expand toggle button (only rendered when `collapsible` is set) |