COMPONENTS / ORGANISMS / al-layout← ALL COMPONENTS

Layout

<al-layout><ALLayout> REACTbeta

The single arrangement primitive for the design system. Layout owns three orthogonal jobs, selected by `variant`:

PLAYGROUND12 CONTROLS
PREVIEW / al-layout / SOUTHLEFT
Loading preview
Flat, minimal, type-first. Duplicate Favorite Remove MK M. Kim · 4h ago Stable Hairline borders on warm paper neutrals, one refined blue, shadows reserved for overlays. Design Engineering Dismiss Continue Floating labels, retired. Duplicate Favorite Remove TC T. Chen · 2d ago In review Hairline borders on warm paper neutrals, one refined blue, shadows reserved for overlays. Design Engineering Dismiss Continue A segmented stepper. Duplicate Favorite Remove JR J. Ruiz · now Redesign Hairline borders on warm paper neutrals, one refined blue, shadows reserved for overlays. Design Engineering Dismiss Continue
variant
direction
gap
align
justify
size
gutter
FLAGS
CODE — HTML / REACT

Guidance

AUTHORED · CITED TO SOURCE

The single arrangement primitive in the system. Everything about where boxes sit — direction, gap, alignment, distribution, wrapping, stretch, the page measure, grid columns — lives here and nowhere else. Four jobs, selected by variant: flow stacks or rows content, constrained owns the page measure with fluid gutters, grid is an N-column grid, bento is a 12-column auto-row grid for asymmetric tiles.

WHEN TO USE

  • Any time you need to arrange content — inside a component's slot, inside a page section, or around a whole page. There is no second answer to this question in the system.
  • A page needs a measure: variant="constrained" puts children in a centred column capped at size, with fluid gutter tracks either side.
  • A child needs to break out of that measure edge-to-edge: give it the bleed attribute and it spans the gutters.
  • You need a track list the props cannot name — a sidebar shell, an asymmetric split. Set --al-layout-template on a grid layout; three dashboard apps already do exactly this.

WHEN NOT TO USE

  • What you actually need is a bounded content surface with a heading, body and actions. That is a container with regions, not an arrangement.USE CARD →
  • You are grouping form controls that share a label, a required state and a validation message. That grouping is semantic, and it needs the component that owns the semantics — which then nests its own layout inside.USE CHECKBOX-GROUP →
  • You need a visual break between sections rather than space between them.USE DIVIDER →
  • You are positioning something against a trigger — a floating panel anchored to a control. That is not flow arrangement.USE POPOVER →

DO

  • Reach for <al-layout> FIRST when a new component needs its slotted children arranged. Nesting the slot content in a layout is the pattern al-checkbox-group and al-radio-group both use.
  • Use --al-layout-padding for the layout's own inset. :host is display: contents, so padding set on <al-layout> from outside is dropped — the custom property is the supported way in. It reaches the shadow box by INHERITANCE, which display: contents does not interrupt, so it needs no display override and no wrapper element. The four demo-app shells and the homepage hero all set it this way.
  • Span grid children with the shared .al-u-grid__item.col:N classes. There is deliberately no second span system.
  • Prefer the gap prop over --al-layout-gap. The prop is on the design scale and wins over the custom property; the custom property exists for the value the scale cannot name.

DON’T

  • Don't create a new *-group wrapper component. A wrapper that owns no behaviour, no ARIA relationship and no state is this component with props — that rule is a blocker in the contributor contract, not a preference.
  • Don't add a direction, orientation, gap, align, justify or wrap property to a new component, and don't hand-roll display: flex or display: grid to arrange SLOTTED children. Internal shadow structure — an icon against a label inside an atom — is exempt; content the consumer provides is not.
  • Don't look for al-button-group, al-layout-container, al-layout-section, al-bento-grid, al-split-content, al-chip-group or al-toast-group. All seven were removed. The first five owned only arrangement; the last two did own behaviour, but nothing in the system used it, so they were cut rather than carried.
  • Don't use --al-layout-padding for inline insets under variant="constrained". The inline space there is the gutter tracks — use it for block padding only.
  • Don't wrap an <al-layout> in a <div> just to pad it, and don't force display: block on the host to make padding stick. Both are the workaround for a hook that already exists; five call sites had independently invented them, each with its own comment re-deriving the display: contents trap. Set --al-layout-padding on the layout itself instead.

ACCESSIBILITY IN PRACTICE

  • Layout renders no landmark and no role. A <main>, <nav> or <aside> around it is the page's job, and a page built entirely from layouts with no landmarks is unnavigable by region.
  • Visual order and DOM order are the same in flow and grid here, and they should stay that way. Reordering with CSS breaks the correspondence between what is seen and what is read or tabbed.
  • fullHeight sets a height floor of 100vh by default via --al-layout-min-height. Viewport-height floors are a common source of content that cannot be scrolled to on small screens — check the short viewport.

CONTENT GUIDELINES

  • Layout holds no text of its own, so its content rule is about rhythm: use the gap scale rather than one-off values, so unrelated pages end up with the same spacing vocabulary.
  • Let the measure do the work for reading. A constrained layout capped at size keeps line length in the range prose is comfortable at, without any per-page decision.

