# Stack

Flex layout as props, on a closed spacing scale.

```tsx
import * as stylex from "@stylexjs/stylex"
import { VStack } from "@/components/ui/stack"
import { color, radius, space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  card: {
    borderRadius: radius.md,
    borderWidth: 1,
    borderStyle: "solid",
    backgroundColor: `color-mix(in oklab, ${color.muted} 40%, transparent)`,
    paddingInline: space.s3,
    paddingBlock: space.s2,
  },
})

export default function StackDemo() {
  return (
    <VStack gap={2} width="16rem">
      <div {...stylex.props(styles.card)}>First</div>
      <div {...stylex.props(styles.card)}>Second</div>
      <div {...stylex.props(styles.card)}>Third</div>
    </VStack>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx
   ```

2. Set up the token layer, if you have not already. `Stack` reads the `space`
   scale. See [Installation](https://carbon.style/docs/installation).

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/stack.tsx"
   "use client"

   import { useRender } from "@base-ui/react/use-render"
   import type { CSSProperties, ElementType, ReactNode } from "react"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"

   import { space } from "@/theme/tokens.stylex"

   export type SpacingStep = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12

   const gapStyles = stylex.create({
     0: { columnGap: space.s0, rowGap: space.s0 },
     0.5: { columnGap: space.s0_5, rowGap: space.s0_5 },
     1: { columnGap: space.s1, rowGap: space.s1 },
     1.5: { columnGap: space.s1_5, rowGap: space.s1_5 },
     2: { columnGap: space.s2, rowGap: space.s2 },
     3: { columnGap: space.s3, rowGap: space.s3 },
     4: { columnGap: space.s4, rowGap: space.s4 },
     5: { columnGap: space.s5, rowGap: space.s5 },
     6: { columnGap: space.s6, rowGap: space.s6 },
     8: { columnGap: space.s8, rowGap: space.s8 },
     10: { columnGap: space.s10, rowGap: space.s10 },
     12: { columnGap: space.s12, rowGap: space.s12 },
   })

   const paddingInlineStartStyles = stylex.create({
     0: { paddingInlineStart: space.s0 },
     0.5: { paddingInlineStart: space.s0_5 },
     1: { paddingInlineStart: space.s1 },
     1.5: { paddingInlineStart: space.s1_5 },
     2: { paddingInlineStart: space.s2 },
     3: { paddingInlineStart: space.s3 },
     4: { paddingInlineStart: space.s4 },
     5: { paddingInlineStart: space.s5 },
     6: { paddingInlineStart: space.s6 },
     8: { paddingInlineStart: space.s8 },
     10: { paddingInlineStart: space.s10 },
     12: { paddingInlineStart: space.s12 },
   })

   const paddingInlineEndStyles = stylex.create({
     0: { paddingInlineEnd: space.s0 },
     0.5: { paddingInlineEnd: space.s0_5 },
     1: { paddingInlineEnd: space.s1 },
     1.5: { paddingInlineEnd: space.s1_5 },
     2: { paddingInlineEnd: space.s2 },
     3: { paddingInlineEnd: space.s3 },
     4: { paddingInlineEnd: space.s4 },
     5: { paddingInlineEnd: space.s5 },
     6: { paddingInlineEnd: space.s6 },
     8: { paddingInlineEnd: space.s8 },
     10: { paddingInlineEnd: space.s10 },
     12: { paddingInlineEnd: space.s12 },
   })

   const paddingBlockStartStyles = stylex.create({
     0: { paddingBlockStart: space.s0 },
     0.5: { paddingBlockStart: space.s0_5 },
     1: { paddingBlockStart: space.s1 },
     1.5: { paddingBlockStart: space.s1_5 },
     2: { paddingBlockStart: space.s2 },
     3: { paddingBlockStart: space.s3 },
     4: { paddingBlockStart: space.s4 },
     5: { paddingBlockStart: space.s5 },
     6: { paddingBlockStart: space.s6 },
     8: { paddingBlockStart: space.s8 },
     10: { paddingBlockStart: space.s10 },
     12: { paddingBlockStart: space.s12 },
   })

   const paddingBlockEndStyles = stylex.create({
     0: { paddingBlockEnd: space.s0 },
     0.5: { paddingBlockEnd: space.s0_5 },
     1: { paddingBlockEnd: space.s1 },
     1.5: { paddingBlockEnd: space.s1_5 },
     2: { paddingBlockEnd: space.s2 },
     3: { paddingBlockEnd: space.s3 },
     4: { paddingBlockEnd: space.s4 },
     5: { paddingBlockEnd: space.s5 },
     6: { paddingBlockEnd: space.s6 },
     8: { paddingBlockEnd: space.s8 },
     10: { paddingBlockEnd: space.s10 },
     12: { paddingBlockEnd: space.s12 },
   })

   const styles = stylex.create({
     base: { display: "flex", minWidth: 0 },
     horizontal: { flexDirection: "row" },
     vertical: { flexDirection: "column" },
     grow: { flexGrow: 1 },
     scrollable: { overflow: "auto", minHeight: 0 },
   })

   const wrapStyles = stylex.create({
     nowrap: { flexWrap: "nowrap" },
     wrap: { flexWrap: "wrap" },
     "wrap-reverse": { flexWrap: "wrap-reverse" },
   })

   const justifyStyles = stylex.create({
     start: { justifyContent: "flex-start" },
     center: { justifyContent: "center" },
     end: { justifyContent: "flex-end" },
     between: { justifyContent: "space-between" },
     around: { justifyContent: "space-around" },
     evenly: { justifyContent: "space-evenly" },
   })

   const alignStyles = stylex.create({
     start: { alignItems: "flex-start" },
     center: { alignItems: "center" },
     end: { alignItems: "flex-end" },
     stretch: { alignItems: "stretch" },
     baseline: { alignItems: "baseline" },
   })

   type SizeValue = number | string

   const toSize = (value: SizeValue) => (typeof value === "number" ? `${value}px` : value)

   type StackOwnProps = {
     direction?: "horizontal" | "vertical"
     justify?: keyof typeof justifyStyles
     align?: keyof typeof alignStyles
     gap?: SpacingStep
     padding?: SpacingStep
     paddingInline?: SpacingStep
     paddingInlineStart?: SpacingStep
     paddingInlineEnd?: SpacingStep
     paddingBlock?: SpacingStep
     paddingBlockStart?: SpacingStep
     paddingBlockEnd?: SpacingStep
     wrap?: keyof typeof wrapStyles
     grow?: boolean
     scrollable?: boolean
     width?: SizeValue
     height?: SizeValue
     maxWidth?: SizeValue
     minHeight?: SizeValue
     xstyle?: StyleXStyles
     style?: CSSProperties
     className?: string
     children?: ReactNode
   }

   type StackProps<T extends ElementType = "div"> = StackOwnProps &
     Omit<useRender.ComponentProps<T>, keyof StackOwnProps>

   function Stack<T extends ElementType = "div">({
     render,
     ref,
     direction = "vertical",
     justify,
     align,
     gap,
     padding,
     paddingInline,
     paddingInlineStart,
     paddingInlineEnd,
     paddingBlock,
     paddingBlockStart,
     paddingBlockEnd,
     wrap,
     grow,
     scrollable,
     width,
     height,
     maxWidth,
     minHeight,
     xstyle,
     style,
     className,
     ...props
   }: StackProps<T>) {
     const inlineStart = paddingInlineStart ?? paddingInline ?? padding
     const inlineEnd = paddingInlineEnd ?? paddingInline ?? padding
     const blockStart = paddingBlockStart ?? paddingBlock ?? padding
     const blockEnd = paddingBlockEnd ?? paddingBlock ?? padding

     const sx = stylex.props(
       styles.base,
       direction === "vertical" ? styles.vertical : styles.horizontal,
       justify && justifyStyles[justify],
       align && alignStyles[align],
       gap != null && gapStyles[gap],
       inlineStart != null && paddingInlineStartStyles[inlineStart],
       inlineEnd != null && paddingInlineEndStyles[inlineEnd],
       blockStart != null && paddingBlockStartStyles[blockStart],
       blockEnd != null && paddingBlockEndStyles[blockEnd],
       wrap != null && wrapStyles[wrap],
       grow && styles.grow,
       scrollable && styles.scrollable,
       xstyle,
     )

     const sizing: CSSProperties = {
       ...sx.style,
       ...(width != null && { width: toSize(width) }),
       ...(height != null && { height: toSize(height) }),
       ...(maxWidth != null && { maxWidth: toSize(maxWidth) }),
       ...(minHeight != null && { minHeight: toSize(minHeight) }),
       ...style,
     }

     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "stack",
         "data-orientation": direction,
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: Object.keys(sizing).length > 0 ? sizing : undefined,
       },
     })
   }

   function HStack<T extends ElementType = "div">(props: Omit<StackProps<T>, "direction">) {
     return <Stack<T> {...(props as StackProps<T>)} direction="horizontal" />
   }

   function VStack<T extends ElementType = "div">(props: Omit<StackProps<T>, "direction">) {
     return <Stack<T> {...(props as StackProps<T>)} direction="vertical" />
   }

   export { HStack, Stack, VStack }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import { HStack, Stack, VStack } from "@/components/ui/stack"
