Skip to content

Layout (Container, Stack, Flex, Grid, Divider)

LayoutSince 0.2.0
import { Container, Divider, Flex, Grid, Stack } from '@absoluteui/components/layout';
import '@absoluteui/components/layout/style.css';

Using Angular? See Layout (Container, Stack, Flex, Grid, Divider) for Angular.

Layout primitives: Container, Stack, Flex (and Inline), Grid, and Divider. The CSS classes are the primary API and work without JavaScript. The JS helpers (Stack.apply, Flex.apply, Grid.apply, Container.apply, Divider.create) give frameworks and AI assistants a validated, typed API. They apply the same classes and custom properties to an element you own. Each returns a handle whose destroy() removes exactly what it added.

  • Spacing siblings with token gaps instead of ad-hoc margins.
  • Responsive card or tile grids that reflow without media queries (aui-grid–auto).
  • Centering page content with a token maximum width (aui-container).
  • Separating groups of content with a semantic or decorative divider.
  • Visual grouping with a border or background: use Surface or Card.
  • Tabular data: use a table (Data Table in a later release).
  • Reordering content visually so it differs from the reading order (flex row-reverse on meaningful sequences).
import { Stack } from '@absoluteui/components/layout';
const list = document.createElement('div');
document.body.append(list);
const stack = Stack.apply(list, { gap: 6 });
stack.update({ align: 'start' });
import { Container, Grid } from '@absoluteui/components/layout';
const page = document.createElement('main');
const tiles = document.createElement('div');
page.append(tiles);
document.body.append(page);
Container.apply(page, { size: 'xl' });
const grid = Grid.apply(tiles, { minColumnWidth: '14rem', gap: 4 });
// Later, when the view is removed:
grid.destroy();
import { Divider, Flex } from '@absoluteui/components/layout';
import { Button } from '@absoluteui/components/button';
const toolbar = document.createElement('div');
const save = document.createElement('button');
save.textContent = 'Save';
Button.enhance(save);
const share = document.createElement('button');
share.textContent = 'Share';
Button.enhance(share);
toolbar.append(save, Divider.create({ orientation: 'vertical' }), share);
document.body.append(toolbar);
Flex.apply(toolbar, { gap: 2, align: 'center', justify: 'end' });
import { Divider } from '@absoluteui/components/layout';
document.body.append(Divider.create({ label: 'or' }));
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { layout: { defaultGap: 3 } } });
import { Divider, Flex, Stack } from '@absoluteui/components/layout';
import '@absoluteui/components/layout/style.css';
const panel = document.querySelector<HTMLElement>('#settings');
const actions = document.querySelector<HTMLElement>('#settings-actions');
if (panel && actions) {
const stack = Stack.apply(panel, { gap: 6 });
panel.insertBefore(Divider.create(), actions);
Flex.apply(actions, { gap: 2, justify: 'end' });
window.addEventListener('pagehide', () => {
stack.destroy();
});
}
<main class="aui-container aui-container--md">
<div class="aui-stack aui-gap-6">
<div class="aui-grid aui-grid--auto aui-gap-4">...tiles...</div>
<hr class="aui-divider">
<div class="aui-inline aui-gap-2 aui-justify-end">...buttons...</div>
</div>
</main>
import { Grid } from '@absoluteui/components/layout';
const tiles = document.querySelector<HTMLElement>('#tiles');
if (tiles) Grid.apply(tiles, { minColumnWidth: '14rem', gap: 4 });
<main class="aui-container aui-container--lg"> centered, max-inline-size, inline padding
<div class="aui-stack aui-gap-6"> flex column + gap
<div class="aui-flex aui-gap-2 aui-justify-between"> wrapping row
...
<div class="aui-divider aui-divider--vertical" role="separator" aria-orientation="vertical">
...
</div>
<hr class="aui-divider"> semantic horizontal separator
<div class="aui-divider aui-divider--labelled" role="separator" aria-label="or">
<span class="aui-divider__label">or</span>
</div>
<div class="aui-grid aui-grid--auto" style="--aui-grid-min: 14rem"> (custom property set via the CSSOM)
<div>tile</div> ...
</div>
</div>
</main>
Stack.apply(el: HTMLElement, options?: StackOptions): LayoutHandle<StackOptions>; Flex.apply(el: HTMLElement, options?: FlexOptions): LayoutHandle<FlexOptions>; Grid.apply(el: HTMLElement, options?: GridOptions): LayoutHandle<GridOptions>; Container.apply(el: HTMLElement, options?: ContainerOptions): LayoutHandle<ContainerOptions>; Divider.create(options?: DividerOptions, doc?: Document): HTMLElement

