@cueplusplus/theme-base
v1.1.0
Published
The blank abstract theme every CUE++ theme extends: the neutral base palette, the token contracts, the manifest schema, and the resolver that turns a theme's deltas into a complete token map.
Readme
@cueplusplus/theme-base
The blank abstract theme every CUE++ theme extends, and the contract that makes "a palette is a package" work — addressed to the two people who need it by name: whoever writes a theme package by hand, and whoever writes a tool that reads one.
Most apps never reach for it by name. @cueplusplus/ui re-exports the types,
@cueplusplus/ui/styles.css already imports base.css, and every @cueplusplus/theme-* preset
declares this package as a peer — so it is usually in the tree before you think about it.
Install
The @cueplusplus scope is public on npm and resolves there by default, so there is nothing
to configure and no credential to supply — it installs like any other package.
pnpm add @cueplusplus/theme-base @cueplusplus/tokens@cueplusplus/tokens is the one peer this package declares (>=0.9.0 <1): the density and type
axes this package does not own. It is a peer rather than a dependency because a tree with two copies
of the token layer in it has two token vocabularies, and only one of them is the one the stylesheets
on the page were built against. culori comes along as a dependency; nothing else does.
Quick start
Four things live here, and every one of them is a fact more than one package has to agree on.
The blank base
base.css is every --cue-* colour at a neutral default, declared on a bare :root, with
@cueplusplus/tokens/axes.css imported for the density and type axes. It is what
@cueplusplus/ui/styles.css brings in, so a page that registers no theme still paints something
legible: greys, one desaturated accent, and the platform monospace.
Import it yourself only when you are assembling the stylesheet without @cueplusplus/ui/styles.css
— a theme configurator, a token playground, an email template that wants the vocabulary and not the
components:
The second line below is a palette, and this package ships none: install one preset beside the
base (pnpm add @cueplusplus/theme-cue, or any other @cueplusplus/theme-*) before that import
resolves.
@import "@cueplusplus/theme-base/base.css";
@import "@cueplusplus/theme-cue/theme.css"; /* any installed palette, and it must come after */The order is not decorative. A theme declares its colours under [data-theme="cue"], which ties
with this bare :root at the same specificity, so the one declared later wins.
The contract a theme package satisfies
A theme package is a manifest plus the stylesheet that paints it. The manifest has eleven required
fields — schemaVersion, name, package, extends, mode, supportsLight, colors, fonts,
densities, fontPairings, contrast — and assertManifest is what says so at runtime;
manifest.schema.json is the same statement for a validator that is not JavaScript. mode is
complete when theme.css stands on its own and delta when it holds only what differs from the
base. extends carries the theme-base range the manifest was resolved against, and it is the
package's own peerDependencies entry rather than a second copy of it.
The typed half is ThemeRegistry, which a theme package augments with declare module in its
index.d.ts. ThemeName is the keys of that interface, so it widens with each theme a consumer
installs rather than naming a list compiled in here.
Reading a theme
resolve() turns a theme's deltas into a complete token map, and is what a tool that has to know a
theme's effective colours calls instead of parsing CSS:
import { assertManifest, resolve } from "@cueplusplus/theme-base";
export function darkColors(manifest: unknown) {
assertManifest(manifest); // throws, listing every problem, if it is not one
return resolve(manifest, { mode: "dark" }).colors;
}assertManifest is an assertion function rather than a returning one — it narrows manifest in
place and gives back void, so it is a statement of its own and not an argument to resolve.
validateManifest is the same check as a list of problems, for a caller that wants to report them
all rather than throw on the first.
resolve(null, …) answers the same question about the blank base itself. resolveDensities() and
resolveFonts() are the two axes on their own.
The contrast report
@cueplusplus/theme-base/contrast is the WCAG measurement cue-theme build refuses a theme on —
contrastReport() over CONTRAST_REQUIREMENTS, with contrastRatio() and relativeLuminance()
underneath and WCAG_AA_NON_TEXT (3), WCAG_AA_TEXT (4.5) and WCAG_AAA_TEXT (7) as the floors.
To build a theme rather than read one, use
@cueplusplus/theme-tools —
cue-theme init scaffolds a package that peers on this one and builds before it is edited.
What it ships
| Subpath | What it is |
| --- | --- |
| @cueplusplus/theme-base | the contract (ThemeManifest, ThemeRegistry, ThemeName, the colour and geometry token lists), assertManifest / validateManifest, and resolve / resolveDensities / resolveFonts |
| @cueplusplus/theme-base/contrast | contrastReport, contrastRatio, relativeLuminance, CONTRAST_REQUIREMENTS and the three WCAG floors |
| @cueplusplus/theme-base/base.css | the blank base, importing @cueplusplus/tokens/axes.css |
| @cueplusplus/theme-base/manifest.schema.json | the manifest contract as JSON Schema, for a validator that is not TypeScript |
| @cueplusplus/theme-base/package.json | |
ESM only, Node 22 or newer, types beside every entry.
Where the rest is
- The theming guide — https://ui.cueplusplus.com/docs/theming. The token vocabulary, the
density and mode axes, the shipped presets, and
createTheme()for a palette of your own. docs/CONSUMING.md— registry access in full, the peer matrix per subpath, and the table of what a failed install means.CHANGELOG.md, in this package — one entry per release, addressed to you rather than to the diff, with a Migrating section on anything that needs an edit. Read it before an upgrade; the version number alone does not say what moved.