```

```tsx
<HStack align="center" gap={2}>
  <Icon />
  <span>Label</span>
</HStack>
```

Use `Stack` when the styling is only the layout. Give an element with its own
background, border or type a `stylex.create()` block, and put its layout there.

## Direction

`Stack` runs vertically. `HStack` and `VStack` fix `direction`. Pass
`direction` yourself only when the value is computed.

```tsx
import * as stylex from "@stylexjs/stylex"
import { HStack, VStack } from "@/components/ui/stack"
import { color, radius, space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  card: {
    borderRadius: radius.md,
    borderWidth: 1,
    borderStyle: "solid",
    backgroundColor: `color-mix(in oklab, ${color.muted} 40%, transparent)`,
    paddingInline: space.s3,
    paddingBlock: space.s2,
  },
})

export default function StackDirection() {
  return (
    <VStack gap={4}>
      <HStack gap={2}>
        <div {...stylex.props(styles.card)}>One</div>
        <div {...stylex.props(styles.card)}>Two</div>
        <div {...stylex.props(styles.card)}>Three</div>
      </HStack>
      <VStack gap={2}>
        <div {...stylex.props(styles.card)}>One</div>
        <div {...stylex.props(styles.card)}>Two</div>
      </VStack>
    </VStack>
  )
}
```

## Alignment

Use `justify` and `align` for `justify-content` and `align-items`, without the
`flex-` prefixes.

```tsx
import * as stylex from "@stylexjs/stylex"
import { HStack, VStack } from "@/components/ui/stack"
import { color, radius, space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  row: {
    borderRadius: radius.lg,
    borderWidth: 1,
    borderStyle: "solid",
    padding: space.s2,
  },
  tall: {
    borderRadius: radius.md,
    backgroundColor: color.muted,
    paddingInline: space.s3,
    paddingBlock: space.s2,
  },
  short: {
    borderRadius: radius.md,
    backgroundColor: `color-mix(in oklab, ${color.muted} 40%, transparent)`,
    paddingInline: space.s3,
    paddingBlock: space.s1,
  },
})

