Skip to content

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

--k-space-1 --k-space-2 --k-space-3 --k-space-4 --k-space-5 --k-space-6 --k-space-7 --k-space-8

Where each step lives

a card

cards pad with space-5

onetwo

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 --wide for 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>
.k-container--narrow · 46rem
.k-container · 72rem
.k-container--wide · 90rem
.k-container--gutter · fluid margins, no cap
.k-container--full · edge to edge

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.

one
two
three
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.

one
two · --k-flow: var(--k-space-6)
three
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.

a button
a longer item
tag
another
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.

first half
second half
wide start (1.6fr)
1fr
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.

1/3
2/3
3/3
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.

card
card
card
card
card
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.

1/2
2/2
1/3
2/3
3/3
1/4
2/4
3/4
4/4
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.

text-align: center
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.

__hero (2×2)
cell
cell
__wide (2×1)
__tall (1×2)
cell
cell
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.

sidebar · minmax(14rem, 1fr)
main · 3fr
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.

sticky · top: --k-space-4
tall content
keeps going
and going
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).

Placeholder landscape

k-frame · 16:9

Placeholder landscape, square crop

--square · 1:1

Placeholder landscape, 4:3 crop

--4x3

Placeholder landscape, portrait crop

--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:

NameWidthUsed by
sm640px.k-thirds, .k-cols-2, .k-cols-3, .k-cols-4 (to 2 cols)
md820px.k-split, .k-sidebar, the mobile/desktop-only utilities
lg1080px.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>