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.

-subtle -muted

Buttons

Form (with validation)

View
Plan

Form niceties

https://
/ mo

Rich-text editor

Combobox (searchable & multi-select)

Badges, chips & alerts

Neutral Primary Success Warning Error
Chip Selected tag Active Removable Clickable
Heads up
This is an informational alert.
Saved successfully.

Overlays & feedback

Popover

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

Overview content.
Details content.
Activity content.
A three-pillar front-end framework.
No — drop in the files and go.

Data & status

NameStatusUptime
Server AOnline99.98%
Server BDegraded97.40%
Server COffline0.00%
Fleet3 nodes65.79%
Progress
Loading…
KS

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.

Devices 6
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):

HostStatusUptime
No devices found Try clearing the filter, or add your first device to start monitoring.

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.

Tickets
Host Team Priority Open
web-01Service deskP162
db-02NOCP245
cache-03ProjectsP344
edge-04FieldP234
api-05Service deskP328
queue-06NOCP121
mail-07ProjectsP319
vpn-08FieldP217
file-09Service deskP314
print-10NOCP312
backup-11ProjectsP19
dns-12FieldP36
nas-13Service deskP24
wifi-14NOCP32

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.

Command palette

These two register declaratively — the attribute makes the control a command, and the palette runs it by clicking it.

Relative time
EventWhen
Backup finished
Alert raised
Ticket opened
Maintenance window

Styles: default, compact, short. Hover for the absolute timestamp.

Copy to clipboard
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.

Category axis — evenly spaced (the lie)
Timeweb-01db-02
08:004155
08:014457
08:024356
14:029261
14:038860
Time axis — the six-hour gap is visible
Timeweb-01db-02
08:004155
08:014457
08:024356
14:029261
14:038860

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.

Active problems
Couldn't reach the monitoring API
All clear No active problems on any monitored host.
HostProblemSeverity
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.

Monthly revenue $48,290 Up: 12.4%
Active sessions 1,204 Down: 3.1%
Avg. response 184 ms No change
Skeleton
No messages yet

When teammates reach out, their conversations will show up right here.

Activity
  1. 09:42
    Deployment v1.4.0 succeeded on all nodes.
  2. 09:31
    Node B reported degraded latency during rollout.
  3. 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.

Revenue by quarter
QuarterManaged ITCloudSecurity
Q148,29021,4009,800
Q251,12024,90012,300
Q346,70028,15014,050
Q454,38030,60015,900
Tickets by site
SiteHardwareNetworkSoftware
Columbus342146
Dublin181225
Westerville111714
Remote62831
Response time, p50 vs p95
Weekp50p95
W162212
W258198
W366184
W454190
W551171
W649163
Backups completed
Fleet by OS
OSDevices
Windows412
macOS96
Linux44
Other17
Keystone.charts.render(el, config) — click a bar for ks:chart:select

Inline sparklines ride along in text or table cells: CPU , memory , deploys — colored via currentColor, defaulting to slot 1.

Charts — live data, morph & annotations

An update doesn't redraw a chart, it moves it. Marks are keyed by meaning — series × category, tick value — so a bar that changed is the same bar resizing, a reading that scrolls off a rolling window slides out the left, and isolating a series (click a legend key) rescales the axis smoothly. Table-fed charts watch their table: re-render its rows from an async poll and the chart follows. Hit targets, tooltips and the table are right the instant the data lands; reduced motion simply swaps.

Open tickets by queue
QueueOpen
Service desk42
Projects18
Network27
Security11
Onboarding23
Edits the <table> — nothing calls the chart
Portal response time
Hourp95
07:00142
08:00168
09:00181
10:00236
11:00214
12:00171
13:00158
14:00139
KeystoneCharts.update(el, { labels, keys, series })
Fleet by OS
OSDevices
Windows 10188
Windows 11224
macOS96
Linux44

Infographics — gauges, grids, boards & flows

Eight forms in the charts module, each built from markup you'd write anyway — a <meter> or a <table>, which stays as the chart's screen-reader twin and its no-JS fallback. Color does one job per form: categorical slots for identity, the steel sequential ramp for magnitude, the ordinal ramp for ordered stages, and state tones only for state — always with a word and a glyph. Dense grids are one tab stop: focus one and walk it with the arrow keys.

