Layout
<al-layout><ALLayout> REACTbetaThe single arrangement primitive for the design system. Layout owns three orthogonal jobs, selected by `variant`:
CODE — HTML / REACT
Guidance
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 atsize, with fluid gutter tracks either side. - A child needs to break out of that measure edge-to-edge: give it the
bleedattribute and it spans the gutters. - You need a track list the props cannot name — a sidebar shell, an asymmetric split. Set
--al-layout-templateon agridlayout; 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 patternal-checkbox-groupandal-radio-groupboth use. - Use
--al-layout-paddingfor the layout's own inset.:hostisdisplay: 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, whichdisplay: contentsdoes not interrupt, so it needs nodisplayoverride 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:Nclasses. There is deliberately no second span system. - Prefer the
gapprop 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
*-groupwrapper 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,justifyorwrapproperty to a new component, and don't hand-rolldisplay: flexordisplay: gridto 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-grouporal-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-paddingfor inline insets undervariant="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 forcedisplay: blockon 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 thedisplay: contentstrap. Set--al-layout-paddingon 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
flowandgridhere, and they should stay that way. Reordering with CSS breaks the correspondence between what is seen and what is read or tabbed. fullHeightsets 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
gapscale rather than one-off values, so unrelated pages end up with the same spacing vocabulary. - Let the measure do the work for reading. A
constrainedlayout capped atsizekeeps 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.
FIGMA PARITY
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.
ACCESSIBILITY
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.
INSTALL
import '@southleft/al-web-components/components/layout';
import { ALLayout } from '@southleft/al-react';API — 13 PROPS
SLOTS
EVENTS
THIS COMPONENT DECLARES NO EVENTS IN THE MANIFEST.
CSS CUSTOM PROPERTIES
SOURCE — components/layout/layout.ts · Foundations/Layout