---
name: layout
description: >
  Structure pages with responsive layout primitives. Box (responsive props
  for display, flex, gap, padding, margin, width, height, position, overflow),
  Stack (vertical flex, default gap=2), Inline (horizontal flex, default gap=2),
  Columns/Column (CSS grid 1-12 + auto-fill, responsive column count),
  MaxWidthWrapper (container centering), PageLayout (sidebar-aware header +
  content). ResponsiveValue type: T | [T,T] | [T,T,T] | [T,T,T,T] mapping
  to base/sm/md/lg breakpoints. Activate when building page layouts or using
  responsive spacing.
type: core
library: '@loke/design-system'
library_version: '2.0.0-rc.6'
requires:
  - getting-started
sources:
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/box'
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/stack'
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/inline'
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/columns'
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/max-width-wrapper'
  - 'LOKE/merchant-frontends:packages/design-system/src/layout/page-layout'
  - 'LOKE/merchant-frontends:packages/design-system/src/lib/responsive'
---

# Layout Primitives

This skill builds on **getting-started**. Read it first for setup and imports.

## Setup

Layout primitives compose together to build page structures. Import each from its subpath:

```tsx
import { Box } from "@loke/design-system/box";
import { Stack } from "@loke/design-system/stack";
import { Inline } from "@loke/design-system/inline";
import { Columns, Column } from "@loke/design-system/columns";
import { MaxWidthWrapper } from "@loke/design-system/max-width-wrapper";
import { PageLayout, PageLayoutSidebarProvider } from "@loke/design-system/page-layout";
```

Basic composition -- Stack for vertical flow, Inline for horizontal, Box for custom:

```tsx
<Stack gap={4}>
  <Inline gap={2}>
    <Box padding={4} background="card" borderRadius="lg">
      Card content
    </Box>
    <Box padding={4} background="card" borderRadius="lg">
      Another card
    </Box>
  </Inline>
  <Box paddingY={2}>Footer area</Box>
</Stack>
```

## Core Patterns

### Box with responsive props

Box is the foundational layout primitive. Every variant prop accepts a `ResponsiveValue<T>` -- either a single value or an array mapping to breakpoints:

| Format | Breakpoints |
|--------|-------------|
| `gap={4}` | All breakpoints |
| `gap={[2, 4]}` | base=2, sm=4 |
| `gap={[2, 4, 6]}` | base=2, sm=4, md=6 |
| `gap={[2, 4, 6, 8]}` | base=2, sm=4, md=6, lg=8 |

The `ResponsiveValue` type is defined as:

```ts
type ResponsiveValue<T> = T | [T, T] | [T, T, T] | [T, T, T, T];
```

Breakpoints map to Tailwind prefixes: `""` (base), `"sm:"`, `"md:"`, `"lg:"`.

**All responsive Box props:**

