@withseismic/grid
v0.1.0
Published
Both axes are twelfths. A layout system where a page is right when its parts sum to twelve in each direction.
Maintainers
Readme
@withseismic/grid
Both axes are twelfths.
The page is twelve columns wide. A row is twelve units tall. The two axes take
the same seven names — full, 2-3, half, third, quarter, sixth,
12th — and the same arithmetic. A row's cells must sum to twelve across; a
split's cells must sum to twelve down.
That is the whole system, and the point of it is that a layout stops being a matter of taste. It is right when it sums, and you can check that by counting instead of by looking.
pnpm add @withseismic/gridimport "@withseismic/grid/grid.css";
import { Page, Row, Cell, Split, Rest, Movement, Hero, GridOverlay } from "@withseismic/grid";
<Page>
<GridOverlay />
<Hero
no="00"
title="Twelve columns, twelve units tall."
support={<p className="t-1 copy">No page margin. No gutter.</p>}
>
<Split w="half">
<Cell v="2-3" media={<Plate />} label="IMAGE:" note="2/3" />
<Cell v="third" label="THE LADDER:" note="1/3">
<span className="t-2">FULL — 2-3 — HALF — THIRD</span>
</Cell>
</Split>
</Hero>
<Movement no="I" title="A page is a sequence of movements." />
<Row h="3rd">
<Cell w="half" label="A:" note="1/2">…</Cell>
<Cell w="quarter" label="B:" note="1/4">…</Cell>
<Cell w="quarter" label="C:" note="1/4">…</Cell>
</Row>
<Rest h="12th" />
</Page>Two numbers, one inset
Twelve columns and a 12px unit. There is no page margin and no gutter: the
cell's own padding (2 units) is the only inset in the system. A margin, a
gutter and a padding are three mechanisms for one job, and two of them make
the third unreadable — with the other two at zero, a column is exactly
page / 12 with no remainder, and the column edge, the hairline and the
padding edge are one line.
Both are available as one-class experiments (html.margins, html.gutters)
because being able to remove them without anything else moving is the argument
for the system, not for the setting.
The unit is also the baseline
12px. Cell padding is 2 baselines, a vertical twelfth is 8, a page is 96. So a line of type in one cell lands on the same line as type in the cell beside it — not merely inside a box of the same height, which is the difference between aligned and coincidentally similar.
The type scale runs on root two: 14, 20, 28, 40, 56, 80, 112, 158, with line boxes in whole baselines. Alternate steps are exact doubles, which is the only ratio where extending the scale cannot make it drift. Half-leading is trimmed, so the padding you set is the padding you see.
Hierarchy is countable
A page is a sequence of movements. One opens full width with a single statement, divides, and closes with a rest. What ranks a row is how many cells it has, and height agrees with it: inside a movement the cell count strictly increases and the height never increases.
Two adjacent rows divided the same way is the anti-pattern — it reads as one region cut in half for no stated reason. If two rows want the same division, they are one row.
Press G and every row prints its own count. A movement is right when the numbers only climb.
Continuity
A grid of closed rows hands the eye off at every boundary — correct for apparatus, wrong for anything meant to be looked at. Media can cross the line:
<Row h="3rd">
<Cell w="third" media={<Plate />} bleed="6th" label="/ 01" />
<Cell w="2-3" label="…">…</Cell>
</Row>
<Row h="6th" ruled={false}>
<Cell w="third" /> {/* left empty to receive the bleed */}
…
</Row>The hairline continues to either side and the picture passes in front of it. Only media bleeds, only downward, one per row. Consecutive bleeds alternate sides — left, right, left — so the page acquires a route rather than a sequence: each hand-off throws the eye across the full measure to pick up the next. Two on the same side send it straight down one column, which is the path it would take anyway. The overlay measures each bleed's side and flags any that repeats.
ruled={false} is the other half: two rows that are one field rather than two
subjects.
/flow on the docs site is the system used as a page rather than described as
one — four pictures, four crossings, one Z.
Overlays
Nothing in this system is asserted that cannot be looked at.
| key | shows | |---|---| | G | the twelve columns, the vertical ladder, and each row's cell count | | B | the baseline, every 12px, brighter every eight | | R | rules off — the honest test of whether the grid is doing its job | | ← / → | density: tight / normal / loose — inner air and the narrowest allowed fraction | | ↑ / ↓ | the page unit — vertical scale, in multiples of 144 |
Margin and gutter remain as html.margins / html.gutters classes for
comparison; they are no longer on keys, because the answer is decided.
Rules off is worth the key it takes. The hairlines are the system made visible, and a page that only works with them on is held together by its own diagram. Turn them off: if the regions are still legible from alignment and air alone, the grid is doing its job. It works by setting the rule colour to the ground rather than removing borders, so nothing moves by a pixel — the only way the comparison means anything.
Two knobs, not one
Looseness is two independent things and they are on different arrows.
Density (← / →) is inner air. Tight is 1X padding, normal 2X, loose 4X — and loose also bans the narrow end of the ladder: no quarters, sixths or twelfths. A page restricted to halves and thirds with double the padding cannot be busy; it can only be well or badly proportioned, which is a much easier problem. The ban is checked rather than enforced, because silently promoting a quarter to a third would break every row it appears in — four quarters make twelve and four thirds make sixteen.
Vertical scale (↑ / ↓) is the page unit. Every named height is a fraction of it, so raising it makes every region taller without changing a single fraction in the source. It steps in multiples of 144 because a twelfth must stay a whole number of 12px baselines: 1152/12 = 96 and 1440/12 = 120 both work, 1200/12 = 100 does not.
That heights are derived from one property rather than listed is what makes this a single line of code instead of a second set of layout rules.
Without React
grid.css is the whole system; the components are a convenience over it. In
Astro, plain HTML, a Webflow export or anything else, use the classes
directly — the arithmetic does not care what rendered the markup.
<main class="wsg">
<section class="frame row h-3rd" data-cells="3">
<div class="cell w-half">
<div class="cell__tl mono mono--dim">A:</div>
<div class="cell__tr mono mono--dim">1/2</div>
<div class="cell__main"><p class="t-4">…</p></div>
<div class="cell__br mono">6</div>
</div>
<div class="cell w-quarter">…</div>
<div class="cell w-quarter">…</div>
</section>
<div class="frame rest h-12th"></div>
</main>The class vocabulary mirrors the components exactly: .row + .h-*,
.cell + .w-* / .v-*, .split, .rail, .rest, .stack--*, .plate,
.t-1….t-8. The only thing you give up is data-cells being counted for
you — set it yourself if you want the overlay to check your rows.
Fonts
The package names families and never ships a typeface. --sans and
--mono are custom properties; point them wherever your licence allows. A
layout system that carries someone's foundry licence around with it is a
liability rather than a convenience.
The docs site sets --mono to Tronica Mono (© 2021 ATK Studio, Radinal
Riki) under WithSeismic's own licence for it. It is a licensed face, so it
lives in the docs app and never in the package.
Theming
grid.css is layout only. Colour and the two faces are custom properties with
neutral defaults — override them, or import theme-spec.css for the dark
technical look the system was designed against.
Docs
https://docs-production-de45.up.railway.app — the spec, and /flow, which
is the system used as a page rather than described as one.
pnpm docs:dev runs it locally on 3200. The documentation is the system
documenting itself, which is the only honest test of it.
Artwork on /flow is this alcohol-ink painting
from Pexels, one image in three crops. <Ink> in the package generates
abstract figures with SVG turbulence when you have no artwork yet.
Releasing
Releases happen in CI, not from a laptop — the same rule as the Hogsend repo, and here it is also a necessity: the npm account has 2FA set to auth-and-writes, so an interactive publish demands a one-time password and an automation token does not.
Once: create an Automation token on npmjs.com, then
gh secret set NPM_TOKEN --repo dougwithseismic/withseismic-gridAfter that a release is two commands:
npm version patch # or minor / major
git push --follow-tags.github/workflows/release.yml type-checks, builds, asserts the tag matches
package.json, and publishes with provenance.
License
MIT.
