Ledger is the shared CSS foundation for jflamb.com, Assistant Workbench, and the Retirement Dashboard. It gives the three products one visual identity: ink on warm paper, a viridian brand, serif prose, mono figures, and rules instead of decorative shadows.
The package is intentionally small. It provides versioned design tokens, themes, accessibility defaults, and a stable contract for site-local components. It does not force the three products into one component library.
npm install @jflamb/ledgerImport the stylesheet from application CSS:
@import "@jflamb/ledger/ledger.css";Or import it from JavaScript when the bundler handles CSS:
import "@jflamb/ledger/ledger.css";For a site without npm tooling, use a version-pinned CDN URL or vendor the file into the site repository:
<link rel="stylesheet" href="https://unpkg.com/@jflamb/ledger@0.1.0/ledger.css">Pin the version in production. Do not load an unversioned latest URL.
Site components should use semantic roles rather than palette values:
.panel {
color: var(--color-text-primary);
background: var(--color-surface-panel);
border: 1px solid var(--color-border);
border-radius: var(--radius-static);
padding: var(--space-4);
}
.primary-action {
min-block-size: var(--control-min-block-size);
color: var(--color-text-on-action);
background: var(--color-action);
border-radius: var(--radius-control);
}
.primary-action:focus-visible {
outline: var(--focus-ring);
outline-offset: var(--focus-ring-offset);
}Palette primitives such as --viridian and --paper are implementation details. Keeping components on semantic tokens lets Ledger improve contrast or rebalance themes without requiring component rewrites.
- Components consume only layer-2 semantic tokens (
--color-*,--font-*,--text-*,--space-*,--radius-*,--control-*,--focus-*,--shadow-*). Layer-1 primitives (--paper,--ink,--viridian, …) are private toledger.cssand are the only values that change per theme. - Color never carries meaning alone. Every status has a text label; selection changes border weight and adds a check, not just a fill.
- Square is static, round is interactive.
--radius-staticfor panels and tables;--radius-control/--radius-selectablemark what can be operated. - Shadows are for overlays only (
--shadow-overlay): menus, tooltips, dialogs. Static grouping uses rules and the one-step paper raise. - Type roles:
--font-prose(serif) for display and explanatory text,--font-ui(system sans) for controls/navigation/dense labels,--font-monofor figures, dates, codes, and short statuses (withfont-variant-numeric: tabular-nums). - Site-local tokens (e.g. the dashboard's
--chart-*) may alias down to Ledger tokens; they may not redefine shared names.
--color-*: text, surfaces, borders, actions, focus, and statuses--font-*: prose, interface, and numeric roles--text-*: type scale--space-*: 4px spacing scale--radius-*: static, tag, control, selectable, and pill shapes--control-*: interaction sizing--focus-*: keyboard focus treatment--shadow-*: overlays only
The full token list and values live in ledger.css.
ledger.css defines light (default), dark (prefers-color-scheme and :root[data-theme="dark"], so a manual toggle beats the system), high contrast (prefers-contrast: more), and print. The theme preference is shared across subdomains via a first-party cookie — localStorage is origin-scoped and serves only as a same-origin fallback:
// set on toggle — readable by every *.jflamb.com origin
document.cookie =
"jflamb-theme=dark; Domain=.jflamb.com; Path=/; Max-Age=31536000; SameSite=Lax; Secure";
// no-flash bootstrap (inline in <head>): cookie → localStorage → system
const t = document.cookie.match(/(?:^|; )jflamb-theme=(dark|light)/)?.[1]
?? localStorage.getItem("jflamb-theme");
if (t) document.documentElement.dataset.theme = t;Use data-theme="light" or data-theme="dark" for a manual override. Without an override, Ledger follows prefers-color-scheme. The stylesheet also includes prefers-contrast: more and print values.
npm test runs the contrast gate: it parses ledger.css itself and verifies every text-role/surface pair at ≥ 4.5:1 and every interactive-border/focus pair at ≥ 3:1, across light, dark, and high-contrast, and confirms the two dark blocks never diverge. A palette change that regresses accessibility fails the build.
Run the local checks before committing:
npm test
npm run check:packagenpm run sync:site -- /path/to/jflamb.com vendors the canonical stylesheet into a zero-tooling site. npm run check:drift -- /path/to/jflamb.com verifies that copy byte-for-byte.
Semver. Renaming or removing a primitive or semantic token is major; adding or re-pointing a semantic alias is minor; value tweaks that pass the gate are patch.
- Update
versioninpackage.jsonand add a dated entry toCHANGELOG.md. - Merge the change to
main. - Create a GitHub release whose tag exactly matches
v<package version>. - The
publishworkflow validates the tag, reruns the accessibility and package checks, and uses npm trusted publishing to stage the package with provenance. - Review the package under Staged Packages on npmjs.com and approve it with 2FA.
The trusted publisher is restricted to publish.yml in jflamb/ledger and may only run npm stage publish. The repository stores no npm publishing token. Ordinary pushes and pull requests cannot stage or publish packages, and a staged release does not become public until a maintainer approves it with 2FA.