Lozenge
A Jira-flavoured design system with runtime theme axes — original CSS
built on the published Atlassian palette anchors, with Bootstrap-style class ergonomics.
Components are plain HTML + CSS with zero runtime JavaScript; behavior comes from the
platform (native <dialog>, popover, <details>, radios + :has()).
Every component publishes an agent-readable markup contract in specs/, and these
pages are generated from those contracts.
The four theme axes
Everything routes through four numeric custom properties plus
data-theme — no rebuild, animatable, drivable from a slider or your own logic.
This page loads the theme panel (bottom right): every example below re-resolves live as
you drag the dials.
Scheme
Light on :root, dark under [data-theme="dark"] (with a
prefers-color-scheme fallback). The attribute also works on a subtree:
Page scheme
In progressForced dark subtree (data-theme="dark")
Set it from JS
document.documentElement.dataset.theme = "dark";
Contrast
--lz-contrast is a continuous dial from −1 to +1, resolved through OKLCH
relative color at runtime. OS prefers-contrast maps onto it — including
reduced contrast for photophobic users — and every declared text/surface pair is
CI-verified against WCAG 2.2 ratios at every dial position. Drag the contrast slider in
the theme panel and watch this sample:
Set it from JS
document.documentElement.style.setProperty("--lz-contrast", 0.5);
Accent
--lz-accent-hue / --lz-accent-chroma rotate the entire accent
ramp live; Jira blue is just the dial's resting position. Buttons, links, selection,
focus rings and info status all follow:
Set it from JS
document.documentElement.style.setProperty("--lz-accent-hue", 152); // green
Glass
--lz-glass fades overlay materials between frosted glass (1) and solid (0).
Glass also fades to solid as contrast rises, and is disabled under
prefers-reduced-transparency and forced-colors — solid is the canonical state:
Set it from JS
document.documentElement.style.setProperty("--lz-glass", 0); // solid materials
Token tiers
Three tiers, generated from tokens/ramps.json + tokens/sys.json
by scripts/build-tokens.mjs:
- Reference —
--lz-ref-<ramp>-<step>: OKLCH ramps fitted to the Atlassian hex anchors, plus a parametric accent ramp whose hue/chroma come from the dials. - System —
--lz-sys-*: semantic roles expressed as relative-color functions of the reference tier and the contrast dial. The samesys.jsonis evaluated numerically by the contrast checker, so the CSS and the CI proof cannot drift. - Component — Sass-level tokens consumed by the ~20 components, overridable Bootstrap-style at compile time.
System tokens
Every semantic token from tokens/sys.json. Swatches are
live — they follow the scheme, contrast and accent dials on this page. k is the
lightness shift per unit of contrast, ck the chroma multiplier, α a static
opacity.
Text
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-text |
neutral.800 (k -0.1) |
darkNeutral.1100 (k 0.08) |
|
--lz-sys-text-subtle |
neutral.300 (k -0.1) |
darkNeutral.800 (k 0.1) |
|
--lz-sys-text-subtlest |
neutral.100 (k -0.1) |
darkNeutral.700 (k 0.12) |
|
--lz-sys-text-disabled |
neutral.70 |
darkNeutral.500 |
|
--lz-sys-text-inverse |
neutral.0 |
darkNeutral.deep |
|
--lz-sys-text-on-bold |
neutral.0 |
neutral.0 |
Links
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-link |
accent.400 (k -0.08, ck -0.25) |
accent.100 (k 0.08, ck -0.25) |
|
--lz-sys-link-hover |
accent.300 (k -0.08, ck -0.25) |
accent.75 (k 0.05, ck -0.25) |
|
--lz-sys-link-active |
accent.500 (k -0.06, ck -0.2) |
accent.200 (k 0.06, ck -0.2) |
Surfaces
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-surface |
neutral.0 (k 0) |
darkNeutral.100 (k -0.02) |
|
--lz-sys-surface-sunken |
neutral.20 (k 0.02) |
darkNeutral.deep (k -0.01) |
|
--lz-sys-surface-raised |
neutral.0 (k 0) |
darkNeutral.200 (k -0.01) |
|
--lz-sys-surface-overlay |
neutral.0 (k 0) |
darkNeutral.250 (k -0.01) |
|
--lz-sys-blanket |
neutral.900 (α 0.54) |
darkNeutral.deep (α 0.62) |
|
--lz-sys-surface-raised-hovered |
neutral.10 |
darkNeutral.300 |
Borders & separators
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-border |
neutral.40 (k -0.3) |
darkNeutral.350 (k 0.3) |
|
--lz-sys-separator |
neutral.30 (k -0.2) |
darkNeutral.300 (k 0.2) |
Focus & selection
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-focus-ring |
accent.100 (k -0.06, ck -0.2) |
accent.100 (k 0.05, ck -0.2) |
|
--lz-sys-selected-bg |
accent.50 |
accent.400 (α 0.18) |
|
--lz-sys-selected-text |
accent.400 (k -0.08, ck -0.25) |
accent.100 (k 0.08, ck -0.25) |
Interaction states
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-interaction-bg |
neutral.900 (α 0.04) |
darkNeutral.1100 (α 0.07) |
|
--lz-sys-interaction-hovered |
neutral.900 (α 0.09) |
darkNeutral.1100 (α 0.12) |
|
--lz-sys-interaction-pressed |
accent.75 (α 0.6) |
accent.400 (α 0.35) |
Bold accents
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-accent-bold |
accent.400 (k -0.05, ck -0.2) |
accent.400 (k -0.04, ck -0.2) |
|
--lz-sys-accent-bold-hovered |
accent.300 (k -0.05, ck -0.2) |
accent.300 (k -0.04, ck -0.2) |
|
--lz-sys-accent-bold-pressed |
accent.500 (k -0.04, ck -0.2) |
accent.500 (k -0.03, ck -0.2) |
|
--lz-sys-danger-bold |
red.400 (k -0.05, ck -0.15) |
red.400 (k -0.05, ck -0.15) |
|
--lz-sys-danger-bold-hovered |
red.300 (k -0.05, ck -0.15) |
red.300 (k -0.05, ck -0.15) |
|
--lz-sys-danger-bold-pressed |
red.500 (k -0.04, ck -0.15) |
red.500 (k -0.03, ck -0.15) |
|
--lz-sys-warning-bold |
yellow.300 (k 0.02, ck -0.12) |
yellow.300 (k 0.02, ck -0.12) |
|
--lz-sys-warning-bold-hovered |
yellow.400 (k 0.02, ck -0.12) |
yellow.400 (k 0.02, ck -0.12) |
|
--lz-sys-warning-bold-pressed |
yellow.500 (k 0.02, ck -0.12) |
yellow.500 (k 0.02, ck -0.12) |
Controls
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-input-bg |
neutral.10 |
darkNeutral.0 |
|
--lz-sys-input-hover-bg |
neutral.30 |
darkNeutral.250 |
|
--lz-sys-toggle-off |
neutral.200 |
darkNeutral.400 |
|
--lz-sys-track |
neutral.40 (k -0.08) |
darkNeutral.350 (k 0.08) |
|
--lz-sys-tooltip-bg |
neutral.800 |
darkNeutral.1000 |
|
--lz-sys-tooltip-text |
neutral.0 |
darkNeutral.deep |
Status
| Token | Swatch | Light ref | Dark ref |
|---|---|---|---|
--lz-sys-status-neutral-subtle-bg |
neutral.30 |
darkNeutral.350 |
|
--lz-sys-status-neutral-subtle-text |
neutral.500 (k -0.1) |
darkNeutral.1000 (k 0.06) |
|
--lz-sys-status-neutral-bold-bg |
neutral.500 (k -0.06) |
darkNeutral.700 (k 0.06) |
|
--lz-sys-status-neutral-on-bold |
neutral.0 |
darkNeutral.deep |
|
--lz-sys-status-info-subtle-bg |
accent.50 |
accent.500 (α 0.32) |
|
--lz-sys-status-info-subtle-text |
accent.500 (k -0.08, ck -0.2) |
accent.100 (k 0.07, ck -0.2) |
|
--lz-sys-status-info-bold-bg |
accent.400 (k -0.05, ck -0.2) |
accent.400 (k -0.04, ck -0.2) |
|
--lz-sys-status-info-on-bold |
neutral.0 |
neutral.0 |
|
--lz-sys-status-warning-subtle-bg |
yellow.75 |
yellow.500 (α 0.28) |
|
--lz-sys-status-warning-subtle-text |
neutral.800 (k -0.08) |
yellow.100 (k 0.05, ck -0.2) |
|
--lz-sys-status-warning-bold-bg |
yellow.500 (k 0.03, ck -0.12) |
yellow.400 (k 0.03, ck -0.12) |
|
--lz-sys-status-warning-on-bold |
neutral.800 (k -0.08) |
darkNeutral.deep |
|
--lz-sys-status-danger-subtle-bg |
red.50 |
red.500 (α 0.3) |
|
--lz-sys-status-danger-subtle-text |
red.500 (k -0.08, ck -0.2) |
red.100 (k 0.06, ck -0.2) |
|
--lz-sys-status-danger-bold-bg |
red.400 (k -0.05, ck -0.15) |
red.400 (k -0.05, ck -0.15) |
|
--lz-sys-status-danger-on-bold |
neutral.0 |
neutral.0 |
|
--lz-sys-status-success-subtle-bg |
green.50 |
green.500 (α 0.3) |
|
--lz-sys-status-success-subtle-text |
green.500 (k -0.08, ck -0.2) |
green.200 (k 0.06, ck -0.2) |
|
--lz-sys-status-success-bold-bg |
green.400 (k -0.05, ck -0.15) |
green.400 (k -0.05, ck -0.15) |
|
--lz-sys-status-success-on-bold |
neutral.0 |
neutral.0 |
|
--lz-sys-status-discovery-subtle-bg |
purple.50 |
purple.500 (α 0.34) |
|
--lz-sys-status-discovery-subtle-text |
purple.500 (k -0.08, ck -0.2) |
purple.100 (k 0.07, ck -0.2) |
|
--lz-sys-status-discovery-bold-bg |
purple.400 (k -0.05, ck -0.15) |
purple.300 (k -0.04, ck -0.15) |
|
--lz-sys-status-discovery-on-bold |
neutral.0 |
neutral.0 |