Application
Builder and app chrome — the components a page-builder, dashboard, or editor UI needs. Zero JavaScript; states ride ARIA.
App shell
.k-app is the frame: a grid of
__head, __rail, __work,
__side, and __foot that fills the
viewport (min-block-size: 100dvh). The demo below is
scaled down inside a box so it can live on this page — in real
use the shell is the page. Honestly: the rail and side
panels only show at ≥1080px; below that they're hidden and the
shell is header, work, footer. A builder on a phone needs its own
pattern — put the panels in a
drawer.
Markup
<body class="k-app">
<header class="k-app__head">
<div class="k-toolbar">…</div>
</header>
<nav class="k-app__rail" aria-label="Layers">
<ul class="k-tree">…</ul>
</nav>
<main class="k-app__work">
<div class="k-canvas">
<div class="k-artboard">…</div>
</div>
</main>
<aside class="k-app__side" aria-label="Inspector">
<div class="k-props">…</div>
</aside>
<footer class="k-app__foot">
<div class="k-statusbar">…</div>
</footer>
</body>
Toolbar
.k-toolbar is the strip along the top:
__group clusters related tools,
__sep draws a hairline between clusters, and
__spring pushes whatever follows to the far end.
Tools are toggles — press state lives in
aria-pressed, and keel styles it. The segmented
switch is a plain .k-btn-group; inside a group,
aria-pressed="true" gets the same soft-accent
treatment. Moving the attribute between buttons is your JS — the
one-line handlers below are page-demo wiring, not part of keel.
Markup
<div class="k-toolbar">
<div class="k-toolbar__group">
<button class="k-tool" type="button" aria-pressed="false" aria-label="Menu">
<span class="k-icon k-icon--menu"></span>
</button>
<button class="k-tool" type="button" aria-pressed="true" aria-label="Search">
<span class="k-icon k-icon--search"></span>
</button>
</div>
<span class="k-toolbar__sep"></span>
<div class="k-btn-group">
<button class="k-btn k-btn--small k-btn--ghost" aria-pressed="false">Desktop</button>
<button class="k-btn k-btn--small k-btn--ghost" aria-pressed="true">Tablet</button>
<button class="k-btn k-btn--small k-btn--ghost" aria-pressed="false">Mobile</button>
</div>
<span class="k-toolbar__spring"></span>
<button class="k-btn k-btn--small" type="button">Publish</button>
</div>
Tool
.k-tool is the icon toggle on its own — a square
quiet button whose pressed state is
aria-pressed="true": soft accent fill, accent text.
Because it's icon-only, an aria-label is required,
not polite. Flipping the attribute on click is your JS; keel only
draws the two states.
Markup
<!-- pressed -->
<button class="k-tool" type="button" aria-pressed="true" aria-label="Search">
<span class="k-icon k-icon--search"></span>
</button>
<!-- unpressed -->
<button class="k-tool" type="button" aria-pressed="false" aria-label="Insert">
<span class="k-icon k-icon--plus"></span>
</button>
Tree
.k-tree is the layers panel: a nested list where
branches are native details — open and close work
with no script — and every clickable line is a
__row. The chevron is a __twist span
that rotates when its branch opens. The selected row carries
aria-selected="true" and gets the soft-accent fill
with an inset accent edge. Honestly: expanding is native, but
selection, drag-to-reorder, and rename are your application's
JS — keel draws the rows.
-
Page
-
Hero
-
Section
-
Markup
<ul class="k-tree">
<li>
<details open>
<summary class="k-tree__row">
<span class="k-tree__twist"><span class="k-icon k-icon--chevron-right"></span></span>
Page
</summary>
<ul>
<li>
<details open>
<summary class="k-tree__row" aria-selected="true">
<span class="k-tree__twist"><span class="k-icon k-icon--chevron-right"></span></span>
Hero
</summary>
<ul>
<li><button class="k-tree__row" type="button">Heading</button></li>
<li><button class="k-tree__row" type="button">Buttons</button></li>
</ul>
</details>
</li>
</ul>
</details>
</li>
</ul>
Property rows
.k-props is the inspector: a stack of
__rows, each an aligned label-and-control pair, with
__group headings ruling off sections. Controls are
the ordinary form elements — select, range, switch, swatch — just
drawn compact inside a row. It's built to live in
.k-app__side, but it works anywhere a settings
column does.
var(--k-accent)
Markup
<div class="k-props">
<div class="k-props__group">Layout</div>
<div class="k-props__row">
<label for="width">Width</label>
<select id="width"><option>Fill container</option></select>
</div>
<div class="k-props__group">Style</div>
<div class="k-props__row">
<label for="radius">Radius</label>
<input type="range" id="radius" min="0" max="24" value="8">
</div>
<div class="k-props__row">
<label for="shadow">Shadow</label>
<span class="k-switch"><input type="checkbox" id="shadow" checked></span>
</div>
</div>
Canvas & artboard
.k-canvas is the workspace surface — the dotted
grid your page floats on. .k-artboard is the page
itself: an ordinary surface with a page shadow, and whatever's
inside it is real keel markup rendered with real tokens.
.k-selected is the builder outline — the accent ring
that says "this block is selected." The canvas is just the
surface; scrolling and zooming it is layout, not behavior.
Markup
<div class="k-canvas">
<div class="k-artboard">
<!-- the page being built — real keel markup -->
<div class="k-selected">
<h3>Ship the page</h3>
<p>A hero, on an artboard, on the canvas.</p>
<a class="k-btn k-btn--small" href="#">Get started</a>
</div>
</div>
</div>
Status bar
.k-statusbar is the thin strip along the bottom —
zoom level, where you are, whether the work is saved.
__spring pushes the two ends apart, same as in the
toolbar. Everything in it is plain text and icons; it states, it
doesn't act.
Markup
<div class="k-statusbar">
<span>100%</span>
<span class="k-statusbar__spring"></span>
<span>Page / Hero / Heading</span>
<span><span class="k-icon k-icon--check"></span> Saved</span>
</div>
That's the whole tier: keel draws the chrome — shell, strips, rows, surfaces, states. Selecting a block, dragging a layer, undoing an edit — that's your application's JavaScript, and it always was going to be. The split is the point: see Zero JavaScript in concepts.