@cueplusplus/theme-graphite
v1.0.2
Published
The graphite theme for @cueplusplus/ui.
Readme
@cueplusplus/theme-graphite
The graphite palette for @cueplusplus/ui — measured from agent-native.com on 2026-09-26: warm
off-white ink on a #0a0a0a ground, borders one step off that ground, a cyan accent, and Geist
Mono. Every value was read off the site's served stylesheet and recorded under this system's token
names, not the site's. A palette is a package here precisely so that it is not the only one:
install several, register several, and a theme switcher is an array with more than one entry in 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-graphite @cueplusplus/theme-base@cueplusplus/theme-base is the one peer this package declares, and it carries the token contract
the palette is written against. A palette on its own paints nothing: install it beside
@cueplusplus/ui and its own peers — §2 has that line.
Quick start
The theme stylesheet is the third import, and its position is load-bearing rather than tidy. A theme
declares its colours under [data-theme="graphite"], which ties with the blank base's bare :root
at the same specificity, so the one declared later wins. Put it before styles.css and the
blank base paints over it.
/* app/globals.css */
@import "tailwindcss";
@import "@cueplusplus/ui/styles.css";
@import "@cueplusplus/theme-graphite/theme.css";Then register the manifest. themes is the registry the provider may switch between; theme names
the one that paints.
import graphite from "@cueplusplus/theme-graphite";
import { ThemeProvider } from "@cueplusplus/ui/system";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider themes={[graphite]} theme="graphite">
{children}
</ThemeProvider>
);
}Importing the package from its root, rather than reaching for its manifest.json subpath, also
closes ThemeName to the themes you actually installed — so a misspelt theme value is a type
error rather than a page that silently paints the blank base. Add prepaintScript() to <head>
with the same themes array, so the first frame is not the defaults flashing past: §4
has it.
What it ships
| Subpath | What it is |
| --- | --- |
| @cueplusplus/theme-graphite | the manifest, as both the default export and manifest |
| @cueplusplus/theme-graphite/theme.css | the palette — the only file a browser needs |
| @cueplusplus/theme-graphite/variants.css | the density rungs and font pairings this theme adds. Empty: it adds none |
| @cueplusplus/theme-graphite/manifest.json | the same manifest as JSON, for a tool that reads rather than imports |
| @cueplusplus/theme-graphite/package.json | |
Both modes, both measured. The manifest is complete with supportsLight: true, so
theme.css writes the whole colour contract four times over: [data-theme="graphite"] for dark,
[data-theme="graphite"][data-mode="light"] for light, and both again for [data-mode="system"]
inside a prefers-color-scheme query. The light block is the site's own light mode, not an
inversion of the dark one — with one substitution. The site's status hues carry no light override,
and on its paper they measure between 1.60:1 and 1.86:1 against a 3:1 floor, so light-mode ok,
warn, danger and info come from the site's documentation palette instead — its good, warning
and bad hues and its link accent, which it already serves in light mode.
Fonts: one, the monospace. The theme block sets its own stack and then hands it to the pairing axis:
--cue-font-theme-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--cue-font-mono: var(--cue-font-pairing-mono, var(--cue-font-theme-mono));The second line is the handshake, and it is why the first is safe: a data-font pairing stamped on
an ancestor wins over the theme's stack, which in turn wins over the base's.
Sources are src/graphite.{dark,light}.tokens.json, colour and type only — no geometry token may
appear in a theme. theme.css, variants.css, manifest.json, index.js and index.d.ts are
written by cue-theme build and committed; prepack runs cue-theme check, which rebuilds all
five in memory and refuses to publish if anything on disk has drifted. Each source's $extensions
says which of the site's roles every value was read from.
No waived contrast pairs. Every required pair clears its floor in both modes — fg/bg at
18.79:1 in each, and the tightest, light ok/bg, at 4.76:1 against 3 — and theme.config.ts
names no knownContrastFailures, so a retune that fails one refuses the build rather than shipping.
This is the first preset written after @cueplusplus/tokens stopped emitting palettes, so unlike the
ten before it there is no [email protected] stylesheet to hold it against:
packages/release/test/theme-packages.test.mjs sweeps it for the package shape and leaves it out of
the byte-for-byte pre-migration identity check.
What it refuses
A palette is colour and one monospace stack, so most of what makes the site recognisable is not here, and some of it would not be even if it could be:
- The rest of the look. Type, rhythm and finish are the
data-font,data-styleanddata-paintaxes, set beside the theme rather than inside it. - The site's identity. Its logos and partner-logo wall, its screenshots and its copy.
- The hero. The gradient hero, the many-stop blue gradient, and the shader and canvas behind it.
- Its names. The site's own custom-property names and the class that scopes them. The values are measurements; the names here are this system's.
Where the rest is
- The theming guide — https://ui.cueplusplus.com/docs/theming. The token vocabulary, the
density and mode axes, the other 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.
