Skip to content

Feedback

Status at a glance — pills, boxes, toasts, and loading states.

Badges

.k-badge is a small pill. --outline is quiet; --secondary uses the secondary color; --ok, --warn, and --danger use the status tokens.

Badges read two knobs, --k-badge-bg and --k-badge-fg — the status modifiers are nothing but knob-setters. This is the entire definition of .k-badge--ok:

.k-badge--ok { --k-badge-bg: var(--k-ok);
               --k-badge-fg: hsl(0 0% 100%); }

--solid fills with the full accent and its on-accent text; --large steps the type up to text-s with a touch more padding; --dot sets a small leading circle in the current color, for a status pill like ● Live. They stack — a large solid dot is just the three together.

Default Outline Secondary Ok Warn Danger Solid Large Live
Markup
<span class="k-badge">Default</span>
<span class="k-badge k-badge--outline">Outline</span>
<span class="k-badge k-badge--secondary">Secondary</span>
<span class="k-badge k-badge--ok">Ok</span>
<span class="k-badge k-badge--warn">Warn</span>
<span class="k-badge k-badge--danger">Danger</span>
<span class="k-badge k-badge--solid">Solid</span>
<span class="k-badge k-badge--large">Large</span>
<span class="k-badge k-badge--dot">Live</span>

Alerts

.k-alert is a bordered box with a colored start edge; the status modifiers change only that edge. Alerts are static markup — they are not live regions by default. If you inject one dynamically and want screen readers to announce it, add role="status" (or role="alert" for urgent messages) yourself. The edge color is the --k-alert-edge knob, so a custom alert flavor is one line: .alert-tip { --k-alert-edge: var(--k-accent-2); }.

--soft tints the whole surface from that same edge color and drops the border, for a softer fill. It combines with the status modifiers — k-alert--soft k-alert--ok tints from the ok color — and the fill is itself the --k-alert-bg knob if you want to set it directly.

A neutral note. The default edge uses the accent.

Saved. Everything went as expected.

This draft has unsaved changes.

The upload failed. Try again.

Saved — the soft variant, tinted from the ok color.

Markup
<div class="k-alert"><p>A neutral note.</p></div>
<div class="k-alert k-alert--ok"><p>Saved.</p></div>
<div class="k-alert k-alert--warn"><p>Unsaved changes.</p></div>
<div class="k-alert k-alert--danger"><p>The upload failed.</p></div>
<div class="k-alert k-alert--soft k-alert--ok"><p>Saved.</p></div>

Banner

.k-banner is the announcement strip — the one-line note that sits above the header. Soft accent by default; --accent flips it to the full accent with the on-accent text. Links inside inherit the banner color. It reads two knobs, --k-banner-bg and --k-banner-fg, so a one-off flavor is one line of CSS, the same trick as buttons and badges.

--sticky pins the banner to the top of the viewport as the page scrolls (position: sticky; top: 0, above the header at z-index: 20) — for an announcement bar that stays put. It's left out of the live demos here, since a strip that follows the scroll would sit over the rest of the page; the markup is below.

keel is pre-1.0 — names may still change. Read the notes
The accent variant, for the one announcement that matters.
Markup
<div class="k-banner">
  <span>keel is pre-1.0 — names may still change.</span>
  <a href="notes.html">Read the notes</a>
</div>

<div class="k-banner k-banner--accent">
  <span>The accent variant.</span>
</div>

<!-- sticky: stays at the top of the viewport on scroll -->
<div class="k-banner k-banner--sticky">
  <span>keel is pre-1.0 — names may still change.</span>
</div>

Toast

.k-toast is a [popover] pinned to the bottom end of the viewport, with the same colored start edge as an alert. Because it's a native popover it sits in the top layer, closes on Esc, and light-dismisses on an outside click — all free. A button with popovertarget opens it with no script, as below. Showing it from a timer or an event (saved, uploaded, failed) is one line of your JavaScript — el.showPopover() — keel only styles it.

Saved. Your changes are live.

Markup
<button class="k-btn k-btn--ghost" popovertarget="saved-toast">Show toast</button>

<div class="k-toast" id="saved-toast" popover>
  <p>Saved. Your changes are live.</p>
</div>

<!-- from your own code: -->
<!-- document.getElementById("saved-toast").showPopover() -->

Spinner

.k-spinner is a small rotating ring, sized to the surrounding text via em — it fits inline, in a button, or scaled up with a font-size. The element itself is empty; put the state in words next to it and hide the ring from assistive tech, or a screen reader hears nothing at all. Reduced-motion users see a static ring — the text carries the meaning either way.

Building…

Markup
<p role="status"><span class="k-spinner" aria-hidden="true"></span> Building…</p>

<button class="k-btn" disabled>
  <span class="k-spinner" aria-hidden="true"></span> Saving
</button>

Skeleton

.k-skeleton is a shimmering placeholder line, one text-line tall. Width is content-shaped, so set it inline per line — a short one for the heading, longer ones for body text. Hide the whole placeholder from assistive tech; it stands for content, it isn't content.

Markup
<div class="k-card" aria-hidden="true">
  <span class="k-skeleton" style="width: 45%; margin-bottom: var(--k-space-3)"></span>
  <span class="k-skeleton" style="width: 100%; margin-bottom: var(--k-space-2)"></span>
  <span class="k-skeleton" style="width: 70%"></span>
</div>

Progress & meter

Both are base-styled — zero classes. A bare progress renders as a slim rounded track with an accent bar; give it a label, because the bar alone says nothing to a screen reader. meter is for a measurement within a known range, not for task progress — with low/high/optimum set, the bar colors itself from the status tokens: ok in the optimal region, warn outside it, danger when it's far off. Honestly: that coloring rides on the vendor ::-webkit-meter-* pseudo-elements, so it's Chromium-first — Firefox keeps its own default bar (keel only rounds it).

64%
55
85
Markup
<!-- progress: a task moving toward done -->
<label for="upload">Uploading — 64%</label>
<progress id="upload" value="64" max="100">64%</progress>

<!-- meter: a value in a known range; the bar colors itself -->
<label for="storage">Storage used</label>
<meter id="storage" min="0" max="100"
       low="30" high="70" optimum="90" value="55">55</meter>