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