@amplib/ui

npm ↗ GitHub ↗

Three stylesheets for dense tool interfaces. Stark black and white, monospaced, zero radius. Emphasis expressed by polarity, hairline weight and letter-spacing. No build step, no dependencies, no JavaScript.

npm i @amplib/ui

Stylesheets

Three languages, one page each — load exactly one. The tool file and the reference file define the same class names at different scales, so a page holding both gets whichever loaded last.

ui.css

The tool language. 10/12/24px, compact density available. What a working surface is built from — panels, fields, tabs, lists, meters. demo.html is its kitchen sink; it opens directly off disk, no server.

docs.css

The reference language — the same system spoken at reading distance. 12/14/20/32px, a fourth rung for section heads, code at body size, no compact mode. This page is set in it.

gallery.css

The exhibit language, and the opposite pole from the other two: serif, letter-spaced, black. Works hang dimmed and brighten under attention. gallery-demo.html shows it; stegassette: live is it in use.

Importing

The specifier depends on what is resolving it. Parcel reads a bare specifier in CSS as a relative path and does not consult exports, so it needs its own scheme and the filename. TypeScript resolves a CSS import through the bundler's ambient *.css declaration, which only matches a specifier ending in .css.

by consumer
where specifier
CSS, via Vite or webpack @import "@amplib/ui";
CSS, via Parcel @import "npm:@amplib/ui/ui.css";
TypeScript or JavaScript import "@amplib/ui/ui.css";
no build step cp node_modules/@amplib/ui/ui.css .

Modes

Two orthogonal modes, both plain attributes, both settable on <html>, <body> or any subtree. color-scheme is set for you, so native scrollbars, pickers and the caret follow along.

attribute effect
dark: paper #000, ink #fff
data-theme="light" light: paper #fff, ink #000
data-theme="auto" follows the system preference, no JavaScript
data-density="compact" 11px tool density, tighter boxes (ui.css only)
.ht-surface-invert crosses the ink/paper pair for a subtree

Tokens

Only two literals exist — --ht-black and --ht-white. Everything else is a color-mix of the current pair, which is why flipping polarity recomputes the whole palette.

color
--ht-ink / --ht-paper current polarity
--ht-ink-dim / -faint secondary and tertiary text; both clear 5:1
--ht-ink-ghost lines and texture only — never legible as type
--ht-line / -strong hairline, and its hover weight
--ht-raised / --ht-sunken button fill, input well
type
--ht-text-small secondary — 10px tool, 12px reference
--ht-text-ui everything else — 12px tool (11 compact), 14px reference
--ht-text-title section names — reference only, 20px
--ht-text-display the one page title — 24px tool, 32px reference
--ht-text-control-floor 0, or 16px under (pointer: coarse) — the floor text-entry controls take, so iOS Safari does not zoom on focus
space and geometry
--ht-space-1 … -9 2 · 4 · 6 · 8 · 12 · 16 · 24 · 32 · 48px
--ht-gap / --ht-flow inline and block rhythm — layout hypertokens read these, so one declaration retunes a region
--ht-hairline 1px, the only border width
--ht-radius 0, the only radius — do not raise it
--ht-control-size 28px, or 22px compact

Layers

A later layer wins outright, whatever the specificity — which is why base sits before hypertokens. Anything you write outside a layer beats all of it, so app CSS never needs !important. To override inside the system's order, declare your own layer after them.

@layer amplib.reset, amplib.tokens, amplib.base, amplib.hypertokens,
  amplib.components, amplib.utilities;
what each layer holds
tokens values and modes
base native element defaults — plain HTML already looks right
hypertokens coordinated .ht-* fragments — type, arrangement, shape, surface, media, participation, time
components recipes that earn a name — panel, field, toolbar, tabs, list, badge, status, meter, figure
utilities single-purpose escapes