export default function StackAlignment() {
  return (
    <VStack gap={3} width="20rem">
      <HStack justify="between" align="center" gap={2} xstyle={styles.row}>
        <div {...stylex.props(styles.tall)}>between</div>
        <div {...stylex.props(styles.short)}>center</div>
      </HStack>
      <HStack justify="end" align="end" gap={2} xstyle={styles.row}>
        <div {...stylex.props(styles.tall)}>end</div>
        <div {...stylex.props(styles.short)}>end</div>
      </HStack>
    </VStack>
  )
}
```

## Spacing

Every spacing prop takes a step off the scale. Each step is `0.25rem` times its
number, so `gap={3}` is `0.75rem`. An off-scale value is a type error.

```
0  0.5  1  1.5  2  3  4  5  6  8  10  12
```

Padding props resolve most-specific-first, per edge. An edge prop beats its axis
prop, which beats `padding`.

```tsx
<VStack padding={4} paddingBlockEnd={8} />
```

## Sizing

`width`, `height`, `maxWidth` and `minHeight` take a number, read as pixels, or
a CSS length string. `Stack` writes them to the element's inline `style`.

```tsx
<VStack gap={4} maxWidth="28rem" />
```

## Scrolling

Set `scrollable` on a flex child that has to scroll. It sets `overflow: auto`
and the `min-height: 0` the child needs, or its automatic minimum size holds it
at content height and the scrollbar never appears. Pair it with `grow`.

```tsx
<VStack height="100svh">
  <Header />
  <VStack grow scrollable>
    <LongContent />
  </VStack>
