Layout
Full-bleed bands, five container widths, and structural arrangements — direction, gap, and wrapping, never x/y placement.
Spacing
Eight steps: 1–4 are static, for spacing inside components;
5–8 are fluid, for section rhythm — resize the window and watch
them breathe. Everything multiplies by --k-density.
The scale — each bar is the token's actual width, live
Where each step lives
a card
cards pad with space-5
clusters gap with space-3
a band
sections breathe with space-7
Using space tokens in your own CSS
.feature { padding: var(--k-space-5); } /* fluid — breathes */
.toolbar { gap: var(--k-space-3); } /* static — steady */
.closing { padding-block: var(--k-space-7); } /* section rhythm */
:root { --k-density: 0.85; } /* the whole scale tightens */
Bands
The whole layer is two ideas: bands (a
section with a container inside) and
arrangements (how the things inside a container
relate to each other).
<section class="k-section">
<div class="k-container">
<!-- content -->
</div>
</section>
.k-section is a full-width band with generous
vertical padding and no background of its own; modifiers change one
thing each — shown here as compact strips with each class applied
directly:
k-section — plain band, no background
k-section--alt
k-section--tint
k-section--tint-2
k-section--accent
.k-section--tight- Less vertical padding, for bands that shouldn't breathe as much.
.k-section--alt- Surface background with a top and bottom border.
.k-section--tint- Soft accent background.
.k-section--tint-2- Soft secondary background — same idea, second color.
.k-section--accent- Full accent background with inverted text — use sparingly.
Alternating plain and --alt bands is the usual rhythm
of a keel page.
The band's vertical padding is itself a knob,
--k-section-pad — that's all --tight
actually does:
<section class="k-section" style="--k-section-pad: var(--k-space-8)">…</section>
Containers
Bands are always full width; the container inside each band decides how wide the content runs — the full story of that pairing is on Concepts.
.k-container- The default reading width (
--k-width, 72rem). .k-container--narrow- Prose width (46rem).
.k-container--wide- Wide content like grids of cards (90rem).
.k-container--gutter- Fluid side margins via
--k-gutter, no width cap. Prefer it over--widefor full-screen layouts that should breathe rather than stop at a max-width. .k-container--full- Edge to edge, with a small inline padding.
<div class="k-container--gutter k-container">…</div>
Modifiers are used together with the base class, e.g.
class="k-container--narrow k-container"; on small
screens they all resolve to roughly full width minus a gutter.
Arrangements
The structural classes below describe how children relate inside a container — direction, gap, and wrapping, nothing about where anything sits on the page.
Stack
.k-stack lays children in a column with a
consistent gap; --tight and --loose
shrink or grow that gap.
Markup
<div class="k-stack"> <!-- or k-stack--tight / k-stack--loose -->
<div>one</div>
<div>two</div>
<div>three</div>
</div>
Flow
.k-flow is owl spacing: a margin between siblings
instead of a flex gap, so unlike .k-stack it keeps
content in normal flow. The gap is a custom-property knob —
set --k-flow on the element to tune it per use;
it defaults to --k-space-4.
Markup
<div class="k-flow"> <!-- default gap: --k-space-4 -->
<p>…</p>
<p>…</p>
</div>
<div class="k-flow" style="--k-flow: var(--k-space-6)">
…
</div>
Cluster
.k-cluster lays children in a row that wraps,
vertically centered — buttons, tags, meta rows.
Markup
<div class="k-cluster">
<a class="k-btn" href="#">Primary</a>
<a class="k-btn k-btn--ghost" href="#">Secondary</a>
</div>
Split
.k-split is two equal columns; add
--wide-start to make the first column wider
(1.6:1). Below 820px both stack into a single column.
Markup
<div class="k-split"> <!-- 1fr 1fr -->
<div>…</div>
<div>…</div>
</div>
<div class="k-split k-split--wide-start"> <!-- 1.6fr 1fr -->
<div>…</div>
<div>…</div>
</div>
Thirds
.k-thirds is exactly three equal columns.
Below 640px they stack.
Markup
<div class="k-thirds">
<div>…</div>
<div>…</div>
<div>…</div>
</div>
Grid
.k-grid is a responsive card grid: as many
columns as fit at a 15rem minimum (auto-fit +
minmax), wrapping by itself — no breakpoints, no
column counts to manage.
Markup
<div class="k-grid">
<div class="k-card">…</div>
<div class="k-card">…</div>
<div class="k-card">…</div>
</div>
Columns
.k-cols-2, .k-cols-3 and
.k-cols-4 are fixed-count grids for when you know
exactly how many columns you want — unlike .k-grid,
which decides for itself. All three collapse to a single column
below 640px; .k-cols-4 takes an intermediate step
and stays at two columns until 1080px.
Markup
<div class="k-cols-2">…</div> <!-- 2 cols ≥640px, 1 below -->
<div class="k-cols-3">…</div> <!-- 3 cols ≥640px, 1 below -->
<div class="k-cols-4">…</div> <!-- 4 cols ≥1080px, 2 from 640px, 1 below -->
Center
.k-center centers text. That's all it does —
it doesn't center blocks or position anything.
Markup
<div class="k-container--narrow k-container k-center">
<h2>A centered closing band</h2>
<p>…</p>
</div>
Bento
.k-bento is a dense feature grid: uniform cells
with a few spanning stars. Cells are your own markup — cards
work well. .k-bento__wide spans two columns,
.k-bento__tall two rows, .k-bento__hero
both; dense flow backfills the gaps, and everything stacks to
one column on phones. Four columns from 1080px, two from
640px.
Markup
<div class="k-bento">
<div class="k-card k-bento__hero">the star</div>
<div class="k-card">…</div>
<div class="k-card k-bento__wide">…</div>
<div class="k-card k-bento__tall">…</div>
</div>
Sidebar & sticky
Sidebar
.k-sidebar is a sidebar-plus-main split: from
820px the first child becomes a sidebar
(minmax(14rem, 1fr)) and the second child takes
three times the room (3fr). Below 820px they stack.
Markup
<div class="k-sidebar">
<nav>…</nav> <!-- sidebar -->
<main>…</main> <!-- main -->
</div>
Sticky
.k-sticky keeps an element pinned while its
container scrolls past, resting --k-space-4 from the
top. It only does something when a sibling is taller
than it — a sticky element alone in a short container just sits
there. The usual pairing is the sidebar above: scroll this demo's
column and the left box stays put.
Markup
<div class="k-sidebar">
<nav class="k-sticky">…</nav> <!-- pins while main scrolls -->
<main>…</main>
</div>
Frame
.k-frame crops media to a fixed aspect ratio —
16:9 by default — with object-fit: cover, so any
image, video or iframe fills it without distortion. Modifiers:
--square (1:1), --4x3, and
--portrait (3:4).
k-frame · 16:9
--square · 1:1
--4x3
--portrait · 3:4
Markup
<div class="k-frame"> <!-- 16:9 -->
<img src="photo.jpg" alt="…">
</div>
<div class="k-frame k-frame--square">…</div> <!-- 1:1 -->
<div class="k-frame k-frame--4x3">…</div> <!-- 4:3 -->
<div class="k-frame k-frame--portrait">…</div> <!-- 3:4 -->
Breakpoints
keel has exactly three fixed breakpoints, and every responsive rule in the framework uses one of them:
| Name | Width | Used by |
|---|---|---|
sm | 640px | .k-thirds, .k-cols-2, .k-cols-3, .k-cols-4 (to 2 cols) |
md | 820px | .k-split, .k-sidebar, the mobile/desktop-only utilities |
lg | 1080px | .k-cols-4 (to 4 cols) |
An honest note: these are not tokens, because they can't be — media queries cannot read custom properties. They are three documented numbers, and that's all they will ever be.
Page layouts
These are page skeletons, not components — each one is bands + containers + one arrangement. Steal the shape, then fill it from sections.
Marketing page
Stacked full-bleed bands: hero, a thirds band, a split band, a closing CTA band.
Skeleton
<section class="k-hero">
<div class="k-container">…</div>
</section>
<section class="k-section k-section--alt">
<div class="k-container"><div class="k-thirds">…</div></div>
</section>
<section class="k-section">
<div class="k-container"><div class="k-split">…</div></div>
</section>
<section class="k-section k-section--accent">
<div class="k-container k-center">…</div>
</section>
Documentation
A top nav bar, then a sticky sidebar beside the content — on this very site a right rail joins in from 1280px.
Skeleton
<section class="k-section">
<div class="k-container--wide k-container">
<div class="k-sidebar">
<aside class="k-sticky">…nav…</aside>
<main>…</main>
</div>
</div>
</section>
Article
A narrow prose measure, with one wide figure breaking out between the narrow bands.
Skeleton
<section class="k-section">
<div class="k-container--narrow k-container">…prose…</div>
<div class="k-container"><figure>…wide figure…</figure></div>
<div class="k-container--narrow k-container">…prose…</div>
</section>
App shell
A header bar, a sidebar carrying a list nav, and a content area of cards. keel handles the layout; app behavior is your JS or framework.
Skeleton
<header class="k-section--alt">
<div class="k-container--wide k-container">
<nav class="k-nav">…</nav>
</div>
</header>
<section class="k-section k-section--tight">
<div class="k-container--wide k-container">
<div class="k-sidebar">
<ul class="k-list">…nav items…</ul>
<main><div class="k-cols-2">
<div class="k-card">…</div>
<div class="k-card">…</div>
</div></main>
</div>
</div>
</section>