@adea-ai/themes
v0.9.5
Published
The Adea theme catalogue: first-party and imported semantic OKLCH themes, with Base24, terminal, CSS and syntax adapters.
Maintainers
Readme
@adea-ai/themes
The Adea theme catalogue. Thirty-four themes — every one an OKLCH theme with the full set of semantic surface roles, sixteen ANSI colours, a cursor and a selection — including first-party Adea palettes and mature upstream palettes normalized through the same adapters.
bun add @adea-ai/themesThe catalogue
| Family | Variants | | --------------------------------------------------- | --------------------------------------------------------------------------------------- | | Adea | Dark, Dark Colorblind, Dark High Contrast, Light, Light Colorblind, Light High Contrast | | Aardvark | Ink, Blue | | Catppuccin | Latte, Frappé, Macchiato, Mocha | | Tokyo Night | Day, Storm, Night | | Rosé Pine | Dawn, Moon, Main | | Gruvbox | Light, Dark | | Everforest | Light, Dark | | Ayu | Light, Mirage, Dark | | Solarized | Light, Dark | | Monokai | Classic | | Nord | Nord, Light | | Dracula, One Dark, Kanagawa, Vesper | one each |
The six Adea variants are the managed family — two appearances × three accessibility variants — and all of them are composed from upstream palettes rather than authored or copied:
| Variant | canvas | hues | accent |
| ------------------------ | ------------------------------------ | ------------------------------------- | ---------- |
| Adea Dark | GitHub Dark Default, re-greyed | GitHub Dark Default | its violet |
| Adea Dark Colorblind | same re-greyed canvas | GitHub Dark Colorblind | its violet |
| Adea Dark High Contrast | GitHub Dark High Contrast, re-greyed | GitHub Dark High Contrast | its violet |
| Adea Light | GitHub Light Default #ffffff | GitHub Dark Default, transposed | its violet |
| Adea Light Colorblind | GitHub Light Colorblind #ffffff | GitHub Dark Colorblind, transposed | its violet |
| Adea Light High Contrast | GitHub Light High Contrast | GitHub Dark High Contrast, transposed | its violet |
The re-greying is the one family-wide customisation: GitHub's dark greyscale is cut
blue (h≈258), so its chroma is halved and the hue rotated to violet-grey — the
ground the family's violet accent is meant to read on. The hue slots are untouched
(GitHub's dark palette stays GitHub's), the accessibility variants are compositions
of GitHub's own accessibility variants, and every light variant is its dark
counterpart's hues transposed onto paper, so a red is the same red in both modes of
a variant and switching appearance changes lightness and nothing else.
tests/provenance.test.ts asserts the partnership — identical hue and chroma for
every chromatic role across a variant's two appearances, the re-tinted canvases,
and the violet accent on all six.
The other themes reproduce somebody else's palette from a named revision and are credited in NOTICE.
Using it
For an application that needs one palette, import its generated module directly:
import theme from '@adea-ai/themes/themes/adea-dark'
import { toXtermTheme } from '@adea-ai/themes/adapters/xterm'
terminal.options.theme = toXtermTheme(theme)This entry point does not load the catalogue, other palettes, or normalization.
@adea-ai/themes/metadata exports themeMetadata, a picker catalogue containing
identity, family, appearance, description, tags, and provenance, without palette
values. Load a selected palette through a static map of dynamic imports so the
application bundler can create separate chunks. The full catalogue API below
remains available when every palette is needed.
All JavaScript exports use native ESM with explicit relative file extensions.
The shadcn adapter is available at @adea-ai/themes/adapters/shadcn.
tests/package.test.ts builds and packs the actual archive, resolves its public
exports in Node, and checks that a single-theme browser bundle excludes other
palettes and catalogue tooling. Its 16 KiB uncompressed ceiling leaves headroom
for one record and the terminal adapter while detecting catalogue inclusion.
This package still has no runtime dependencies.
import { getTheme, themeFamilies, resolveTheme } from '@adea-ai/themes'
import { themeCssVariables } from '@adea-ai/themes/adapters/css'
import { toXtermTheme } from '@adea-ai/themes/adapters/xterm'
import { toShikiTheme } from '@adea-ai/themes/adapters/shiki'
// Apply a theme to the document.
const theme = resolveTheme('catppuccin-mocha', 'dark')
for (const [name, value] of Object.entries(themeCssVariables(theme))) {
document.documentElement.style.setProperty(name, value)
}
// Colour a terminal.
terminal.options.theme = toXtermTheme(theme)
// Register a syntax theme.
const highlighter = await createHighlighter({ themes: [toShikiTheme(theme)], langs: [...] })syntaxRolesHex(theme) keeps comments at the catalogue's intentionally quiet default.
For editor palettes where comments and diff markers are small content text,
editorRolesHex(theme) returns the editor roles with every rounded hex value checked
against a 4.5:1 contrast floor. The source syntax palette remains unchanged.
Every exported theme value is an oklch() string, which means the browser does the
colour work and a consumer can compose — color-mix(in oklch, var(--adea-accent),
transparent 20%) — and get a predictable result. The adapters that need hex (xterm,
Shiki) convert, gamut-mapping rather than clipping.
The design, in four decisions
Adea owns the schema and the transformation. The seventeen surface roles in
src/schema.ts are the contract. No upstream project is asked to satisfy it.
palettes/ holds Base24 schemes reproduced from a pinned revision; and
src/normalize.ts is the single place an input becomes a canonical application theme.
Adding a theme is a line in src/sources.ts plus a rebuild.
A theme may also be composed from two donors — one supplying the structure, another
the hues — which is how all six of Adea's own themes are built. The composition is a slot
map in COMPOSED_SOURCES, so both parents stay named and freshening either one is a
one-line diff rather than a re-transcription. A composition can also declare
hueTranspose, which moves a borrowed hue set onto a different kind of canvas as a
group: hue and chroma intact, every relative brightness preserved.
OKLCH is the representation. Perceptually uniform lightness is what makes a surface ladder buildable by adding fixed steps, a contrast failure repairable by moving one axis, and a theme re-hueable without re-deriving its ramp by eye.
Contrast is measured, not assumed. Nothing enters the catalogue without clearing
the floors in src/validate.ts, and bun run catalogue:build fails if a theme
cannot be repaired within budget. The pipeline caught real defects that a
hand-authored catalogue would have shipped:
- Solarized Light's body text measures 4.28:1 on its own background — under WCAG AA.
- Tokyo Night's black is 1.17:1 against its canvas, and Gruvbox Light's
whitewas resolving to a near-black, both from light schemes that invert Base24's greyscale ramp. - Everforest Light contains no colour above 2.6:1 on its own canvas, so its status and accent roles have to be deepened rather than used as published.
Derived colours are derived. Syntax roles, chart series and status fills are
functions of the roles a theme does hold (src/derive.ts), so two consumers cannot
disagree about what colour a keyword is.
Base24 is a bridge, not the schema
Base24 is an excellent interchange format and a poor application schema: it has a slot for the colour of a deprecated API and nothing that means "the surface one step above the card". So Base24 is how values get in and how they get out:
import { toBase24, formatBase24Scheme, parseBase24Scheme } from '@adea-ai/themes'
import { getBase24Scheme } from '@adea-ai/themes'
formatBase24Scheme(toBase24(getTheme('nord')!)) // a Base24 scheme
getBase24Scheme('nord') // the vendored original, untouchedtoBase24 re-derives Base24's orange and brown slots, because Adea's schema has no
role for them; getBase24Scheme returns the artefact as reproduced, for callers that
need it byte-for-byte. The mapping is documented in src/adapters/base24.ts.
Adapters
| Import | For | Output |
| ------------------- | --------------------- | --------------------------------------------------- |
| adapters/css | the application | CSS custom properties in OKLCH |
| adapters/tailwind | the application | a Tailwind v4 @theme inline block |
| adapters/shadcn | this org's components | the shadcn vocabulary, bridged from the same source |
| adapters/xterm | shells | xterm.js's hex-only ITheme |
| adapters/shiki | code views | a Shiki theme registration |
| adapters/base24 | interop | Base24, both directions |
The shadcn bridge is worth knowing about before you read the code: Adea's accent
becomes shadcn's primary, because in shadcn --primary is the action colour and
--accent is a hover wash. Mapping it the other way turns every primary button grey.
The mapping is in src/adapters/shadcn.ts.
Working on it
bun install
bun run verify # typecheck, lint, catalogue freshness, tests, build
bun run catalogue:build # regenerate src/generated from palettes/ and sources.ts
bun run catalogue:check # fail if the committed catalogue is stale
bun run vendor # refresh palettes/ from the pinned upstream revisionThe generated catalogue is committed, and catalogue:check fails when it and its
inputs disagree. A palette change is therefore a reviewable diff rather than an
invisible drift.
palettes/ is regenerated by bun run vendor, which reproduces Base24 schemes from
a revision pinned in src/sources.ts. To adopt a newer upstream, change
CATALOGUE_REVISION, run vendor, then catalogue:build and read the diff — the
build prints every value it repaired and why.
Composing a theme from two palettes
Add an entry to COMPOSED_SOURCES in src/sources.ts naming both donor slugs, the
slot map, and the slots to synthesise; list the greyscale roles to pin to the structure
donor; set hueTranspose if the hues are being borrowed for a different kind of canvas;
then bun run vendor && bun run catalogue:build. The vendor step asserts the two maps
cover every Base24 slot, so a composition cannot be half-specified. Assert each half
against its donor in tests/provenance.test.ts.
Adding a theme
- Add its Base24 scheme to
palettes/(or letvendorfetch it: add the slug toVENDORED_SOURCESinsrc/sources.tsfirst). - Record its family's project, URL and licence in
FAMILY_PROVENANCE, and its entry inVENDORED_SOURCES— id, labels, description, appearance, tags. - Declare
accentSlotif the family's identity is not in its blue slot. Everforest is green, Rosé Pine is iris, Monokai is magenta. - Add a fidelity assertion to
tests/provenance.test.ts. The suite fails without one — a theme cannot be added without recording where its colours come from. bun run catalogue:build && bun run verify.
If the build reports a role it could not repair within budget, the answer is a
decision rather than a tweak: either the palette genuinely cannot express that role,
in which case say so in the entry's description, or the floor is wrong, in which case
change it in CONTRAST_FLOORS and justify it there.
Licence
Apache-2.0. The palettes are not: see NOTICE for each project and its terms.
