Grid
A CSS grid layout with responsive column support. Accepts a compact string syntax for breakpoint-based column counts.
Basic Usage
Responsive Columns
Pass a space-separated string with breakpoint prefixes. The syntax mirrors Tailwind's responsive convention.
Gap Sizes
gap names a rung on the --space-wk-* ladder. A grid of cards that should match the spacing inside WireKit's own components takes the tighter --gap-wk-* ladder instead with scale="gap". The rung names stay the same and the values from md up are smaller, as the note under Props shows.
Alignment
Semantic Elements
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:
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.
Sessions
1,284 this week
Signups
96 this week
Revenue
€ 18,400
Churn
1.2 %
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.
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:
{{-- 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.
A template with a breakpoint cannot fall back this way, and it is better to know before the policy ships. Its values still ride in the inline style, as custom properties, but a class reads them — so at a breakpoint the template occupies there is no cols class to fall back to, and without the style the class resolves to no tracks at all. Under a strict policy, keep the responsive layout in cols and use template only where the policy allows inline styles.
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.
Navigation
Content — this pane takes whatever is left
Detail
Anything grid-template-columns accepts works: lengths, fr, auto, min-content, max-content, minmax(), repeat(), fit-content().
A template without a breakpoint is that wide at every viewport, and that is what asking for exact tracks means. Unlike min, it is handed to CSS as written. 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 use a bare track list for a layout that has to work on a phone. Give it a breakpoint instead, as below, or prefer min if every track can flex — it collapses to one column instead of overflowing.
A track list that switches at a breakpoint
The layout applications reach for most: one column on a phone, and from lg a flexible column beside a fixed aside. Prefix a track list with a breakpoint and put it in brackets, an underscore standing for each space — the same spelling a hand-written lg:grid-cols-[…] uses. Tokens without a prefix are the base, and cols fills every breakpoint the template leaves free.
Content — the whole width on a phone, whatever is left beside the aside from lg
Aside — below the content on a phone, 14rem beside it from lg
{{-- 1. One column below lg, two tracks from lg --}}
<x-wirekit::grid cols="1" template="lg:[minmax(0,1fr)_20rem]">…</x-wirekit::grid>
{{-- 2. A base and two breakpoints: two tracks, then the three-pane workspace --}}
<x-wirekit::grid template="1fr md:[1fr_16rem] xl:[14rem_1fr_18rem]">…</x-wirekit::grid>
Keep cols="1" for the phone when the template starts at a breakpoint. It is the one column definition the grid has below that breakpoint, and it is what lets a long word wrap: without it the column sizes itself to its widest unbreakable content, and the page scrolls sideways.
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 — at every width, or switching at a breakpoint | 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". Takes breakpoints in brackets, _ for each space: "lg:[1fr_20rem]". Wins over min and cols |
gap |
string | 'md' |
Spacing: none, xs, sm, md, lg, xl, 2xl |
scale |
string | 'space' |
Which spacing ladder gap names a rung on: space (the default, unchanged) or gap for the tighter --gap-wk-* ladder — see the note below the props |
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. scale="space" (the default) reads --space-wk-*; scale="gap" reads the tighter --gap-wk-* ladder, which uses the same xs…2xl labels with different values from md up and is what WireKit's own components read for spacing inside a component. The rung name alone does not tell you which — gap="md" is 1rem on one ladder and 0.75rem on the other — so changing scale without changing gap re-spaces the container on purpose, and swapping a raw utility for a rung of the same name re-spaces it by accident. See Two spacing ladders, same rung names.
| Rung | scale="space" (default) |
scale="gap" |
|---|---|---|
xs |
0.25rem |
0.25rem |
sm |
0.5rem |
0.5rem |
md |
1rem |
0.75rem |
lg |
1.5rem |
1rem |
xl |
2.5rem |
1.5rem |
2xl |
4rem |
2rem |
Keyboard Interaction
This component is a layout wrapper. Keyboard interaction is delegated to its children.