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
- Usage rulesTheme previews
Installation
- npm
- Yarn
- pnpm
npm install @rcarls/rc-webcomponents@next
yarn add @rcarls/rc-webcomponents@next
pnpm add @rcarls/rc-webcomponents@next
import '@rcarls/rc-webcomponents/rc-splitter/define';
Usage
Put the primary pane in the default slot and the secondary pane in the secondary slot, and
name the primary pane with label.
<rc-splitter label="Recipe list" style="inline-size: 100%; block-size: 400px">
<div>Recipe list</div>
<div slot="secondary">Recipe details</div>
</rc-splitter>
Live demo
Accessibility
The separator handle has role="separator", takes focus, and is labelled by the primary pane,
whose name comes from label. It reports its position with aria-valuenow, aria-valuemin,
aria-valuemax, and a localized aria-valuetext, and aria-controls the primary pane. With
collapsible, a native collapse button also controls the pane and reports aria-expanded.
| Key | Action |
|---|---|
| Right Arrow, Down Arrow | Grows the primary pane by step. |
| Left Arrow, Up Arrow | Shrinks the primary pane by step. |
| Shift+Arrow | Moves ten times as far. |
| Home, End | Moves to the smallest or largest primary pane size. |
| Enter | Collapses the primary pane, or restores its last size. |
Left and Right swap in right-to-left text. See the WAI-ARIA Window Splitter pattern.
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.
Customize 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 modeled 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.
Localization
Built-in text follows the element's own lang, else <html lang>. Override it with
messages, or register it for every instance under rc-splitter: label, expand, collapse. See
Localization.
API
Properties
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
messages | JS property only | Partial<RCSplitterMessages> | undefined | Not specified | Built-in text overrides; unset keys fall back to registered text, then English. |
label | label | string | undefined | Not specified | Accessible label for this splitter. Defaults to the `label` message. |
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 | The value as a CSS length, such as `40%` or `240px`. |
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) |