Global configuration key: layout (see Configuration).

Option Type Default Description
gap 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 Stack, Flex, Grid: gap step mapped to the space-N token. Defaults to the configured defaultGap.
align 'start' | 'center' | 'end' | 'stretch' | 'baseline' Stack, Flex: cross-axis alignment (aui-align-*).
justify 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly' Flex: main-axis distribution (aui-justify-*).
wrap boolean true Flex: wrap items onto new lines (false adds aui-flex–nowrap).
direction 'row' | 'row-reverse' "row" Flex: row follows the writing direction; row-reverse adds aui-flex–reverse.
columns 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto' "auto" Grid: fixed column count or responsive auto-fit mode.
minColumnWidth string "16rem" Grid auto mode: minimum column width as a strict CSS length (for example ‘12rem’); sets –aui-grid-min.
size 'sm' | 'md' | 'lg' | 'xl' "lg" Container: maximum inline size from container-width-* tokens.
orientation 'horizontal' | 'vertical' "horizontal" Divider: <hr> when horizontal, role=separator with aria-orientation when vertical.
label string Divider: short centered text; also the separator’s accessible name. Horizontal only.
decorative boolean false Divider: hide from assistive technology. Cannot be combined with label.
defaultGap 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 4 Gap applied by Stack, Flex, and Grid helpers when gap is omitted. Globally configurable.
Property Type Description
element HTMLElement LayoutHandle: the consumer element the helper applies to.
options Readonly<O> LayoutHandle: current options.
Method Description
update(options: Partial<O>): void Merge, validate, and apply options. Invalid options throw and leave the element unchanged.
destroy(): void Remove the classes and custom properties this helper added, restore displaced classes and custom property values, and unsubscribe. Idempotent.
isCssLength(value: unknown): value is string The strict CSS length check used for minColumnWidth.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-container Centered, width-capped region with space-4 inline padding (default width container-width-lg).
aui-container--sm | --md | --lg | --xl Container maximum inline size.
aui-stack Flex column with a gap (default space-4).
aui-flex Flex row that wraps, with a gap (default space-4).
aui-inline Like aui-flex with align-items: center, for inline clusters such as tags or buttons.
aui-flex--nowrap | aui-flex--reverse Flex modifiers.
aui-grid CSS grid with a gap (default space-4).
aui-grid--cols-1 ... aui-grid--cols-12 Fixed equal-width columns.
aui-grid--auto Responsive auto-fit columns of at least var(–aui-grid-min, 16rem), never wider than the container.
aui-gap-0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 Gap utilities mapped to space-N tokens.
aui-align-start | center | end | stretch | baseline align-items utilities.
aui-justify-start | center | end | between | around | evenly justify-content utilities.
aui-divider Horizontal separator line (<hr> or role=separator).
aui-divider--vertical Vertical separator line; stretches to the row height.
aui-divider--labelled | aui-divider__label Horizontal divider with centered text.
--aui-grid-min Custom property: minimum column width in auto mode (default 16rem).

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-text-muted | --aui-container-width-lg | --aui-container-width-md | --aui-container-width-sm | --aui-container-width-xl | --aui-font-family-sans | --aui-font-size-sm | --aui-line-height-tight | --aui-space-1 | --aui-space-10 | --aui-space-12 | --aui-space-2 | --aui-space-3 | --aui-space-4 | --aui-space-5 | --aui-space-6 | --aui-space-8

type LayoutGap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12;
type LayoutAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
type LayoutJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
type GridColumns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto';
interface StackOptions { gap?: LayoutGap; align?: LayoutAlign }
interface FlexOptions { gap?: LayoutGap; align?: LayoutAlign; justify?: LayoutJustify; wrap?: boolean; direction?: 'row' | 'row-reverse' }
interface GridOptions { columns?: GridColumns; minColumnWidth?: string; gap?: LayoutGap }
interface ContainerOptions { size?: 'sm' | 'md' | 'lg' | 'xl' }
interface DividerOptions { orientation?: 'horizontal' | 'vertical'; label?: string; decorative?: boolean }
interface LayoutHandle<O> { readonly element: HTMLElement; readonly options: Readonly<O>; update(options: Partial<O>): void; destroy(): void }

Pattern: Presentational CSS layout (no ARIA widget pattern). Dividers follow HTML <hr> and the WAI-ARIA separator role (non-focusable).

