Skip to content
LuoForge/Tungsten
Components/Grid·Experimental

Grid

since v2.1.0

CSS 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/tungsten
pnpm add @hey-mike/tungsten
import { 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 />.

PropTypeDefaultDescription
cols1 | 2 | 3 | 4 | 6 | 1212Number 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.
gap0 | 1 | 2 | 3 | 4 | 6 | 12 | 8 | 104
collapseOnMobilebooleanfalseWhen 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

Source