Keystone gives you styles, icons, and behavior as three independent layers — each a single include that works from a CDN or a local file, on its own or together. Charting comes as an optional fourth include, so pages that don't chart pay nothing. The behavior layer carries what data apps keep rewriting: paged and selectable tables, async region states, a command palette, and a print sheet.
Small, honest, and dependency-free. Use exactly the parts you want.
Want our icons but not our styles? Our JS but your own CSS? Every pillar stands alone and degrades gracefully when the others are absent.
No frameworks, no runtime, no bundler. The behavior layer is vanilla JavaScript written from scratch. Drop the files in and go.
The same single tag works whether you link to a CDN or download the files. Icons embed their font, so there's no assets folder to chase.
Everything is built on --ks-* CSS custom properties. Retheme by overriding variables; dark mode ships in the box.
Focus management, ARIA wiring, keyboard support, and reduced-motion respect are built into the components — not bolted on.
Add your own components with a buildless plugin system: drop a module, list it in config.json, and it loads at runtime.
Each is one include. Use one, two, or all three.
Design tokens and component classes — buttons, forms, cards, modals, tabs, tables and more.
<link rel="stylesheet" href="keystone.css">
601 geometric icons in one self-contained file — the webfont is base64-embedded, so there's nothing else to download.
<link rel="stylesheet" href="keystone-icons.css">
From-scratch components: modals, drawers, dropdowns, tabs, accordions, tooltips, toasts, theme & form helpers.
<script type="module" src="keystone.js"></script>
Bar, line, area, donut and sparklines as inline SVG — no charting library.
Charts are built from a real <table>, so
without JS the data still renders, and with it the table stays reachable
behind “View data” as the chart's screen-reader representation. Series
colors come from the colorblind-safe --ks-chart-1…8
slots and follow the theme automatically.
<link rel="stylesheet" href="keystone-charts.css"> <script type="module" src="keystone-charts.js"></script>
| 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 |
Dashboards spend most of their code on the same few problems. These are in the pillars you already loaded — no extra include, no extra file.
Sorting, client-side paging, row selection, column show/hide and CSV / clipboard export — four behaviors over one controller, so they compose instead of fighting. Rows arriving later from an API are picked up automatically. Without JS it's one plain, complete table.
<table class="ks-table" id="tickets"
data-ks-table-sortable
data-ks-table-select
data-ks-table-page="5">
| Host | Tech | Open |
|---|---|---|
| store5-pfsense | Adam Wilson | 62 |
| nc-web-server | Chase Merwin | 45 |
| mow-sql | Taner Russo | 44 |
| ue-server-2024 | Andrew Paris | 34 |
| store-backup-01 | Adam Wilson | 28 |
| adam-pc | Chase Merwin | 21 |
| ops-store5 | Taner Russo | 17 |
The four-state dance every data screen does — loading, error + retry,
empty, data — as markup instead of app code, with the poll timer and the
“updated 2m ago” stamp handled. Keystone owns the states;
you own the fetch. A failed refresh
keeps your last good numbers on screen instead of blanking the board.
Keystone.resource('#problems', {
load: () => myApi.activeProblems(),
poll: 60000,
});
| Host | Problem | Since |
|---|
Register commands, get ⌘K and real keyboard shortcuts. It only binds once your page registers something, so pages that don't use it keep the shortcut.
<time data-ks-relative> reads
“” and keeps itself
current on one shared tick that stops when the tab is hidden. The date you
author stays the no-JS fallback.
A dashboard prints as a report: light palette, hairlines, no chrome, charts
with their data tables. And data-ks-copy on any
button copies a snippet — every Copy button on this page is one.
Link to it, or download it. Then write plain HTML.
Drop these in your <head>. Keep only the pillars you need.
<link rel="stylesheet" href="https://ui.aheliotech.app/keystone.css"> <link rel="stylesheet" href="https://ui.aheliotech.app/keystone-icons.css"> <script type="module" src="https://ui.aheliotech.app/keystone.js"></script> <!-- Optional: only on pages that chart --> <link rel="stylesheet" href="https://ui.aheliotech.app/keystone-charts.css"> <script type="module" src="https://ui.aheliotech.app/keystone-charts.js"></script>
Served from ui.aheliotech.app. Pin a version when releases are tagged.
<link rel="stylesheet" href="keystone.css"> <link rel="stylesheet" href="keystone-icons.css"> <script type="module" src="keystone.js"></script> <!-- Optional: only on pages that chart --> <link rel="stylesheet" href="keystone-charts.css"> <script type="module" src="keystone-charts.js"></script>
Download the files below and reference them from your project.
Components are plain classes and data-ks-* hooks. The behavior layer wires them up automatically.
<body class="ks-body ks-scope">
<button class="ks-btn ks-btn--primary" data-ks-modal-open="hello">
<span class="ks-i ks-i-controls-search"></span> Open
</button>
<div class="ks-modal" data-ks-modal id="hello">
<div class="ks-modal-dialog">
<div class="ks-modal-header">
<h3 class="ks-modal-title">Hello</h3>
<button class="ks-modal-close" data-ks-modal-close></button>
</div>
<div class="ks-modal-body">It works.</div>
</div>
</div>
</body>
These controls are live — rendered by the same Keystone files this page loads.
This dialog is rendered by keystone.js — focus is trapped, Esc closes it, and the backdrop dismisses on click.
It works.
Grab individual files, or take the whole keystone/ folder.
Optional charts module — add both files only on pages that chart.
No build, no bundler. Drop a module, list it, and it loads at runtime.
plugins/config.json
[
{ "path": "sample/main.js", "enabled": true },
{ "path": "my-plugin/main.js" }
]
plugins/my-plugin/main.js
export default function register(ks) {
ks.register('greeter', {
selector: '[data-greet]',
mount(el) {
ks.utils.on(el, 'click', () =>
ks.toast({ message: 'Hi!' }));
},
});
}
The core fetches config.json relative to keystone.js and imports each module in the order you list. A missing config is harmless.
No pillar requires another. Load whatever fits.
| Loaded | Result |
|---|---|
| Icons only | .ks-i-* glyphs render. |
| Style only | Components fully styled; interactive ones sit in their default state. |
| Behavior only | Behaviors work; injected marks are inline SVG; appearance is unstyled. |
| Any combination | Progressive enhancement, with no errors when a pillar is absent. |
A two-tone bracketed [KS] — technical and corporate. Download the assets below.