WHERE THIS CAME FROM

  • libs/al-web-components/components/layout/layout.tsThe rule stated on the component itself, with the two semantic groups named as the examples of what is allowed to exist alongside it.
  • libs/al-web-components/components/layout/layout.scssThe escape hatch for a track list the props cannot express, and the note that three dashboard apps use it for sidebar shells.
  • libs/al-web-components/components/layout/layout.scssThe `::slotted(*)` reset of every `--al-layout-*` hook — what makes setting one on a layout safe, because the value cannot leak into the layouts nested inside it. The cited symptom is a hero's own block rhythm padding the button and chip rows within it.
  • libs/al-web-components/components/layout/layout.tsWhy padding has to arrive as a custom property: `display: contents` drops padding set from outside.
  • AGENTS.mdThe contributor contract: the blocker-level rule against new arrangement props and new `*-group` wrappers, and the list of components removed under it.

Each line above names a file and a literal string that must still appear in it.pnpm gate:guidance re-reads them from this built page, so guidance that has stopped being true fails a check rather than misleading you.

NOT TRACKEDSouthleft Design SystemFIGMA SET · Layout

Deliberately not represented in Figma.

DECIDED BY
NEVER SYNCED
FIGMA SIDE
OBSERVED
LAST CONFIRMED SYNC
2026-08-28
PUBLIC SURFACE
13 ATTR · 1 SLOT · 0 EVENT · 0 PART

The two sides have never been confirmed equal, so there is no baseline to compare against.

Arrangement primitive with no Figma set of its own, by design (COVERAGE.md:19; layout.scss display:contents host) - composites degrade it to a coarse auto-layout frame, which is the documented behaviour, not drift.

CHECKEVIDENCEDETAILRESULT
Default stateAXElayout--BentoPASS
Advanced statesAXE4 further storiesPASS
Screen readerMANUALNo screen-reader pass has been recorded for this component.NOT RECORDED
Keyboard navigationINTERACTION TESTSNo interaction tests and no manual keyboard pass.NOT RECORDED

MEASURED 2026-09-08 · 5 STORIES · AXE WCAG2A + WCAG2AA + WCAG21A + WCAG21AA + WCAG22AA

COLOR CONTRAST0 RULES FAILING

The CI gate disables color-contrast globally (libs/al-web-components/story-fixture/src/main.ts), so its result never reaches a test run. It is enabled for this measurement, and reported here whichever way it comes out.

NO CONTRAST VIOLATIONS ACROSS THIS COMPONENT’S 5 STORIES.

import '@southleft/al-web-components/components/layout';
import { ALLayout } from '@southleft/al-react';
PROPTYPEDEFAULT
variant'constrained' | 'grid' | 'bento'Style variant - **default** a flow layout — a column, or a row with `direction="row"` - **constrained** a centred content column with gutter tracks; children marked `bleed` break out edge-to-edge - **grid** an N-column grid, sized by `columns` - **bento** a 12-column auto-row grid for asymmetric feature tiles
direction'row' | 'column'Direction - **column** (default) stacks children vertically - **row** arranges children horizontally, centered on the cross axis
gap'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'Gap between children (default is 16px) - **none** removes the gap - **xs** 4px - **sm** 8px - **md** 16px (same as the default) - **lg** 24px - **xl** 32px
align'start' | 'center' | 'end' | 'stretch'Cross-axis alignment - Defaults to `stretch` for a column and `center` for a row
justify'start' | 'center' | 'end' | 'between'Main-axis distribution
size'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full'Measure — the width of the centred content column under `variant="constrained"`. Defaults to the theme's container width.
gutter'none' | 'sm' | 'md' | 'lg'Gutter — the width of the tracks either side of the content column under `variant="constrained"`. Defaults to a fluid 20px–60px clamp.
columnsnumberColumn count under `variant="grid"` (1–12, default 12)
noCollapsebooleanKeep the grid's columns at every width - By default a grid collapses to a single column below the medium breakpoint so tiles never become unreadably narrow
wrapbooleanAllow children to wrap onto multiple lines
growbooleanAbsorb the free space of a flex parent - Needed when the layout is slotted into a flex container (such as a dialog or popover footer) and `justify` needs room to act on
responsivebooleanCollapse a row into a column on small screens
fullHeightbooleanFill at least the height of the viewport - Opt in for page shells. Override the height with `--al-layout-min-height`
SLOTDESCRIPTION
(default)The layout content. Under `variant="constrained"`, a child carrying the `bleed` attribute breaks out of the content column.

THIS COMPONENT DECLARES NO EVENTS IN THE MANIFEST.

PROPERTYDESCRIPTIONDEFAULT
--al-layout-gapGap between children, for a value the `gap` prop's scale cannot name. An explicit `gap` prop still wins over it. Prefer the prop.
--al-layout-paddingThe layout's own inset. Defaults to `0`. Needed because `:host` is `display: contents`, so padding set from outside is dropped. Under `variant="constrained"` use it for block padding only — inline insets there are the gutter tracks.
--al-layout-gutterGutter track width under `variant="constrained"`. Defaults to a fluid 20px–60px clamp.
--al-layout-measureContent column width under `variant="constrained"`. Set via `size`.
--al-layout-columnsColumn count under `variant="grid"`. Set via `columns`.
--al-layout-min-heightHeight floor when `fullHeight` is set. Defaults to 100vh.

SOURCE — components/layout/layout.ts · Foundations/Layout