| Category | Props |
|----------|-------|
| Display | `display` (`block`, `flex`, `grid`, `hidden`, `inline`, `inline-block`, `inline-flex`, `inline-grid`, `table-cell`) |
| Flex | `flexDirection` (`row`, `col`, `row-reverse`, `col-reverse`), `flexWrap` (`wrap`, `nowrap`, `wrap-reverse`), `flexGrow`, `flexShrink` |
| Alignment | `alignItems` (`start`, `center`, `end`, `baseline`, `stretch`), `justifyContent` (`start`, `center`, `end`, `between`, `around`, `evenly`) |
| Spacing | `gap`, `gapX`, `gapY`, `spaceX`, `spaceY` |
| Padding | `padding`, `paddingX`, `paddingY`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight` |
| Margin | `margin`, `marginX`, `marginY`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight` (all support `auto`) |
| Dimensions | `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight` |
| Position | `position` (`relative`, `absolute`, `fixed`, `sticky`, `static`), `top`, `right`, `bottom`, `left` (support negative values), `zIndex` |
| Overflow | `overflow`, `overflowX`, `overflowY` (`auto`, `hidden`, `scroll`, `visible`) |
| Border | `border`, `borderTop`, `borderBottom`, `borderLeft`, `borderRight` (boolean), `borderColor`, `borderRadius` (`sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `full`, `none`) |
| Visual | `background` (color tokens), `boxShadow` (`sm`, `md`, `lg`, `xl`, `2xl`, `inner`, `none`), `cursor`, `pointerEvents`, `textAlign` |
| Other | `container` (boolean), `as` (HTML element tag), `asChild` (slot pattern) |

Size values are numeric: `0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16, 20, 24, 28, 32` or named: `none`, `xxsmall`, `xsmall`, `small`, `medium`, `large`, `xlarge`, `xxlarge`.

```tsx
<Box
  display="flex"
  flexDirection={["col", "row"]}
  gap={[2, 4, 6, 8]}
  padding={[2, 4]}
  background="card"
  borderRadius="lg"
  overflow="hidden"
>
  {children}
</Box>
```

The `as` prop changes the rendered HTML element (default `"div"`):

```tsx
<Box as="section" padding={4}>
  Section content
</Box>
```

### Stack and Inline

**Stack** renders a vertical flex container (`display="flex"`, `flexDirection="col"`). Default `gap={2}`. Accepts all Box props except `display` and `flexDirection`.

```tsx
import { Stack } from "@loke/design-system/stack";

<Stack gap={4} padding={2} alignItems="start">
  <div>First item</div>
  <div>Second item</div>
  <div>Third item</div>
</Stack>
```

**Inline** renders a horizontal inline-flex container (`display="inline-flex"`, `flexDirection="row"`). Default `gap={2}`. Same prop restrictions as Stack.

```tsx
import { Inline } from "@loke/design-system/inline";

<Inline gap={3} alignItems="center" flexWrap="wrap">
  <Tag>React</Tag>
  <Tag>TypeScript</Tag>
  <Tag>Tailwind</Tag>
</Inline>
```

Both support responsive gap:

```tsx
<Stack gap={[2, 4, 6]}>
  {items}
</Stack>
```

### Columns for grid layouts

**Columns** renders a CSS grid. Props: `columns` (1-12 or `"auto-fill"`, default 2), `gap` (default 1). Both are responsive.

**Column** sets `colSpan` (1-12, default 1) on a grid child. Also responsive.

```tsx
import { Columns, Column } from "@loke/design-system/columns";

<Columns columns={[1, 2, 3]} gap={4}>
  <Column colSpan={1}>Sidebar</Column>
  <Column colSpan={[1, 1, 2]}>Main content</Column>
</Columns>
```

A 12-column responsive layout:

```tsx
<Columns columns={12} gap={4}>
  <Column colSpan={[12, 4, 3]}>Navigation</Column>
  <Column colSpan={[12, 8, 9]}>Content</Column>
</Columns>
```

Auto-fill columns:

```tsx
<Columns columns="auto-fill" gap={2}>
  {cards.map((card) => (
    <div key={card.id}>{card.title}</div>
  ))}
</Columns>
```

### MaxWidthWrapper for container centering

Centers content with a max-width container and responsive horizontal padding (`px-2.5` base, `md:px-20`).

```tsx
import { MaxWidthWrapper } from "@loke/design-system/max-width-wrapper";

<MaxWidthWrapper>
  <h1>Page content</h1>
</MaxWidthWrapper>
```

Accepts `className` and `style` for customization.

### PageLayout with sidebar

Full-page layout with a sidebar-aware header and scrollable content area. Includes a loading state with a centered Spinner.

```tsx
import { PageLayout, PageLayoutSidebarProvider } from "@loke/design-system/page-layout";
import { Settings } from "@loke/icons";

// With sidebar context (wrap at app level)
<PageLayoutSidebarProvider
  sidebar={{ trigger: <SidebarTrigger />, imageUrl: "/logo.svg" }}
>
  <PageLayout
    header={{
      icon: Settings,
      title: "Settings",
      belowTitle: <span>Manage your account</span>,
      rightAside: <Button>Save</Button>,
    }}
  >
    <div>Page content here</div>
  </PageLayout>
</PageLayoutSidebarProvider>
```

**PageLayoutProps:**

| Prop | Type | Description |
|------|------|-------------|
| `header.icon` | `LokeIcon` | Icon component from `@loke/icons` |
| `header.title` | `string` | Page heading text |
| `header.belowTitle` | `ReactNode` (optional) | Content below the title |
| `header.rightAside` | `ReactNode` (optional) | Right-aligned header content |
| `sidebar` | `SidebarConfig` (optional) | Override sidebar from context |
| `loading` | `boolean` (optional) | Shows centered Spinner instead of children |

The sidebar can be provided directly via the `sidebar` prop or through `PageLayoutSidebarProvider` context. The direct prop takes precedence. On mobile (`md:hidden`), the sidebar renders a top bar with the trigger, a centered logo image, and a spacer.

`HEADER_HEIGHT` is exported as `"8rem"` for layout calculations.

## Common Mistakes

### HIGH: Mixing responsive array props with className

Responsive prop arrays and Tailwind responsive className prefixes target the same breakpoints and will conflict:

```tsx
// WRONG -- responsive gap prop and sm:gap-8 className conflict
<Box gap={[2, 4, 6]} className="sm:gap-8" />

// CORRECT -- use responsive props exclusively for responsive behavior
<Box gap={[2, 4, 8, 8]} />

// CORRECT -- className for non-responsive additions only
<Box gap={[2, 4, 6]} className="border-dashed" />
```

Use responsive prop arrays for responsive behavior. Use `className` only for static styles that the prop API does not cover.

### MEDIUM: Using PageLayout without SidebarProvider

PageLayout reads `PageLayoutSidebarContext` to render the mobile sidebar bar. If no sidebar is provided via props or context, the mobile top bar is omitted silently -- which may cause a broken layout on small screens.

```tsx
// WRONG -- no sidebar context, mobile layout has no top bar
<PageLayout header={{ icon: Settings, title: "Settings" }}>
  {children}
</PageLayout>

// CORRECT -- provide sidebar via context
<PageLayoutSidebarProvider sidebar={{ trigger: <MenuButton />, imageUrl: "/logo.svg" }}>
  <PageLayout header={{ icon: Settings, title: "Settings" }}>
    {children}
  </PageLayout>
</PageLayoutSidebarProvider>

// CORRECT -- provide sidebar via prop
<PageLayout
  sidebar={{ trigger: <MenuButton />, imageUrl: "/logo.svg" }}
  header={{ icon: Settings, title: "Settings" }}
>
  {children}
</PageLayout>
```

### MEDIUM: Inconsistent responsive breakpoint arrays

A 3-value array `[1, 2, 3]` maps to base/sm/md. The `lg` breakpoint inherits the `md` value. This is fine when intentional, but can cause confusion when you expect all four breakpoints covered.

```tsx
// 3 values: base=1, sm=2, md=3, lg inherits md (3)
<Columns columns={[1, 2, 3]} />

// Explicit 4 values: base=1, sm=2, md=3, lg=4
<Columns columns={[1, 2, 3, 4]} />
```

When targeting all breakpoints, prefer the explicit 4-value form to avoid ambiguity.

---

**Tension note:** Responsive props vs className escape hatch -- Layout responsive prop arrays conflict with Tailwind className responsive prefixes. Use props for responsive behavior, className for non-responsive static styles. See also: theming/SKILL.md

## See also

- display-components/SKILL.md
- interactive-components/SKILL.md
- theming/SKILL.md
