Skip to content
Guide

Manage spacing with Stack and Row.

Give vertical and horizontal groups one spacing owner. Use Col for flexible children, a responsive gap for changing density, and render for meaningful HTML.

Parent-owned gapsCSS flexboxSemantic lists
01 / Prerequisites

Install Stack, Row, and Col

Use a React 19 and TypeScript project with Tailwind CSS 4 and shadcn/ui configured. After registering @layout-kit, add just these three primitives; the CLI resolves their shared spacing source and Base UI dependency.

Terminal
npx shadcn@latest add @layout-kit/stack @layout-kit/row @layout-kit/col

Start with registry installation if the namespace is not configured. The example uses plain text and requires no additional shadcn components or event handlers.

02 / The spacing model

Let the parent own spacing

Stack is a vertical flex container; Row is a horizontal flex container. Both own gap, align, and justify. Put spacing on the container instead of giving each child a bottom or right margin.

  • Use an outer Stack for the heading and list, then another Stack for the list items. Each gap describes one relationship.
  • Use Row to put a project’s details beside its status. Col with grow lets the details fill spare horizontal space.
  • Col does not space its own children. Nest a Stack inside it to separate the title from the description.
  • Gap supports 0, 1, 2, 3, 4, 6, 8, and 12 on the Tailwind scale. A value of 4 corresponds to 1rem with Tailwind’s default spacing.
03 / Copy and adapt

Complete project list

The outer gap increases from 4 to 6 at md. Individual rows can wrap, and a flex basis gives the description room before the status moves to the next line.

Projects

Two sample projects

  • Storefront

    Product pages and checkout

    In progress
  • Documentation

    Examples and API reference

    Ready for review
components/project-list.tsx
import { Col } from "@/components/ui/col";
import { Row } from "@/components/ui/row";
import { Stack } from "@/components/ui/stack";

const projects = [
  { name: "Storefront", detail: "Product pages and checkout", status: "In progress" },
  { name: "Documentation", detail: "Examples and API reference", status: "Ready for review" },
];

export function ProjectList() {
  return (
    <Stack gap={{ base: 4, md: 6 }}>
      <Row justify="between" gap={3} wrap>
        <h2 className="text-lg font-semibold">Projects</h2>
        <p className="text-sm text-muted-foreground">Two sample projects</p>
      </Row>
      <Stack render={<ul />} gap={3}>
        {projects.map((project) => (
          <Row key={project.name} render={<li />} gap={3} align="start" wrap>
            <Col grow className="basis-48">
              <Stack gap={1}>
                <h3 className="font-medium">{project.name}</h3>
                <p className="text-sm text-muted-foreground">{project.detail}</p>
              </Stack>
            </Col>
            <span className="text-sm text-muted-foreground">{project.status}</span>
          </Row>
        ))}
      </Stack>
    </Stack>
  );
}
04 / Details that matter

Wrapping and semantics

  • Row does not wrap by default. Pass wrap when its children may no longer fit; this is CSS flex-wrap, not a JavaScript width check.
  • Stack renders the ul and each Row renders an li. Visual layout alone does not create list semantics.
  • All primitives set min-w-0. Long unbroken content still needs an explicit wrapping, truncation, or scrolling strategy.
  • align works across the flex axis and justify along it. Their visual directions change between Stack and Row.
  • A partial responsive map only emits its specified classes. Include base when you need an explicit small-screen value.

Use direct Tailwind classes for an isolated layout or a value outside the supported scale. Layout Kit is useful when repeated arrangements benefit from shared typed props. See Stack, Row, and Col for the complete API.