78 UI Kit
Guides

Layout

The kit has no twelve-column grid, no .row, no .col-6 — on purpose. Not because layout does not matter, but because the browser already does it. This page shows what the old grid frameworks were solving, and the native CSS that replaced them.

Why there is no grid system

A grid framework is the one component almost every kit ships and the one this kit deliberately leaves out. The reason is simple: CSS now has a real layout system, and it is standard. Grid, Flexbox and the gap property do everything the old .row / .col / gutter model did — with less markup, no wrapper divs, and nothing to learn that is not the actual language.

So a kit that ships its own twelve-column grid today is handing you a heavier, non-standard wrapper around something your browser already does — and, worse, it quietly stops you learning the real thing. That is the trap this page exists to keep you out of: reaching for a kit instead of the two or three lines of CSS that would have done it and stayed with you forever.

What a grid framework was actually solving

Bootstrap’s grid was a genuinely brilliant answer — to a 2013 problem. Back then CSS had no layout tools at all: you positioned things by floating them and clearing the float, and there was no way to put space between items without hacks. The grid papered over all of that — a .row with negative margins, .col-* children with matching padding to fake the gutters, and a pile of responsive classes because there was no other way to reflow.

Every one of those problems has since been fixed in the platform. Grid landed in every browser in 2017; gap works in Flexbox too as of 2021; container queries arrived in 2023. The framework solved a real problem — the problem just does not exist anymore.

The cheat sheet

If you know the Bootstrap grid, you already know the shape of this. Here is the whole translation:

Bootstrap gridNative CSS today
.containermax-width + margin-inline: auto
.rowdisplay: grid (or display: flex)
gutters (row negative margin + col padding)gap — one property, no hacks
.col-6grid-column: span 6 on a 12-track grid
.col-md-4 (responsive)repeat(auto-fit, minmax(16rem, 1fr)) — often no breakpoint at all
.col (fluid)flex: 1
.g-3 (gutter size)gap: 1rem
nesting a .row in a .cola grid inside a grid — just works, no wrapper

The one row worth staring at is the gutters one. gap is your gutters — the negative-margin dance the old grids did existed only because there was no gap. There is now, and it works in both Grid and Flexbox.

The pattern you will use most

Nine times out of ten you do not want fixed columns at all — you want “lay these cards out, as many across as fit, and reflow on small screens.” That is one rule, and it needs no breakpoints:

auto-fit cards — no media queries
.cards {
  display: grid;
  /* as many columns as fit, each at least 16rem, sharing the space */
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-4);
}
the markup
<div class="cards">
  <div class="_78-card">…</div>
  <div class="_78-card">…</div>
  <div class="_78-card">…</div>
</div>

That single repeat(auto-fit, minmax(…)) replaces the whole stack of col-sm / col-md / col-lg classes. The browser does the reflow; you wrote three lines and no breakpoints.

When you really do want columns

Sometimes you genuinely need set proportions — a wide main area beside a narrow rail. Ask for a twelve-track grid and span across it. This is the Bootstrap grid, in native CSS, in five lines:

a 12-column layout
.layout {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-4);
}
.layout__main { grid-column: span 8; }
.layout__rail { grid-column: span 4; }

For a plain horizontal row — a toolbar, a set of buttons — Flexbox is even less to write, and gap spaces it for you:

a flex row
.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

What the kit gives you instead

Not a grid framework — a handful of small conveniences on top of the native layout above:

  • Spacing and flex utilities for the everyday one-liners — stacking, gaps, letting an element grow. See Utilities for the full list. They are shorthands for the same CSS above, not a layer over it.
  • ._78-grid — the auto-fit pattern above as one class, with the one part that is genuinely hard solved for you: a child that spans two columns when there is room and the whole row when there is not. Below.
  • ._78-kpi-grid and ._78-card-grid — presets of ._78-grid for dashboard stat rows and general cards, so you do not re-type the minimum on every page.
  • Design tokens for spacing — the --space-* scale used in every snippet here, so your gaps stay consistent with the rest of the UI instead of being one-off pixel values.

The rule the kit holds to: it adds a component only when the browser makes something genuinely hard and the kit can solve it once, well. Layout is not that — the platform already solved it. The kit gives you the tokens and a couple of presets, and gets out of the way of the CSS you should own.

._78-grid

Auto-fit columns no narrower than --min (200px by default), a --space-4 gap, and no breakpoints. Set --min on the grid itself to change the track size.

Spanning is the part plain auto-fit gets wrong. Give a child grid-column: span 2 and the moment the grid is down to one column it invents a second, implicit one, and the row runs off the side of the screen. ._78-col-span-2 is two columns when the grid has two, and the whole row when it has one; ._78-col-full is always the whole row.

Span 2

Two columns when there's room, the whole row when there isn't.

One

A plain cell.

Two

A plain cell.

Full

Always the whole row.

_78-grid with spans
<div class="_78-grid">
  <div class="_78-card _78-col-span-2">
    <div class="_78-card-title">Span 2</div>
    <p class="_78-text-dim">Two columns when there's room, the whole row when there isn't.</p>
  </div>
  <div class="_78-card">
    <div class="_78-card-title">One</div>
    <p class="_78-text-dim">A plain cell.</p>
  </div>
  <div class="_78-card">
    <div class="_78-card-title">Two</div>
    <p class="_78-text-dim">A plain cell.</p>
  </div>
  <div class="_78-card _78-col-full">
    <div class="_78-card-title">Full</div>
    <p class="_78-text-dim">Always the whole row.</p>
  </div>
</div>

._78-kpi-grid and ._78-card-grid are presets of it, so a stat card can span on a dashboard row too — the wide account card next to the narrow ones:

Account value
$48,290
the wide card on the row
Open P/L
+$1,204
Win rate
61%
a spanning stat card
<div class="_78-kpi-grid">
  <div class="_78-stat-card _78-col-span-2">
    <div class="_78-stat-card__label">Account value</div>
    <div class="_78-stat-card__num">$48,290</div>
    <div class="_78-stat-card__sub">the wide card on the row</div>
  </div>
  <div class="_78-stat-card">
    <div class="_78-stat-card__label">Open P/L</div>
    <div class="_78-stat-card__num _78-success">+$1,204</div>
  </div>
  <div class="_78-stat-card">
    <div class="_78-stat-card__label">Win rate</div>
    <div class="_78-stat-card__num">61%</div>
  </div>
</div>
Class / propertyWhat it doesSpan 2 from
_78-gridAuto-fit grid, --min: 200px416px wide
_78-card-gridThe same, for cards416px
_78-kpi-gridPreset, --min: 190px396px
_78-kpi-grid-smWith _78-kpi-grid: --min: 150px316px
_78-kpi-grid-lgWith _78-kpi-grid: --min: 240px496px
--minThe narrowest a column gets. Set it on the grid: style="--min: 260px"—
_78-col-span-2Two columns when the grid has two; the whole row when it has one—
_78-col-fullAlways the whole row—
How the span decides

"Room for two" is a container query on the grid, and a container query can't read a custom property — so each preset switches at its own fixed width, two minimums plus the gap. With a --min of your own the span still switches at the preset's width; use ._78-col-full there, or the preset whose minimum matches.

Only a grid with a spanning child becomes a size container. That keeps every other grid exactly as it was, but a grid that does have one needs a width from its surroundings: it is fine in normal flow or a grid cell, and in a flex row give it flex: 1.