Atomic Angular — Doc interne

Resizable

Lay a row or column of panels out and let the user move the boundary between two of them, by dragging or by arrowing the handle.

ResizablePanelGroup lays a row (or column) of panels out and lets the user move the boundary between two of them, by dragging the handle or by arrowing it. Three components work together: ResizablePanelGroup (the flex container and single owner of the sizes), ResizablePanel (one region, sized as a share of the group) and ResizableHandle (the divider, focusable and draggable).

Concept — percentages, not pixels

Sizes are percentages of the group, never pixels — that is what makes a layout survive a window resize. The group normalizes whatever defaultSizes you give so they sum to 100.

Minimal example

The group takes the full size of its parent (size-full), so it needs a parent with a height — one in a plain <div> with no height collapses.

Two panels, side by side
sample.component.ts
import { Component } from "@angular/core";
import { ResizableHandleComponent, ResizablePanelComponent, ResizablePanelGroupComponent } from "@sinequa/galactik";

@Component({
  selector: "sample-component",
  imports: [ResizablePanelGroupComponent, ResizablePanelComponent, ResizableHandleComponent],
  template: `
    <div class="h-96">
      <resizable-panel-group direction="horizontal">
        <resizable-panel [defaultSize]="30" [minSize]="20">Filters</resizable-panel>
        <resizable-handle [withHandle]="true" aria-label="Resize the filters panel" />
        <resizable-panel [defaultSize]="70">Results</resizable-panel>
      </resizable-panel-group>
    </div>
  `,
})
export class SampleComponent {}

How it works

minSize/maxSize per panel are honoured by both the pointer and the keyboard — what one panel refuses to take, its neighbour keeps, so the total always stays at 100%. The handle implements the ARIA window-splitter pattern: role="separator", aria-orientation (the orientation of the line, not of the group — a separator between side-by-side panels is vertical), and aria-valuenow/valuemin/valuemax bounded by the panel before it. Arrow keys move the focused handle by 10% of the group, along the axis the group's direction implies — a resize is therefore fully reachable without a pointer (WCAG 2.1.1). Dragging uses pointer events with pointer capture, so the drag follows the pointer far outside the handle with no window-level listener, and a full-page overlay during the drag keeps an <iframe> in a panel from swallowing the pointer.

Recipes

Remembering the layout

Give the group an autoSaveId and the layout is remembered in localStorage, under the key resizable-panel:<autoSaveId> — restored on the next visit, but only if the group still has the same number of panels; otherwise the panels' own defaultSizes are the better answer.

<resizable-panel-group direction="horizontal" autoSaveId="search-results">…</resizable-panel-group>

Driving the layout from the outside

Reading and resetting the layout from a button
sample.component.ts
import { Component, signal } from "@angular/core";
import { ResizableHandleComponent, ResizablePanelComponent, ResizablePanelGroupComponent } from "@sinequa/galactik";

@Component({
  selector: "sample-component",
  imports: [ResizablePanelGroupComponent, ResizablePanelComponent, ResizableHandleComponent],
  template: `
    <resizable-panel-group #group="resizablePanelGroup" direction="horizontal" (layout)="sizes.set($event)">
      <resizable-panel [defaultSize]="50">Left</resizable-panel>
      <resizable-handle [withHandle]="true" aria-label="Resize panels" />
      <resizable-panel [defaultSize]="50">Right</resizable-panel>
    </resizable-panel-group>
    <button type="button" (click)="group.setLayout([50, 50])">Reset</button>
  `,
})
export class SampleComponent {
  readonly sizes = signal<number[]>([]);
}

setLayout() is ignored unless the array covers exactly the panels present — a partial layout would leave the group summing to something other than 100%.

Options

Prop

Type

ResizablePanel

Prop

Type

ResizableHandle

Prop

Type

Pitfalls

What's next

On this page