Stack

Flex layout whose gap comes from the DS six-step spacing scale, not from a number at the call site (LAYOUT_PHILOSOPHY.md §3). `Stack` stacks children vertically; `Cluster` is the horizontal, wrapping form for chip, tag and button rows.

foundationserveruiv1.1.0

Preview

Stack — live renderprimitives-stack--vertical

Rendered from the story the storybook (a11y) CI gate runs axe against — the preview can't show something that hasn't been verified.

Install

Two equivalent paths — the URL works in any shadcn-CLI setup; the alias works once you've registered @interlace in your components.json.

Via shadcn URL
npx shadcn@latest add https://ds.interlace.tools/r/stack.json
With the @interlace alias
npx shadcn@latest add @interlace/stack

History

This component is at v1.1.0, first shipped in @interlace/ui 1.0.0. The version is stamped as a banner into the file the install writes, so the copy in your tree says which one you have — compare it with the number above before deciding whether to re-run the install.

No release note names this component yet. It shipped with the release above and has not changed since — see the full changelog for what moved elsewhere in the DS.

Import

3 named exports — pull the parts you need.

Public API
import { Cluster, Stack, stackVariants } from '@/components/ui/stack';

Anatomy

Extracted from the primitive's JSDoc header. The source is the only documentation that can't drift.

Stack                            (div by default, or the `render` element)
    └─ children                    (data-direction / -gap / -align / -justify)
  Cluster                          (a Stack with direction=horizontal — data-slot="cluster")
`Cluster` is `Stack` with `direction="horizontal"` and different defaults
(`gap="sm"`, `align="center"`); it overrides `data-slot` to `cluster` by
spreading after Stack's own attribute, so the two are distinguishable in the
DOM and in tests.

Variants

Closed prop unions enforced by class-variance-authority. See Storybook's Variants story for the rendered matrix.

  • direction
    • verticaldefault
    • horizontal
  • gap
    • xs
    • sm
    • mddefault
    • lg
    • xl
    • 2xl
  • align
    • start
    • center
    • end
    • stretch
    • baseline
  • justify
    • start
    • center
    • end
    • between
    • around

API reference

Parsed from the type declarations in the source — the same file the install writes into your tree, so this table can't drift from the component you get.

StackProps

Also accepts every <div> attribute. Plus the variant props listed above.

PropTypeDescription
direction'vertical' | 'horizontal'
renderuseRender.RenderProp

Accessibility

Every story for this component is rendered headlessly and checked with axe-core (wcag2aa, wcag22aa, best-practice, ACT) on every PR. That gate has no continue-on-error, so what ships has zero known violations.

What follows is what static analysis can see. Axe cannot press a key and never sees an overlay open, so the operable-without-a-mouse claim lives in Behavior instead, where the keyboard path is replayed step by step.

Focus + keyboard behaviour
Owned by @base-ui/react/use-render — focus trapping, dismissal and typeahead come from the headless primitive, not from us.
Focus ring (WCAG 2.2 SC 2.4.13)
Not focusable — no focus indicator required.
Reduced motion
No animation to gate.
ARIA in the source
None — semantics come from the element or the Base UI primitive rather than hand-written ARIA.

Examples

3 more states from the same story file.

R-rule compliance

Every primitive in @interlace/ui models to the portable 26-rule floor enforced by the componentApi ESLint preset. The cells below pin exactly where each rule applies in this file.

RuleConceptWhere
R4Extends native el`React.ComponentProps<'div'> & VariantProps<…>`
R6data-slot + data-* on rootstack / cluster, plus direction / gap / align / justify
R7cva + cn + ...rest`cn(stackVariants({…}), className)` + `...props`
R8Enums, no booleansgap / align / justify / direction are closed enums
R10Composition seam`render` — Base UI's `useRender`, not an `as` prop
R18Tailwind onlygap is a utility, never an inline `style`
R19Tokens only`gap-xs…gap-2xl` resolve to the DS `--spacing-*` tokens

Dependencies

Base UI primitive
@base-ui/react/use-render
Lucide icons
none
NPM dependencies
  • @base-ui/react
  • class-variance-authority
Registry dependencies

Source

The full implementation — components/ui/stack.tsx once installed.

170 lines · TypeScript
import * as React from 'react';

// @interlace/stack v1.1.0 — Interlace design system.
// Docs, props and live preview: https://ds.interlace.tools/c/stack
// What changed since: https://ds.interlace.tools/c/stack#history
// Generated banner — keep it, the upgrade diff reads this version.

/**
 * @interlace/ui — Stack + Cluster
 *
 * Flex layout whose gap comes from the DS six-step spacing scale, not from a
 * number at the call site (LAYOUT_PHILOSOPHY.md §3). `Stack` stacks children
 * vertically; `Cluster` is the horizontal, wrapping form for chip, tag and
 * button rows.
 *
 * `gap`, `align` and `justify` are closed enums — there is no arbitrary-value
 * escape hatch short of `className`.
 *
 * ## Anatomy
 *

…150 more lines…

Registry JSON

The raw registry record — what the shadcn CLI fetches.