Theming
You set ten source tokens; Keyline derives every other colour from them, in both themes. Components pick colours by role, not by value, and so can yours: every role is a Tailwind utility.
Loading the live example…
Source tokens
These are the only colour, type and cut values you configure. Keyline's defaults sit in @layer base, so any unlayered declaration of yours wins, wherever it is in the file.
Role weights (3, 2 and 1px), square corners, the signal jobs and the motion behaviours are not tokens: they are what Keyline is. Motion timings are tokens too, covered in Modes.
Derived colours
Fills, foregrounds, text variants, focus, selection, data colours and both themes are computed from the source tokens. They are recomputed only on :root, .light, .dark and elements with data-kl-config. Set a source token on one of those. On a plain element, a control there keeps the old derived colour while a popup opened from it takes the new one.
To change the palette of one region, use a configuration scope: data-kl-config by hand, or KeylineConfigScope (see Configuration).
Brand colours
Commit and attention fills get white or dark text by lightness; the crossover is OKLCH L 0.575. A fill picked by hand between L 0.5 and 0.65 can fall below 4.5:1 contrast with either. Keep those two fills outside that band, or let roleColor pick the lightness for a hue, as the preset palettes do (see Configuration).
Keep the three signal hues apart. Commit and attention closer than 40° make a primary action read as a warning; the working set within 30° of either merges two signals.
Colour roles
When you build your own component, pick the role, not a colour. A divider between cells of one frame is seam; a cursor or checked fill is inverse with on-inverse text.
Tailwind utilities
Every role is a Tailwind colour of the same name prefixed kl-, so it works with any colour utility (bg-, text-, border-, fill-, outline-) and your editor completes it. The utilities follow .dark and configuration scopes like the tokens do, and leave shadcn's bg-primary or border-border alone. Arbitrary values work too: text-(--kl-ink-2).
Every other kl- class is internal and can change between versions. If a composition seems to need one, that is a gap to report (see Composition).
Fonts
Keyline has two voices, UI and Code, set by --kl-font and --kl-font-mono. Atkinson Hyperlegible is the default; IBM Plex and the system fonts are tested alternatives.
IBM Plex
Install the packages, then import them and point the tokens at them:
System fonts
configToCss writes these lines for a configuration (see Configuration). The Atkinson packages stay imported after a switch; remove their @import lines yourself if you no longer want them.
Beside shadcn
Keyline's CSS writes only its own names: --kl-* tokens, kl- classes and data-kl-* attributes. It sets no body styles, no shadcn tokens, no --radius, no --font-sans and no global focus ring, so stock shadcn components on the same page look as they did. Keyline's files go to components/keyline/, so its Button and shadcn's Button can both be imported.
Each outermost Keyline element sets Keyline's font and ink itself, so a component keeps its voice inside an app that uses another font.
The whole app in Keyline
To give stock shadcn components Keyline's look too, add the opt-in theme:
It maps shadcn's semantic colour tokens, sidebar tokens included, to Keyline roles in :root, .light, .dark and configuration scopes. It also sets --radius: 0px, points --font-sans and --font-mono at Keyline's fonts, styles body, adds a :focus-visible ring and sets color-scheme per theme. --popover becomes raised, --border seam, --input line and --ring focus. --chart-* is left to you: Keyline's data colours are roles, not a series.
Because it restyles body and shadcn's tokens, add it only when the whole app should be Keyline. Removing it means deleting those rules from your stylesheet; the CLI does not remove CSS.