Primitives
Pane Resizer
A focusable, draggable, and keyboard-operable divider that resizes the panes on either side, with an optional collapse behaviour.
Drag the divider, or focus it and press the arrow keys. The resizer is a separator carrying aria-valuenow, so the split is announced as it moves.
It carries role="separator" plus live aria-value*, is tabbable, handles arrow / Page / Home / End keys, and drives a pointer-drag resize with setPointerCapture. It is essentially a 1-D slider wearing a separator role; the static visual divider lives in the separate ForSeparator primitive so a plain <hr forSeparator> never pulls the drag / keyboard-resize code in.
Anatomy
<div class="split">
<section id="pane-a">…</section>
<div
forPaneResizer
orientation="vertical"
[(value)]="size"
[min]="120"
[max]="640"
aria-controls="pane-a pane-b"
aria-label="Resize panes"
></div>
<section id="pane-b">…</section>
</div>
Examples
States
One class and one directive, two states. disabled drops the resizer out of the tab order and blocks both keyboard and pointer resizing; it reflects aria-disabled and data-disabled, so the dimmed divider and the live one come from the same stylesheet.
Collapsible panel
With collapsible on, Enter / Space on the focused resizer snaps the panel to its min and a second press restores the last expanded size. This is APG-optional behaviour for a resizer that backs a collapsible pane. Drag or the arrow keys still resize as usual.
Focus the divider and press Enter to collapse the sidebar, then Enter again to bring it back to 220px.
Pointer drag
pointerdown captures the pointer, records the starting value, and on each pointermove adds the raw px delta along the resize axis to value, clamped to [min, max]. Use this directly for px-unit layouts; for percentage / fractional layouts, listen to (resizing) and translate yourself, or skip pointer drag and stick to keyboard.
The press also focuses the divider. Starting a drag calls preventDefault() (so the gesture never turns into a text selection), which suppresses the browser's native focus-on-press, so the directive focuses the host itself. Keyboard fine-tuning continues from where the drag ended instead of needing a Tab first. Ship a visible :focus-visible style for the divider (as the example above does) so the focus ring appears for keyboard users without flashing after every mouse drag.
Escape (or a pointercancel) mid-drag restores the pre-drag value through [(value)] and emits no (resizeCommit). Unmounting the resizer mid-drag reverts too, but the destroyed [(value)] model can no longer emit, so the pre-drag value is reported through the [valueRevert] callback instead. Bind it as a function reference when you persist the size and the pane layout can disappear during a gesture:
<div forPaneResizer [(value)]="size" [valueRevert]="onValueRevert"></div>
readonly onValueRevert = (value: number): void => {
this.persistedSize.set(value);
};
API
ForPaneResizer
| Property | Type | Description |
|---|---|---|
orientation | input | Axis the divider line runs along. The resize axis runs perpendicular. Default: 'horizontal' |
disabled | input | Drops the resizer out of tab order; reflects aria-disabled / data-disabled; blocks keyboard / pointer.Default: — |
value | model | Two-way bindable value along the resize axis. Units are consumer-defined (px, %, fr…). Default: — |
min | input | Lower bound. Default: 0 |
max | input | Upper bound. Default: 100 |
step | input | Step applied by ArrowKeys. Default: 1 |
largeStep | input | Step applied by Page Up / Page Down.Default: 10 |
valueText | input | Optional aria-valuetext string for human-readable values.Default: — |
controls | input | Space-separated list of pane ids surfaced as aria-controls.Default: — |
collapsible | input | Opt-in Enter / Space toggle: collapses to min, and on the next press restores the last size the resizer settled on above min (from a drag, a keyboard burst, or a previous collapse). Falls back to max when no such size exists yet.Default: — |
dir | input | Reading direction. RTL inverts ArrowLeft / ArrowRight and the horizontal axis of pointer drag. Default: — |
valueChange | output | Output. Implicit emitter from model(). Fires on internal updates only and stays silent on consumer writes via [(value)].Default: — |
resizing | output | Output. Verb-named alias for valueChange. Useful when wiring one-way without [(value)].Default: — |
resizeCommit | output | Output. Fires once at the end of a resize burst (key release, pointerup, pointercancel, or blur while a key is still held). Persist final size here.Default: — |
valueRevert | ((value: number) => void) | undefined | Teardown-only revert callback, bound as a function reference. Called with the pre-drag value when the resizer is destroyed mid-drag, where [(value)] can no longer emit. Silent on the Escape / pointercancel reverts.Default: — |
| Data attribute | Values | |
|---|---|---|
data-orientation | horizontal | vertical | |
data-disabled | present | absent |
Keyboard
| Key | Action | |
|---|---|---|
ArrowLeft / ArrowRight | Move along the resize axis for a vertical separator (horizontal pane stack). RTL inverts the horizontal pair. | |
ArrowUp / ArrowDown | Move along the resize axis for a horizontal separator (vertical pane stack). | |
Page Up | Apply largeStep toward max. | |
Page Down | Apply largeStep toward min. | |
Home | Snap to min. | |
End | Snap to max. | |
Enter / Space | Toggle collapse to min / restore the last size settled on above min. That size can come from a drag or a keyboard burst, so a drag-to-min then Enter returns to the pre-drag size (max is the never-sized-yet fallback). Only when collapsible is enabled (off by default). |
Accessibility
Implements the WAI-ARIA Window Splitter pattern.
- Follows the Window Splitter pattern verbatim.
aria-orientationis reflected explicitly (both'horizontal'and'vertical') so AT can announce the resize axis.aria-controlsis recommended: point it at the panes the resizer splits so AT can relate them.
aria-valuetextwhen the bare number is not meaningful (e.g."30 percent of viewport").data-disabledis reflected whendisabledis true so consumers can flip styling and pointer affordances in CSS.- Accessible name. Provide a name via native
aria-label/aria-labelledbyon the host so AT announces what the resizer adjusts.
Styling
forty-cdk ships no styles: put your own class on each piece and key your CSS off the data-* attributes listed under API, not off the for* selectors (Styling forty-cdk explains why).
.pr-resizer[data-orientation='vertical'] {
cursor: col-resize;
}
.pr-resizer[data-orientation='horizontal'] {
cursor: row-resize;
}
.pr-resizer[data-disabled] {
cursor: default;
opacity: 0.5;
}