CPU
38%
Memory
76%
Disk C:
93%
Sets each <meter value> — the dials follow
Tickets opened, by weekday and hour
Day7a8a9a10a11a12p1p2p3p4p5p6p
Mon6142219159121411963
Tue5111716138111210852
Wed4101615128101311752
Thu512181412791110842
Fri491312106786531
Sat012322111000
Sun001111
October patch compliance
StatusDevices
Fully patched446
Service health — last 60 days
DayMicrosoft 365Site-to-site VPNCloud backupHelp desk portal
Backup alerts per day — last six months
DateAlerts
Q3 service scorecard
MeasureActualTargetPoorFairMax
First response in SLA92%95%8090100
Resolved in SLA88%85%7080100
Customer satisfaction4.74.53.54.25
Patch compliance94%98%8595100
Quote pipeline, Q3
StageQuotes
Drafted240
Sent196
Viewed151
Approved78
Device age against support load — bubble size is users served
DeviceAge (years)Tickets (12 mo)UsersSite

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.

Toggle animations
Progress
Indeterminate (looping sweep)
Determinate (eased width) — 20%
Skeleton → content
Scroll reveal

This card rises and fades in as it enters the viewport — scroll-timeline where supported, IntersectionObserver fallback otherwise.

Motion — sequence, constancy & live values

Motion that shows change: a list arrives in order, a sorted row glides to its new place, a polled number ticks from what it showed to what it is now. The navbar's motion button (or Keystone.motion.set('reduce')) switches all of it off in-app — persisted, scoped like density, and never able to turn motion back on over an OS that asked for less.

Motion preference

Effective now: full motion. data-ks-motion="reduce" on any container silences just that subtree.

64%
This ring sits in a reduced subtree — it never animates.
Stagger & FLIP
  • Backup verified — Columbus
  • Patch window closed — Dublin
  • New device enrolled — Remote
  • Firewall policy synced
  • Ticket #4182 escalated
Live numbers
Open tickets 1,204
MRR $48,290
data-ks-value · Keystone.countTo()
Sorting that moves — click a column
DeviceSiteCPU %Last seen
COL-WS-014Columbus34Oct 20, 09:12
DUB-SRV-02Dublin81Oct 20, 09:40
WES-WS-007Westerville12Oct 19, 17:05
COL-SRV-01Columbus67Oct 20, 09:41
REM-LT-221Remote5Oct 18, 22:30
DUB-WS-031Dublin48Oct 20, 08:55
COL-NAS-01Columbus22Oct 20, 09:38
WES-SRV-01Westerville93Oct 20, 09:42

Draw

.ks-draw on an inline SVG draws its line work in when it is first seen; labels fade in behind. For diagrams and process illustrations — fully drawn without JS, under reduced motion, and in print.

Traffic path: endpoints pass through the firewall to cloud services; the firewall also backs up to the vault. Endpoints Firewall Cloud apps Backup vault

Infographics — meter, ring, steps & proportion

CSS-only forms with a non-visual twin built in: <meter> is announced natively, rings carry role="progressbar", steps are an ordered list, a proportion bar's legend is its text. Tone signals state (always beside a word), and every one of them renders with only keystone.css loaded.

Meter
Disk C:42%
42%
MemoryWarning 78%
78%
Disk D:Critical 94%
94%
60% --sm 60% --lg
Ring
87%Patched
99%Backups
72%SLA met
40%
58%Onboarding
--ks-value transitions with zero JS
Proportion bar
Patch status — 459 devices
  • Installed 412
  • Pending 38
  • Failed 9
Tickets by channel
  • Email 214
  • Phone 132
  • Portal 88
  • Chat 41
Steps
  1. DiscoverySite survey complete
  2. AssessmentRisk report delivered
  3. MigrationM365 tenant cutover
  4. TrainingStaff sessions
  5. Go-liveManaged support begins
  1. Quote draftedOct 14 · Account manager
  2. Internal reviewOct 15 · Team lead
  3. Client approval — changes requestedOct 17 · Pricing on line 4
  4. Signed

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

Compact 0.75x
HostStateLoad
node-01Up0.42
node-02Warn0.88
  • Item one A
  • Item two B
Default 1x
HostStateLoad
node-01Up0.42
node-02Warn0.88
  • Item one A
  • Item two B
Comfortable 1.25x
HostStateLoad
node-01Up0.42
node-02Warn0.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.

Comfortable page 1.25x

This table is "default" inside the comfortable card:

HostStateLoad
node-01Up0.42
node-02Warn0.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.

Theme preference — stateful radio group

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

Modal title

This modal is opened by the behavior layer. Press Escape, click the backdrop, or use the close button.

Drawer

Off-canvas panel. Same dismiss model as the modal.