Keystone demo
Style + icons + behavior, each loaded from a single tag. This page uses all three; comment any one out in the source to see the others still work.
Accent theme
Swap the accent color with data-ks-accent on
<html> (or any container). The primary steel-blue
is unchanged; -subtle / -muted
tints derive via color-mix so every preset works in light and dark.
Buttons
Form (with validation)
Form niceties
Rich-text editor
Combobox (searchable & multi-select)
Badges, chips & alerts
Overlays & feedback
Click outside or press Esc to close.
Right-click the Keystone site link (or any selected text) to open the custom context menu.
Promise-based dialogs
await Keystone.confirm() / .alert() / .prompt() — built on the modal, resolved on choice.
Result: (none yet)
Tabs & accordion
Data & status
| Name | Status | Uptime |
|---|---|---|
| Server A | Online | 99.98% |
| Server B | Degraded | 97.40% |
| Server C | Offline | 0.00% |
| Fleet | 3 nodes | 65.79% |
Table utilities
Sortable columns, a frozen first column, a toolbar, and a responsive
card-stack — all composable on the same .ks-table.
Click a header (or focus it and press Enter/Space) to cycle
ascending → descending → original. Type is auto-detected
per column. Narrow the window below 640px to see rows stack into labeled
cards. Without JS it stays a plain static table.
| Host | Status | Uptime | Last seen | Actions |
|---|---|---|---|---|
| web-01 | Online | 99.98% | Jun 30, 2026 |
|
| db-02 | Degraded | 97.40% | Jun 28, 2026 |
|
| cache-03 | Offline | 0.00% | May 11, 2026 |
|
| edge-04 | Online | 99.10% | Jun 30, 2026 |
|
| api-05 | Online | 98.72% | Jun 29, 2026 |
|
| queue-06 | Degraded | 95.30% | Jun 27, 2026 |
|
Empty state (as a full-width <td> row):
| Host | Status | Uptime |
|---|---|---|
|
No devices found
|
||
Table pack
Four behaviors over one shared controller, so they compose: the pipeline is always filter → sort → page. Type in the filter and the pager recounts; sort a column and you land back on page 1; select-all ticks the whole filtered set, not just this page; export writes what you are looking at, in the order you are looking at it, minus any column you hid. Rows added later by an API call are picked up automatically. Without JS it is one plain, complete table.
| Host | Tech | Priority | Open |
|---|---|---|---|
| web-01 | Adam Wilson | P1 | 62 |
| db-02 | Chase Merwin | P2 | 45 |
| cache-03 | Taner Russo | P3 | 44 |
| edge-04 | Andrew Paris | P2 | 34 |
| api-05 | Adam Wilson | P3 | 28 |
| queue-06 | Chase Merwin | P1 | 21 |
| mail-07 | Taner Russo | P3 | 19 |
| vpn-08 | Andrew Paris | P2 | 17 |
| file-09 | Adam Wilson | P3 | 14 |
| print-10 | Chase Merwin | P3 | 12 |
| backup-11 | Taner Russo | P1 | 9 |
| dns-12 | Andrew Paris | P3 | 6 |
| nas-13 | Adam Wilson | P2 | 4 |
| wifi-14 | Chase Merwin | P3 | 2 |
Commands, time & clipboard
Press ⌘K (or Ctrl+K)
for the palette — it only binds once a page registers a command, so pages that
don't use it keep their shortcut. Timestamps rewrite themselves and keep
current on one shared tick. Copy buttons read a literal, a selector, or the
nearest <pre>. And this whole page has a print
stylesheet — try ⌘P.
These two register declaratively — the attribute makes the control a command, and the palette runs it by clicking it.
| Event | When |
|---|---|
| Backup finished | |
| Alert raised | |
| Ticket opened | |
| Maintenance window |
Styles: default, compact,
short. Hover for the absolute timestamp.
Keystone.commands.open()
This line is .ks-no-print — it won't appear on paper.
Printed copy — hidden on screen (.ks-print-only).
Charts — real axes
data-ks-x="time" reads each row label as a date and
plots it at its true position. The two charts below hold
the same readings: on the left a category axis spaces them
evenly, on the right a time axis shows what actually happened — three samples
a minute apart, then a six-hour hole, then two more. Also here:
data-ks-ref threshold lines, click-a-key-to-isolate,
and PNG / CSV export.
| Time | web-01 | db-02 |
|---|---|---|
| 08:00 | 41 | 55 |
| 08:01 | 44 | 57 |
| 08:02 | 43 | 56 |
| 14:02 | 92 | 61 |
| 14:03 | 88 | 60 |
| Time | web-01 | db-02 |
|---|---|---|
| 08:00 | 41 | 55 |
| 08:01 | 44 | 57 |
| 08:02 | 43 | 56 |
| 14:02 | 92 | 61 |
| 14:03 | 88 | 60 |
Click a legend key to isolate that series — the value axis rescales to what's left, and clicking it again brings everyone back. CSV always exports the full dataset, including a series you've isolated out of view.
Async regions
The four-state dance every data screen does — loading → error+retry → empty →
data — as markup instead of app code. Presentation only:
Keystone owns which slot shows, the retry button, the poll timer and the stale
clock; you own the fetch. Force each state below.
Note that a failed reload keeps the last good numbers on screen and
just marks the region stale — blanking a working board because one refresh
timed out is worse than slightly old data.
| Host | Problem | Severity |
|---|---|---|
| Server-rendered rows — what a no-JS visitor sees. | ||
App primitives
Building blocks for data-heavy app screens — all style-only, working in light and dark.
-
09:42
Deployment v1.4.0 succeeded on all nodes.
-
09:31
Node B reported degraded latency during rollout.
-
09:20
Release pipeline started by gkuhl.
Charts
Bar, line/area, donut and sparklines, rendered from real
<table> markup (or inline JSON / the JS API).
Without the module the tables below simply render; with it, each table stays
reachable behind “View data” — it is the chart's screen-reader representation.
Series colors are the validated --ks-chart-1…8
slots (colorblind-safe in light and dark, assigned in fixed order — never
rank, never a state color). Hover or focus any mark for its values.
| Quarter | Managed IT | Cloud | Security |
|---|---|---|---|
| Q1 | 48,290 | 21,400 | 9,800 |
| Q2 | 51,120 | 24,900 | 12,300 |
| Q3 | 46,700 | 28,150 | 14,050 |
| Q4 | 54,380 | 30,600 | 15,900 |
| Site | Hardware | Network | Software |
|---|---|---|---|
| Columbus | 34 | 21 | 46 |
| Dublin | 18 | 12 | 25 |
| Westerville | 11 | 17 | 14 |
| Remote | 6 | 28 | 31 |
| Week | p50 | p95 |
|---|---|---|
| W1 | 62 | 212 |
| W2 | 58 | 198 |
| W3 | 66 | 184 |
| W4 | 54 | 190 |
| W5 | 51 | 171 |
| W6 | 49 | 163 |
| OS | Devices |
|---|---|
| Windows | 412 |
| macOS | 96 |
| Linux | 44 |
| Other | 17 |
Inline sparklines ride along in text or table cells:
CPU ,
memory ,
deploys —
colored via currentColor, defaulting to slot 1.
Motion — controls, progress & reveal
State changes animate: the check mark springs in, the radio core scales up,
the switch knob slides while its track eases off→on. Progress eases width
changes and offers a looping --indeterminate
sweep. All of this respects prefers-reduced-motion.
This card rises and fades in as it enters the viewport — scroll-timeline where supported, IntersectionObserver fallback otherwise.
Density modes
Scale control heights, padding and font for data-dense screens without re-theming.
Add data-ks-density="compact",
"default" or "comfortable" to
<html> or any container.
Compact clamps interactive controls to a 32px minimum tap target.
The middle column below writes "default" explicitly — it is
pixel-identical to omitting the attribute, and it is the only way to reset a
subtree to 1x inside a comfortable ancestor (see below).
| Host | State | Load |
|---|---|---|
| node-01 | Up | 0.42 |
| node-02 | Warn | 0.88 |
- Item one A
- Item two B
| Host | State | Load |
|---|---|---|
| node-01 | Up | 0.42 |
| node-02 | Warn | 0.88 |
- Item one A
- Item two B
| Host | State | Load |
|---|---|---|
| node-01 | Up | 0.42 |
| node-02 | Warn | 0.88 |
- Item one A
- Item two B
Resetting inside a dense page. Density nests, so a container inside a
comfortable ancestor inherits 1.25x — and until now there was no way back. Writing
data-ks-density="default" returns a subtree to 1x, which is
what a data table on an otherwise roomy page wants.
This table is "default" inside the comfortable card:
| Host | State | Load |
|---|---|---|
| node-01 | Up | 0.42 |
| node-02 | Warn | 0.88 |
Sound & haptics
Optional official plugins, shipped off in plugins/config.json. Enable them below to feel/hear them — then click any button on the page. Haptics need a device with a vibration motor.
Stateful fields
Add the stateful attribute and a field remembers its value
across reloads — persisted to the browser's private file system (a port of
pio). Type below, then refresh.
Requires the File System Access API (Chromium/WebKit). Other browsers no-op silently. Radios persist by group name.
Icons
Rendered from the embedded webfont via .ks-i-<category>-<name>.