---
title: Grid
description: CSS grid with responsive column syntax
visibility: guest
draft: false
---

# Grid

A CSS grid layout with responsive column support. Accepts a compact string syntax for breakpoint-based column counts.

## Basic Usage

:::preview{title="3-column grid"}
<x-wirekit::grid cols="1 sm:3">
    <x-wirekit::card><x-wirekit::card.body>One</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Two</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Three</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

## Responsive Columns

Pass a space-separated string with breakpoint prefixes. The syntax mirrors Tailwind's responsive convention.

:::preview{title="Responsive: 1 col → 2 on sm → 3 on lg"}
<x-wirekit::grid cols="1 sm:2 lg:3" gap="md">
    <x-wirekit::card><x-wirekit::card.body>Item 1</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Item 2</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Item 3</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Item 4</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Item 5</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Item 6</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

## Gap Sizes

:::preview{title="Large gap"}
<x-wirekit::grid cols="1 sm:2" gap="lg">
    <x-wirekit::card><x-wirekit::card.body>First</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Second</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

:::preview{title="No gap"}
<x-wirekit::grid cols="1 sm:3" gap="none">
    <x-wirekit::card><x-wirekit::card.body>One</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Two</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Three</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

## Alignment

:::preview{title="Center-aligned grid items"}
<x-wirekit::grid cols="1 sm:3" align="center" gap="md">
    <x-wirekit::card><x-wirekit::card.body>Short</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>This card has more content to demonstrate vertical alignment across grid items</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Short</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

## Semantic Elements

:::preview{title="Grid as section"}
<x-wirekit::grid as="section" cols="1 sm:2" gap="md">
    <x-wirekit::card><x-wirekit::card.body>Left column</x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body>Right column</x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

## Responsive Syntax

The `cols` prop accepts a space-separated string where each token is either a plain number (base columns) or a `breakpoint:number` pair:

```text
cols="1"              → grid-cols-1
cols="1 sm:2 lg:3"   → grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
cols="2 xl:4"         → grid-cols-2 xl:grid-cols-4
```

Supported breakpoints follow Tailwind's defaults: `sm`, `md`, `lg`, `xl`, `2xl`.

## Columns That Follow the Content

`cols` counts columns against the **viewport**. `min` counts them against the **container**: you give the narrowest a column may be, and as many fit as fit — one on a narrow screen.

The difference is invisible until the container is narrower than the window. Open a sidebar, use a split view, drop the grid inside a card, and a `cols="1 md:2 lg:3"` grid keeps rendering three columns because the *window* is still wide. A `min` grid reflows, because it is measuring the box the cards are actually in.

This is the ordinary responsive card grid, so reach for it first and keep `cols` for the cases where the column count is genuinely a design decision per breakpoint.

:::preview{title="A card grid that reflows with its container"}
<x-wirekit::grid min="14rem" gap="md">
    <x-wirekit::card variant="elevated"><x-wirekit::card.body><x-wirekit::heading level="3" size="sm">Sessions</x-wirekit::heading><x-wirekit::text variant="muted">1,284 this week</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card variant="elevated"><x-wirekit::card.body><x-wirekit::heading level="3" size="sm">Signups</x-wirekit::heading><x-wirekit::text variant="muted">96 this week</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card variant="elevated"><x-wirekit::card.body><x-wirekit::heading level="3" size="sm">Revenue</x-wirekit::heading><x-wirekit::text variant="muted">€ 18,400</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card variant="elevated"><x-wirekit::card.body><x-wirekit::heading level="3" size="sm">Churn</x-wirekit::heading><x-wirekit::text variant="muted">1.2 %</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

`min` takes any CSS length — `14rem`, `20ch`, `280px`. A column never overflows its container: below its own minimum the grid drops to a single column instead.

::: warning
**`min` and `template` ride in an inline `style`, so a strict Content-Security-Policy can remove them.** Both take arbitrary values, and an arbitrary value cannot become a class: Tailwind extracts class names from source text, and a track built at runtime leaves its scanner nothing to find. Under a `style-src` policy without `'unsafe-inline'` — Level 3 spells it `style-src-attr` — the browser drops the attribute.

Elsewhere a dropped inline style costs a shade or a width. Here it costs the whole statement: with no track and no columns, the grid stacks into a single column.

