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:

data-theme

Page scheme

In progress

Forced dark subtree (data-theme="dark")

In progress

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:

--lz-contrast
A link In progress Done 99+

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:

--lz-accent-hue / --lz-accent-chroma
A link In progress Done 99+

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:

--lz-glass
Changes saved
Flags, menus, modals and the navbar are frosted-glass surfaces; the glass alpha is a function of the contrast dial.

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 same sys.json is 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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

TokenSwatchLight refDark 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