Grid
since v2.1.0CSS Grid container — enforces a 12-column grid vocabulary and spacing-scale gap values.
Install
Add the package and import the component.
pnpm add @hey-mike/tungstenpnpm add @hey-mike/tungstenimport { Grid } from '@hey-mike/tungsten';import { Grid } from '@hey-mike/tungsten';Preview
Same fixtures used by the visual-regression suite.
Usage
apps/docs/app/snapshots/grid/page.tsx
import { Grid } from '@hey-mike/tungsten';
import { HeroSpecimen } from '../_components/HeroSpecimen';
const COLS = [2, 3, 4, 6] as const;
const ITEMS_PER_COL: Record<number, number> = { 2: 4, 3: 6, 4: 8, 6: 6 };
function Cell({ label }: { label: string }) {
return (
<div className="bg-brand-overlay-08 border border-brand-overlay-20 rounded-lg py-2.5 text-center font-mono text-xs text-ink-2">
{label}
</div>
);
}
export default function GridSnapshot() {
return (
<main data-testid="snapshot-root" className="bg-page text-ink-1 min-h-screen py-8">
<h1 className="text-ink-2 mb-8 px-8 font-mono text-sm uppercase tracking-widest">Grid</h1>
<dl className="flex flex-col gap-8 px-8">
{COLS.map((cols) => (
<div key={cols}>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols={cols} · gap=4</dt>
<dd className="m-0">
<Grid cols={cols} gap={4}>
{Array.from({ length: ITEMS_PER_COL[cols] }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
))}
<div>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols=3 · gap=6 · collapseOnMobile</dt>
<dd className="m-0">
<Grid cols={3} gap={6} collapseOnMobile>
{Array.from({ length: 6 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
<div>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols=4 · gap=2</dt>
<dd className="m-0">
<Grid cols={4} gap={2}>
{Array.from({ length: 8 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
</dl>
{/* Gallery thumbnail (`?hero`): a tidy 3-col grid reads as "grid" at a
glance. Hidden in the detail view; revealed by the hero CSS.
The inner fixed width is required: in the `?hero` flex-centering
context HeroSpecimen's `w-full` has no definite parent to resolve
against, so without it the Grid collapses to min-content columns
(skinny pills). A fixed px width gives the columns room to divide. */}
<div data-hero-specimen className="hidden">
<HeroSpecimen>
<div className="w-[228px]">
<Grid cols={3} gap={4}>
{Array.from({ length: 6 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</div>
</HeroSpecimen>
</div>
</main>
);
}
import { Grid } from '@hey-mike/tungsten';
import { HeroSpecimen } from '../_components/HeroSpecimen';
const COLS = [2, 3, 4, 6] as const;
const ITEMS_PER_COL: Record<number, number> = { 2: 4, 3: 6, 4: 8, 6: 6 };
function Cell({ label }: { label: string }) {
return (
<div className="bg-brand-overlay-08 border border-brand-overlay-20 rounded-lg py-2.5 text-center font-mono text-xs text-ink-2">
{label}
</div>
);
}
export default function GridSnapshot() {
return (
<main data-testid="snapshot-root" className="bg-page text-ink-1 min-h-screen py-8">
<h1 className="text-ink-2 mb-8 px-8 font-mono text-sm uppercase tracking-widest">Grid</h1>
<dl className="flex flex-col gap-8 px-8">
{COLS.map((cols) => (
<div key={cols}>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols={cols} · gap=4</dt>
<dd className="m-0">
<Grid cols={cols} gap={4}>
{Array.from({ length: ITEMS_PER_COL[cols] }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
))}
<div>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols=3 · gap=6 · collapseOnMobile</dt>
<dd className="m-0">
<Grid cols={3} gap={6} collapseOnMobile>
{Array.from({ length: 6 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
<div>
<dt className="text-ink-3 mb-2 font-mono text-xs">cols=4 · gap=2</dt>
<dd className="m-0">
<Grid cols={4} gap={2}>
{Array.from({ length: 8 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</dd>
</div>
</dl>
{/* Gallery thumbnail (`?hero`): a tidy 3-col grid reads as "grid" at a
glance. Hidden in the detail view; revealed by the hero CSS.
The inner fixed width is required: in the `?hero` flex-centering
context HeroSpecimen's `w-full` has no definite parent to resolve
against, so without it the Grid collapses to min-content columns
(skinny pills). A fixed px width gives the columns room to divide. */}
<div data-hero-specimen className="hidden">
<HeroSpecimen>
<div className="w-[228px]">
<Grid cols={3} gap={4}>
{Array.from({ length: 6 }, (_, i) => (
<Cell key={i} label={`${i + 1}`} />
))}
</Grid>
</div>
</HeroSpecimen>
</div>
</main>
);
}
Props
Surface specific to <Grid />.
| Prop | Type | Default | Description |
|---|---|---|---|
| cols | 1 | 2 | 3 | 4 | 6 | 12 | 12 | Number of equal-width grid columns. Constrained to ColCount (1, 2, 3, 4, 6, or 12) —
the values that divide cleanly into a 12-column grid. Use className for non-standard
column counts. |
| gap | 0 | 1 | 2 | 3 | 4 | 6 | 12 | 8 | 10 | 4 | — |
| collapseOnMobile | boolean | false | When true, applies a fixed mobile-first preset that collapses columns on
narrower viewports. Non-standard column counts (5, 7–11) are not supported;
use className for those cases.
Note: the default cols={12} collapses to grid-cols-2 on mobile. Pass an
explicit cols value if you need a different small-screen column density.
Preset per col count:
- 1: no change (always 1 col)
- 2: grid-cols-1 → sm:grid-cols-2
- 3: grid-cols-1 → sm:grid-cols-2 → md:grid-cols-3
- 4: grid-cols-1 → sm:grid-cols-2 → md:grid-cols-4
- 6: grid-cols-2 → sm:grid-cols-3 → md:grid-cols-6
- 12: grid-cols-2 → sm:grid-cols-4 → md:grid-cols-12 |