</VStack>
```

## Composition

Use `render` to choose a semantic element.

```tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { HStack, VStack } from "@/components/ui/stack"

export default function StackRender() {
  return (
    <Card render={<section aria-label="Project" />}>
      <CardHeader>
        <CardTitle render={<h3>Project</h3>} />
      </CardHeader>
      <CardContent>
        <VStack gap={3}>
          <span>Choose where to continue.</span>
          <HStack render={<nav aria-label="Project links" />} gap={4}>
            <a href="#usage">Usage</a>
            <a href="#api-reference">API reference</a>
          </HStack>
        </VStack>
      </CardContent>
    </Card>
  )
}
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

### Stack

| Prop                 | Type                                                                | Default      |
| -------------------- | ------------------------------------------------------------------- | ------------ |
| `direction`          | `"horizontal" \| "vertical"`                                        | `"vertical"` |
| `justify`            | `"start" \| "center" \| "end" \| "between" \| "around" \| "evenly"` | —            |
| `align`              | `"start" \| "center" \| "end" \| "stretch" \| "baseline"`           | —            |
| `gap`                | `SpacingStep`                                                       | —            |
| `padding`            | `SpacingStep`                                                       | —            |
| `paddingInline`      | `SpacingStep`                                                       | —            |
| `paddingInlineStart` | `SpacingStep`                                                       | —            |
| `paddingInlineEnd`   | `SpacingStep`                                                       | —            |
| `paddingBlock`       | `SpacingStep`                                                       | —            |
| `paddingBlockStart`  | `SpacingStep`                                                       | —            |
| `paddingBlockEnd`    | `SpacingStep`                                                       | —            |
| `wrap`               | `"nowrap" \| "wrap" \| "wrap-reverse"`                              | —            |
| `grow`               | `boolean`                                                           | `false`      |
| `scrollable`         | `boolean`                                                           | `false`      |
| `width`              | `string \| number`                                                  | —            |
| `height`             | `string \| number`                                                  | —            |
| `maxWidth`           | `string \| number`                                                  | —            |
| `minHeight`          | `string \| number`                                                  | —            |
| `xstyle`             | `StyleXStyles`                                                      | —            |
| `style`              | `CSSProperties`                                                     | —            |
| `className`          | `string`                                                            | —            |

Pass element-specific props on the render element, such as `render={<Link to="/" />}`.
Use a type argument such as `VStack<"fieldset">` when passing a fieldset ref.

### HStack, VStack

`Stack` with `direction` fixed. They take every other `Stack` prop.

### Data attributes

| Attribute           | On   | Effect              |
| ------------------- | ---- | ------------------- |
| `data-slot="stack"` | root | Targets the layout  |
| `data-orientation`  | root | Mirrors `direction` |