Pass `cols` alongside the track prop and that is what you fall back to:

```blade
{{-- The track when the policy allows it; three equal columns when it does not. --}}
<x-wirekit::grid cols="3" min="14rem"> … </x-wirekit::grid>
```

The inline style wins on specificity whenever it is allowed, so the `cols` classes change nothing about what you normally see — they decide what is left when the style never arrives. Approximate by construction, and far better than one column.
:::

## Columns of Different Widths

`cols` only knows **equal** columns. The two commonest application layouts are neither: the three-pane workspace (navigation, content, detail) and the week grid (a time axis plus seven days). `template` takes an explicit track list and hands it to CSS as written.

:::preview{title="The three-pane workspace" desktopOnly}
<x-wirekit::grid template="10rem 1fr 8rem" gap="sm" style="min-height: 9rem;">
    <x-wirekit::card><x-wirekit::card.body><x-wirekit::text size="sm" variant="muted">Navigation</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body><x-wirekit::text size="sm">Content — this pane takes whatever is left</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
    <x-wirekit::card><x-wirekit::card.body><x-wirekit::text size="sm" variant="muted">Detail</x-wirekit::text></x-wirekit::card.body></x-wirekit::card>
</x-wirekit::grid>
:::

Anything `grid-template-columns` accepts works: lengths, `fr`, `auto`, `min-content`, `max-content`, `minmax()`, `repeat()`, `fit-content()`.

::: warning
**`template` is not responsive, and that is what asking for exact tracks means.** Unlike `min`, it is handed to CSS as written, so a fixed track is that wide at every viewport. The example above reserves `10rem + 8rem` before the content pane gets anything: on a 390 px phone that leaves the middle column a few characters wide and pushes the grid past the screen edge. Nothing warns you — it renders, it is simply unreadable.

Do not reach for `template` as a general-purpose column prop. It is for layouts whose track widths are the design: the three-pane workspace, the week grid. Below the width those layouts need, switch to something that folds:

```blade
{{-- One column on a phone, the workspace from `lg` up. --}}
<div class="lg:hidden"><x-wirekit::stack gap="sm">…</x-wirekit::stack></div>
<div class="hidden lg:block"><x-wirekit::grid template="10rem 1fr 8rem" gap="sm">…</x-wirekit::grid></div>
```

If every track can flex, prefer `min` — it collapses to one column instead of overflowing.
:::

```text
template="14rem 1fr 18rem"                  → navigation | content | detail
template="4.5rem repeat(7, minmax(0, 1fr))" → time axis + seven days
template="1fr 1fr 1fr auto"                 → three equal columns and a trailing action
```

### Which of the three to use

| You want | Prop |
| --- | --- |
| A specific column count per breakpoint | `cols` |
| As many columns as fit, at least this wide | `min` |
| Columns of different, named widths | `template` |

Only one column track can exist, so the props do not combine: `template` wins over `min`, and `min` wins over `cols`.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `cols` | string\|int | `1` | Column count. Integer or responsive string: `"1 sm:2 lg:3"` |
| `min` | string\|null | `null` | Minimum column width for a content-driven grid: `"14rem"`. Wins over `cols` |
| `template` | string\|null | `null` | Explicit column track list: `"14rem 1fr 18rem"`. Wins over `min` and `cols` |
| `gap` | string | `'md'` | Spacing: `none`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl` |
| `align` | string\|null | `null` | Vertical alignment: `start`, `center`, `end`, `stretch` |
| `as` | string | `'div'` | HTML element to render |
| `scope` | string\|null | `null` | Scoped personalization name |

**The rung names are shared with a second ladder, and the values are not.** `gap` reads `--space-wk-*`; the tighter `--gap-wk-*` ladder uses the same `xs`…`2xl` labels with different values from `md` up, and WireKit's own components read it for spacing inside a component. Swapping one for the other by name silently re-spaces the container — see [Two spacing ladders, same rung names](/theming/design-tokens#two-spacing-ladders-same-rung-names).

## Keyboard Interaction

This component is a layout wrapper. Keyboard interaction is delegated to its children.

## Design Tokens

Same spacing tokens as [Stack](/components/stack) and [Row](/components/row).