Key Context Action
Tab / Shift+Tab Any Not applicable: layout primitives and dividers are not focusable. Focus follows the source order of the consumer’s content, including under aui-flex–reverse.
Element Role
container, stack, flex, grid none added (the consumer element keeps its own semantics)
horizontal divider separator (native <hr>)
vertical or labelled divider separator (role attribute on a <div>)
decorative divider hidden (aria-hidden=true)
Attribute Element Value
aria-orientation vertical divider “vertical”
aria-label labelled divider The label text (separator children are presentational)
aria-hidden decorative divider “true”

Not focusable. Layout never changes the DOM order, so reading and focus order stay in source order.

Criterion Level Note
1.3.1 Info and Relationships A
1.3.2 Meaningful Sequence A
1.4.10 Reflow AA
1.4.12 Text Spacing AA

Automated test results: Accessibility and compliance.

  • Auto-fit grids and wrapping flex rows support 320 CSS px reflow (WCAG 1.4.10).
  • Gaps use rem-based tokens, so spacing scales with user text-size and text-spacing settings.
  • Row-reverse only changes visual order; avoid it where order carries meaning (WCAG 1.3.2).
  • Screen readers differ in whether they announce separators and their labels; labelled dividers are supplementary, not structural.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

Applied and destroyed. The primitives have no interactive states.

From Trigger To
- X.apply(el, options) applied (classes added)
applied update(options) applied with new classes; the previous modifiers are removed
applied global defaultGap change gap class updated (when gap is omitted)
applied destroy() element restored to its previous classes and custom properties
  • Auto-fit grids and wrapping rows support 320 CSS px reflow (WCAG 1.4.10). Fixed column counts do not collapse; prefer columns: 'auto' for content grids.
  • Gaps are rem-based, so they scale with user text size and text-spacing overrides.
  • row-reverse changes only the visual order. Avoid it when order carries meaning (WCAG 1.3.2).
  • Screen readers differ in how they announce separators. Do not rely on a divider for structure; use headings or landmarks.
  • Manual screen-reader verification is pending.

aui-grid--auto uses repeat(auto-fit, minmax(min(var(--aui-grid-min, 16rem), 100%), 1fr)): columns are at least the minimum width but never wider than the container. Flex rows wrap by default. Containers keep inline padding at every width.

Logical properties throughout (max-inline-size, margin-inline, padding-inline, border-inline-start). Flex rows follow the writing direction, so start/end and row order mirror automatically.

Spacing uses space-* tokens (identical across built-in themes). Divider lines use color-border and labels color-text-muted. In forced-colors mode, divider lines are drawn in CanvasText.

configureAbsoluteUI({ components: { layout: { defaultGap: 3 } } });

Existing handles that omit gap update immediately.

An explicit gap option overrides defaultGap. All options are dynamic through update(). CSS-only usage defaults to space-4, which matches the default defaultGap.

None. Layout primitives render no icons.

Helpers only change classList and set one custom property (--aui-grid-min) through el.style.setProperty. They never set a style attribute or parse HTML. minColumnWidth must match a strict CSS length pattern: a non-negative number with a known unit, or 0. This rejects calc(), var(), and declaration injection such as '10rem; color: red'. Divider labels use textContent. CSP-safe.

No persistence, network access, or telemetry.

Constant work per apply()/update(): a few classList operations and at most one custom property. No DOM listeners; one configuration subscription per handle (released by destroy()). There is no dedicated performance budget yet.

Not data-driven. Grids and stacks hold any number of children; layout cost is the browser’s.

  • Applying Stack, Flex, or Grid to an element that already has one of them throws LAYOUT_ALREADY_APPLIED. Container can be combined with any of them.
  • Classes that were on the element before apply() are never removed on destroy(). A conflicting pre-existing modifier (for example aui-gap-2 when gap: 6 is applied) is removed while applied and restored on destroy().
  • A --aui-grid-min value set by the consumer before Grid.apply is restored on destroy().
  • minColumnWidth with a fixed columns count throws, because it would be ignored.
Code Category Cause
LAYOUT_OPTIONS configuration Invalid gap, alignment, justify, wrap, direction, columns, length, size, orientation, or divider label combination.
LAYOUT_ALREADY_APPLIED configuration A display helper (Stack, Flex, Grid) or Container already owns the element.
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION configuration Invalid global layout configuration.

Failed apply() calls leave the element unchanged.

  • Fixed grid column counts do not collapse at narrow widths.
  • Labelled dividers are horizontal only.
  • CSS-only usage does not follow defaultGap; it uses space-4 unless a gap class is set.
  • Manual screen-reader verification of separators is pending.
  • Arbitrary gap values (only the documented space-* steps).
  • calc(), var(), or other CSS functions as minColumnWidth.
  • Masonry and subgrid layouts.