GitHub

npm node license CI

644 terminal color schemes converted to [OKLCH](https://oklch.com/) and republished as an npm package + JSON API. The large majority come from [`mbadolato/iTerm2-Color-Schemes`](https://github.com/mbadolato/iTerm2-Color-Schemes); the rest are drawn from several other upstreams, plus a small set authored here. Per-source counts and licenses are in [Attribution](#attribution) — **they are not all the same license**.

Designed for consumption by Astro sites, theme pickers, Tailwind v4 @theme blocks, and any tooling that wants a clean OKLCH palette without parsing iTerm XML or Alacritty TOML.

Live demo + picker: https://williamzujkowski.github.io/oklch-terminal-themes/

Browse 644 themes via a search + filter combobox, preview each theme live across six UI mocks (palette, terminal, IDE, reading view, dashboard, dataviz), copy the active theme as CSS variables / Tailwind @theme / raw JSON, or share a permalink.

Install

pnpm add @williamzujkowski/oklch-terminal-themes

ESM only. The package is "type": "module" with no CommonJS build, so require() will fail with ERR_REQUIRE_ESM. Use import, or await import() from CJS. Node >= 22.

Usage

Full dataset (server-side / build-time)

import themes from '@williamzujkowski/oklch-terminal-themes/themes.json' with { type: 'json' };
const dark = themes.filter((t) => t.isDark);
console.log(dark[0].colors.background.oklchCss);
// -> "oklch(0.264 0.006 314.7)"

The with { type: 'json' } attribute is required. This package is ESM-only, and Node refuses a JSON import without it (ERR_IMPORT_ATTRIBUTE_MISSING); TypeScript reports TS1543 under module: NodeNext. Bundlers accept it too, so it is safe everywhere.

Every JSON subpath ships a TypeScript declaration, so the import is directly assignable to the exported types — const t: TerminalColorTheme[] = themes just works, with no as unknown as cast. Declaring the shape also means TypeScript never infers over the 5.8 MB literal.

Slim dataset (client-side / theme picker)

import themes from '@williamzujkowski/oklch-terminal-themes/themes-slim.json' with { type: 'json' };
// Each color is a ready-to-paste oklch() CSS string.

Index only (lazy-load individual themes)

import index from '@williamzujkowski/oklch-terminal-themes/index.json' with { type: 'json' };

Loading one theme on demand depends on where the code runs.

In a bundler (Vite, Astro, SvelteKit). Use import.meta.glob — a template literal in a dynamic import() cannot be statically analysed, so Vite emits the bare specifier unresolved into the browser bundle with no warning, and it throws at runtime:

const themes = import.meta.glob(
  '/node_modules/@williamzujkowski/oklch-terminal-themes/data/by-name/*.json',
);
async function loadTheme(slug: string) {
  const load =
    themes[`/node_modules/@williamzujkowski/oklch-terminal-themes/data/by-name/${slug}.json`];
  if (load === undefined) throw new Error(`unknown theme: ${slug}`);
  return (await load()) as { default: unknown };
}

In the browser with no bundler, fetch it from a CDN instead:

// Split out so the literal below is a complete, resolvable URL. Inlining it
// leaves `${slug}` inside the string, which the repo's link check reads as
// part of the address and reports as a 404 against a path nobody ships.
const CDN =
  'https://cdn.jsdelivr.net/npm/@williamzujkowski/oklch-terminal-themes@0.7.1/data/by-name/';
async function loadTheme(slug: string) {
  const res = await fetch(`${CDN}${slug}.json`);
  return res.json();
}

In Node, the dynamic specifier resolves fine:

async function loadTheme(slug: string) {
  return (
    await import(`@williamzujkowski/oklch-terminal-themes/themes/${slug}.json`, {
      with: { type: 'json' },
    })
  ).default;
}

CSS custom properties

import { themeToCssVars } from '@williamzujkowski/oklch-terminal-themes';
import dracula from '@williamzujkowski/oklch-terminal-themes/themes/dracula.json' with { type: 'json' };
const css = `:root {\n${themeToCssVars(dracula)}\n}`;

Static per-theme CSS (zero-JS <link> tag)

Every theme also ships a pre-built static CSS file at data/css/<slug>.css — a bare :root { ... } block plus a [data-terminal-theme="<slug>"] { ... } scoped block, both driving the same --terminal-* custom properties. No JS, no build step, no import — just a <link> tag:

Reachable two ways. From a bundler, by package specifier:

import '@williamzujkowski/oklch-terminal-themes/css/dracula.css';

Or with no build step at all, straight from a CDN:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@williamzujkowski/oklch-terminal-themes/data/css/dracula.css"
/>

jsDelivr auto-serves any file from a published npm tarball (data/ is listed in this package's files), so every theme's CSS file is reachable without any extra publishing step — swap dracula for any slug. To pin a version for reproducible builds, insert @0.7.1 (or any published version) directly after the package name; omit it to track latest.

base16/base24 scheme YAML (local export — do not submit upstream)

Every theme also ships a base24 scheme YAML at data/schemes/base24/<slug>.yaml (importable as @williamzujkowski/oklch-terminal-themes/schemes/base24/<slug>.yaml) (base24 is preferred — its base10-base17 range takes this dataset's bright* ANSI slots directly), plus a base16 subset projection at data/schemes/base16/<slug>.yaml for tooling that only understands base16. These are compatible with the tinted-theming/tinty template ecosystem (Alacritty, Kitty, WezTerm, Ghostty, Windows Terminal, foot, and hundreds of app templates) — point tinty or any base16/base24 builder at the file and it Just Works.

system: 'base24'
name: 'Dracula'
author: 'iTerm2-Color-Schemes (via oklch-terminal-themes)'
variant: 'dark'
palette:
  base00: '#282a36'
  # ...
  base09: '#ffb153' # base09/base0F synthesized (hue-derived, no source data — see README)
  # ...

Do not open PRs adding these generated schemes to tinted-theming/schemes or any other upstream curated collection. A dedup/overlap analysis (issue #146, measured against the 633-theme corpus as it stood then) found 227/633 (35.9%) of this corpus already exists there as hand-curated schemes (209 exact-name + 18 family matches — every gruvbox slug collides), and 93.4% of this corpus is itself bulk-imported from iterm2-color-schemes, so this project lacks curation standing for most of it. These schemes are for local/tinty consumption only. The 17 hand-authored native themes are the only non-overlapping set, and upstream submission even for those is a separate future decision.

Slot mapping — most slots are direct references to this dataset's own fields (base00=background, base02=selection, base03=brightBlack, base05=foreground, base07=brightWhite, base08/0A-0E=the six classic ANSI colors, base12-17=the six bright ANSI colors). base01/04/06 are OKLCH lightness-interpolated midpoints between their documented neighbor anchors; base10/11 extrapolate further from background. base09 (orange) and base0F (brown) have no source data in this dataset at all — they're synthesized via hue-derivation (base09 is the circular-hue midpoint between red and yellow; base0F is base09 pulled toward the background's lightness and desaturated). Every emitted YAML discloses this with an inline # base09/base0F synthesized comment. Full mapping table with confidence ratings: src/schemes.ts's module doc comment.

Design tokens (DTCG / W3C Design Tokens)

Every theme ships a W3C Design Tokens file at data/tokens/<slug>.tokens.json, importable as @williamzujkowski/oklch-terminal-themes/tokens/<slug>.tokens.json. Feeds Style Dictionary, Tokens Studio, Figma variables, and anything else that reads the DTCG format.

{
  "color": {
    "$type": "color",
    "background": {
      "$value": {
        "colorSpace": "oklch",
        "components": [0.2882, 0.0221, 277.5],
        "alpha": 1,
        "hex": "#282a36"
      }
    },
    "ansi": { "normal": { "red": { "$value": { "…": "" } } } }
  }
}

Token paths are color.background, color.foreground, color.cursor, color.selection, and color.ansi.normal.* / color.ansi.bright.* for the 16 ANSI slots. Theme metadata (slug, tags, source, counterpart) lives under $extensions["dev.oklch-terminal-themes"], which is the spec's sanctioned place for anything it doesn't model — a non-$ property at the root would be read as a token instead.

Three things worth knowing:

  • Stable spec surface only. These files use the ratified 2025.10 colour type, groups, $type inheritance, $description and $extensions. The multi-mode / resolver drafts are not encoded, even though this dataset knows each theme's light/dark counterpart and could express the pairing. A file that guesses at an unratified shape is worse than one you pair yourself, because it looks authoritative. counterpart is surfaced as metadata so you can do the pairing without guessing.
  • OKLCH, not hex-first. Most token sets that reach Figma have already been flattened to hex and thrown the gamut information away. These carry the OKLCH components the dataset is built on, with hex alongside as an exact sRGB fallback — the source value, never a reconversion, so it agrees with every other export here.
  • Greys have no hue. When chroma is exactly 0 the hue component is "none", the spec's powerless-component form, rather than a literal 0. About 15% of this corpus's slots are achromatic, and calling them all hue-0 would make a consumer's ramp bend toward red for no reason. Tools that don't read "none" still have hex.

Tailwind v4

Import the theme's static CSS — it defines the --terminal-* custom properties that the @theme block then maps to Tailwind colour tokens:

@import 'tailwindcss';
@import '@williamzujkowski/oklch-terminal-themes/css/dracula.css';
@theme {
  --color-terminal-bg: var(--terminal-background);
  --color-terminal-fg: var(--terminal-foreground);
}

CSS cannot import JSON, so the theme has to arrive as CSS. Swap dracula for any slug, or <link> the same file directly (see above) if you are not using Tailwind.

Astro

Read the dataset at build time and emit the custom properties inline — no client-side JS, and the theme is correct on first paint:

<script lang="ts">
  import { themeToCssVars } from '@williamzujkowski/oklch-terminal-themes';
  import themes from '@williamzujkowski/oklch-terminal-themes/themes-slim.json' with { type: 'json' };

  let slug = $state('dracula');
  const theme = $derived(themes.find((t) => t.slug === slug));
</script>
<svelte:head>
  {@html `<style>:root {${themeToCssVars(theme)}}</style>`}
</svelte:head>

Schema

Each theme record:

interface TerminalColorTheme {
  name: string; // "Dracula"
  slug: string; // "dracula"
  isDark: boolean;
  tags: string[]; // see "Tags" below
  source: 'iterm2-color-schemes';
  sourceUrl: string; // deep link to upstream file at pinned SHA
  upstreamSha: string;
  updatedAt: string; // ISO 8601
  colors: Record<
    ColorKey,
    { hex: string; oklch: { l: number; c: number; h: number }; oklchCss: string }
  >;
  contrast: {
    fgOnBg: number; // WCAG 2.x body-text ratio (foreground vs background)
    minAnsi: number; // worst non-blend ANSI slot vs background
    minAnsiSlot: ColorKey; // which slot hit `minAnsi`
    cursorOnBg?: number; // cursor vs background (WCAG 1.4.11 non-text pair)
    selectionContrast?: number; // foreground vs selection-background
    brightnessOrdered?: boolean; // true iff every bright* slot is lighter than its normal counterpart
    brightnessViolations?: ColorKey[]; // bright* slot names that fail the above; empty when ordered
  };
  counterpart?: string; // slug of the canonical opposite-polarity pair — see "Counterpart" below
  accent?: {
    // computed/curatable signature color — see "Accent" below
    source: 'cursor' | ColorKey; // 'cursor' or one of the 16 ANSI keys
    hex: string;
    oklch: { l: number; c: number; h: number };
    oklchCss: string;
  };
  dataviz?: {
    // derived data-visualization palette — see "Dataviz" below
    categorical: ColorValueEntry[]; // 6-8 colors, always distinct
    categoricalSynthesized?: number; // trailing entries derived from the accent, not slots
    sequential: ColorValueEntry[]; // 7-step background -> accent ramp
    diverging: ColorValueEntry[]; // 7-step accent-hue <-> farthest-hue ramp
  };
  cvd?: {
    // colorblind-safety simulation scores — see "Colorblind safety" below
    deuteranopia: number; // min pairwise ΔE2000 among the 6 classic ANSI hues, post-simulation
    protanopia: number;
    tritanopia: number; // data-only — doesn't gate `cvd-safe`/`cvd-caution`
  };
  apca?: {
    // APCA Lc scores, DATA ONLY — see "APCA" below
    fgOnBg: number; // signed Lc, foreground (text) on background
    minAnsi: number; // signed Lc of the worst-case (smallest |Lc|) non-blend ANSI slot
    minAnsiSlot: ColorKey;
  };
}
// Each dataviz color is a full { hex, oklch, oklchCss } record — the same
// shape as `colors[key]` above.
type ColorValueEntry = {
  hex: string;
  oklch: { l: number; c: number; h: number };
  oklchCss: string;
};

20 color keys per theme: background, foreground, cursor, selection, and the 16 ANSI slots (black...white, brightBlack...brightWhite).

Counterpart

counterpart links a theme to its canonical opposite-polarity pair (e.g. ayu-light's counterpart is ayu; remarque-light's counterpart is remarque-dark). It's computed at build time from a slug-stem heuristic (ayu-light and ayu share the stem ayu), plus a small curated map for families with more than one light or dark variant (catppuccin, github, gruvbox, gruvbox-material, material, rose-pine, tokyonight, zenbones).

The field is directional and not necessarily involutive: several dark variants in a family may point at one canonical light member, while that light member points back at only its canonical dark. For example, tokyonight-storm's counterpart is tokyonight-day, but tokyonight-day's counterpart is the bare tokyonight, not tokyonight-storm.

counterpart is present in themes.json, themes-slim.json, index.json, and each data/by-name/<slug>.json record. It's absent (the key is omitted) for themes with no identifiable counterpart. See #128 for the seeding analysis.

Accent

accent is a theme's computed signature/accent color — the answer to "what is this theme's one defining hue?" It's computed at build time by the same heuristic remarque-tokens' theme bridge uses to derive its --color-accent token: cursor if the cursor color is chromatic (OKLCH chroma >= 0.05), otherwise the most-chromatic of the six classic ANSI colors, in this order — blue, purple, red, green, cyan, yellow — with ties broken by that same order.

The accent VALUE is always a reference to the chosen slot's own color (the same hex/oklch/oklchCss), never a newly derived color — scripts/validate.ts asserts that equality exactly. Across the current dataset the heuristic splits: cursor 232, red 153, purple 92, green 28, blue 18, yellow 16, cyan 8.

A small curated override map (CURATED_ACCENT_OVERRIDES in src/accent.ts, seeded empty) can pin a specific theme's accent to a different slot for the rare case where the heuristic's guess doesn't match the theme's actual identity — same shape as the counterpart overrides. See #133.

accent is present in themes.json (full { source, hex, oklch, oklchCss }), and trimmed to { source, oklchCss } in themes-slim.json and index.json — the same lean-index convention as the rest of those files.

Dataviz

dataviz is a theme's derived data-visualization palette — a categorical swatch set plus sequential/diverging ramps, computed at build time as pure functions over colors + accent. It exists so a downstream consumer (e.g. remarque's syntax-highlighting bridge) doesn't have to re-derive chart-ready colors from a raw ANSI palette itself. See #150.

  • categorical (6-8 colors) — selected from the theme's 12 chromatic ANSI slots (6 classic + 6 bright; black/white/brightBlack/brightWhite excluded as non-chromatic). Near-identical hues (bright variants that are little more than a lightened copy of their normal counterpart, within ~20° of hue) collapse to whichever is more chromatic. Selection then starts from the hue closest to the theme's accent and greedily adds the remaining candidate that maximizes its minimum hue-distance to everything already picked — a standard farthest-point / max-min-distance strategy, the same one IBM Carbon Design System and Observable Plot's categorical-palette guidance converge on for "adjacent-distinguishability." Insertion order visits far-apart regions of the hue circle before backfilling nearby gaps, which is why adjacent entries in the final array rarely land on near-complementary (~180° apart) pairs — the failure mode Judith Helfman's writing on categorical color warns produces visual vibration/afterimage artifacts when complementary hues sit directly next to each other. A theme only gets 7 or 8 categorical colors when that many distinct hue clusters actually exist; low-hue-diversity themes correctly settle at the 6-color floor. Candidates below a chroma floor (0.02) are excluded — at c ≈ 0 a color's hue is a numerical artifact rather than a property, so a grey must not win a hue-distance comparison.

    Entries are references to their own ANSI slot's color, same convention as accentexcept where a theme has too few distinct chromatic slots to fill the palette. 33 themes in this corpus do (hercules-graphics has no chromatic slots at all; black-metal-marduk and owl have one). For those, the shortfall is filled with colors derived from the theme's accent, and categoricalSynthesized discloses how many trailing entries are derived — so categorical.slice(0, categorical.length - categoricalSynthesized) is exactly the set backed by a real slot. The field is absent (not 0) when nothing was synthesized. Every entry is guaranteed distinct: padding with duplicates would defeat the palette's only purpose, and it is what these themes shipped before #198.

  • sequential (7 steps) — a straight-line OKLCH interpolation from background to accent: lightness ramps from the background's own value to the accent's, chroma ramps from 0 up to the accent's own chroma, hue is held fixed at the accent's hue throughout (a single-hue ramp reads as one color at increasing intensity, not a rainbow — the Carbon/Observable convention for sequential scales). Index 0 is always background-anchored (lowest emphasis); the last index is always the accent itself (highest emphasis). For a dark theme (low background lightness) that plays out dark-to-light; for a light theme (high background lightness) it plays out light-to-dark — same "low to high emphasis" semantic in both polarities, just expressed in whichever lightness direction that theme's own background implies. Monotonic in lightness by construction.

  • diverging (7 steps, always odd) — two arms meeting at a near-achromatic midpoint: one arm anchors on the accent's own hue, the other on whichever categorical color's hue is farthest (by circular distance) from the accent. Lightness is a single linear ramp across all 7 steps from one arm's endpoint to the other's — the midpoint's lightness is just that ramp evaluated at its center, so the whole array is monotonic in l. The divergence itself reads through chroma/hue: each arm's chroma ramps down to a small near-background value (~0.0075) at the midpoint.

Both sequential and diverging are newly derived colors (not references) — gamut-fit at every step (chroma is clamped to what's actually displayable at that step's own lightness/hue before rounding, since the sRGB gamut boundary narrows sharply near black/white and shifts with hue — a naive lightness/chroma interpolation can walk through combinations that are fine at the ramp's endpoints but invalid partway through). scripts/validate.ts enforces categorical length (6-8) and distinctness, diverging's odd length, sequential's lightness-monotonicity, and a round-trip ΔE2000 < 1.0 gate on every derived color.

dataviz is present in full in themes.json; themes-slim.json trims it to { categorical: string[] } (just the oklchCss strings, mirroring how accent gets trimmed there); index.json omits it entirely to keep the index lean.

Worked exampledracula's accent is green (#50fa7b, hue 148°); its categorical hex row: #50fa7b, #ff79c6, #8be9fd, #bd93f9, #ff5555, #f1fa8c (6 colors — Dracula's ANSI bright variants dedupe against their normal counterparts, same shape as remarque-dark/remarque-light, whose categorical instead settles on blue since their accent hue is 250°).

Colorblind safety (cvd)

cvd scores how well a theme's 6 classic ANSI hues (red, green, yellow, blue, purple, cyan) stay distinguishable under simulated color-vision deficiency. Computed at build time via culori's filterDeficiencyDeuter/filterDeficiencyProt/filterDeficiencyTrit filters (Machado, Oliveira & Fernandes 2009) — never hand-rolled — followed by the minimum pairwise CIEDE2000 ΔE among the 6 simulated colors, the same ΔE metric family this package already uses for its round-trip validation gate. See #149.

cvd: {
  deuteranopia: number; // min pairwise ΔE2000, post deuteranopia simulation
  protanopia: number; // min pairwise ΔE2000, post protanopia simulation
  tritanopia: number; // data-only — doesn't gate the tag (see below)
}

Higher is better — a low score means at least two of the theme's 6 signal colors become hard to tell apart under that deficiency (the "is this a git-diff addition or deletion?" failure mode). The cvd-safe tag requires both deuteranopia and protanopia >= 10 (CIEDE2000 units); anything below either bar is tagged cvd-caution instead. tritanopia (blue-yellow deficiency, far rarer than red-green) is reported for free but doesn't gate either tag.

The 10 threshold is deliberately conservative and validated against known references: the Okabe-Ito-derived wong-colorblind-safe-dark/wong-colorblind-safe-light native themes both clear it on the two gating axes (as they must — they're the textbook "designed to be CVD-safe" palette), currently wong-dark d=15.7/p=12.2 and wong-light d=12.4/p=12.1. Note wong-light's tritanopia score is 9.7, just under the same figure — tritanopia doesn't gate the tag, and the claim here is specifically about the axes that do. Across the full corpus, most themes are decorative community palettes never designed with CVD safety in mind, so only a small minority clear the bar — see the current corpus split in the build log / CHANGELOG rather than treating a low pass rate as a bug.

The simulation runs in linear-light RGB. culori's filterDeficiency* applies its Machado matrices to gamma-encoded sRGB, but the model defines them on linear RGB — the same error R's colorspace carried until 2.1-0. This package converts before applying culori's matrices (never hand-rolling them). See #197; scores are not comparable with versions before that fix.

APCA

apca adds APCA (Accessible Perceptual Contrast Algorithm) Lc scores alongside the WCAG 2.x contrast block, computed via the apca-w3 reference implementation — data only: nothing in this package tags or gates on these values, the wcag-*/ansi-legible tags remain driven entirely by contrast. See #151.

apca: {
  fgOnBg: number; // signed Lc, foreground (text) on background
  minAnsi: number; // signed Lc of the worst-case non-blend ANSI slot vs background
  minAnsiSlot: ColorKey; // which slot hit minAnsi
}

Lc ranges roughly ±108 and is polarity-aware, unlike WCAG2's symmetric ratio: positive Lc means the text color is darker than the background, negative means it's lighter — the sign matters, not just the magnitude. As a rough guide, |Lc| >= 60 is APCA's approximate analogue of WCAG's 4.5:1 body-text guidance (contexts differ — see the APCA docs for the full font-size/weight lookup table this package doesn't attempt to replicate).

Why add a second contrast metric at all? WCAG 2.x's relative-luminance math is well documented to overstate contrast in the low-luminance ranges where most dark terminal themes live. A concrete example from this corpus: github-dark passes wcag-aa (6.09:1) comfortably, but its APCA fgOnBg is only -43.5 — well short of the ~60 body-text guidance. APCA is still evolving outside the W3C standards process, which is exactly why it stays data, not policy, here.

Tags

Tag Meaning
dark / light derived from OKLCH background lightness
vibrant / muted average OKLCH chroma across all 20 slots
popular slug matches a well-known family (dracula, nord, solarized, …)
wcag-aaa contrast.fgOnBg ≥ 7
wcag-aa contrast.fgOnBg ≥ 4.5
wcag-aa-large 3 ≤ contrast.fgOnBg < 4.5
wcag-fail contrast.fgOnBg < 3
ansi-legible contrast.minAnsi ≥ 3 — every non-blend ANSI slot clears AA-large against the background
cursor-visible contrast.cursorOnBg ≥ 3.0 — WCAG 1.4.11 Non-text Contrast floor (cursor is a UI element, not text)
selection-legible contrast.selectionContrast ≥ 4.5 — WCAG 1.4.3 AA body-text bar applied to fg-on-selection
brightness-ordered contrast.brightnessOrdered — every bright* slot is strictly lighter (OKLCH L) than its normal counterpart across all 8 pairs
high-contrast / low-contrast retained for backwards compatibility with pre-WCAG-tag consumers (> 10:1 / < 5:1 respectively)
cvd-safe / cvd-caution cvd.deuteranopia AND cvd.protanopia both ≥ 10 (CIEDE2000, post-simulation) — see "Colorblind safety" above

minAnsi excludes the slot(s) that conventionally blend with the background — black + brightBlack on dark themes, white + brightWhite on light themes — so intentional near-bg slots don't false-flag otherwise well-formed themes.

cursor-visible and selection-legible are additive/optional fields (issue #145) — absent on data built before they existed. cursorOnBg is the background-vs-cursor WCAG ratio; a cursor is a non-text UI element, so the 3:1 WCAG 1.4.11 floor applies rather than the 4.5:1 body-text bar. selectionContrast is foreground-vs-selection-background — the schema carries no dedicated selected-text-color slot, so fg-on-selection is the meaningful "can you still read the text once it's selected?" pair, judged against the same 4.5:1 AA bar as wcag-aa since selected text is still text.

brightness-ordered catches a real bug class where a theme's bright* ANSI slots aren't actually lighter than their normal counterparts (e.g. brightBlack darker than black) — such themes render worse than authored in terminal emulators that map SGR bold to the bright palette (see microsoft/terminal #12957/#5384, terminator #943). contrast.brightnessViolations lists the offending bright* slot names; it's empty when brightnessOrdered is true.

How it's built

  1. Fetch — sparse clones of every repo listed in sources.json, each pinned to a per-source SHA in .upstream-shas.json.
  2. Convert — hex → OKLCH via culori. Achromatic hue coerced to 0 (JSON-safe). Lightness clamped [0, 1], chroma [0, 0.5].
  3. ClassifyisDark derived from OKLCH lightness; tags from chroma average + WCAG contrast + name heuristics; cvd (colorblind-safety simulation scores) and apca (APCA Lc scores, data only) computed alongside.
  4. Validate — Zod schema + a ΔE2000 < 1.0 gate + within-source duplicate-slug guard. The gate measures the published values, not a float re-derivation: for hex-authored slots it compares the stored oklch (4dp) and oklchCss (3dp) against the hex they were derived from, and for OKLCH-authored slots it round-trips the authored oklch through the gamut-clamped, 8-bit-quantized hex. Current corpus max is 0.5439. Cross-source slug collisions resolve via sources.json order (first source wins, dropped duplicate logged).
  5. Emitdata/themes.json, data/themes-slim.json, data/index.json, data/by-name/<slug>.json, plus per-theme static export artifacts: data/schemes/base16/<slug>.yaml, data/schemes/base24/<slug>.yaml, data/css/<slug>.css (see "base16/base24 scheme YAML" and "Static per-theme CSS" above). Every record carries source (the source id) and upstreamSha for that source.

GitHub Actions re-runs this weekly and opens a PR on upstream diff across all sources.

Attribution

Color schemes originate from the upstream repositories configured in sources.json. Authorship of individual schemes belongs to their upstream authors; see NOTICE for the full license texts.

Upstream source Themes Share License
iTerm2-Color-Schemes iterm2-color-schemes 601 93.3% MIT
oklch-terminal-themes (native) native 17 2.6% MIT
Warp — Special Edition warp-special-edition 8 1.2% Apache-2.0
Monoglow monoglow 4 0.6% Apache-2.0
Cyberdream cyberdream 3 0.5% MIT
JetBrains-inspired (jb.nvim) jb-nvim 2 0.3% Apache-2.0
Kanagawa Paper kanagawa-paper 2 0.3% MIT
Koda koda 2 0.3% MIT
Thorn thorn 2 0.3% MIT
Warm Burnout warm-burnout 2 0.3% MIT
SilkCircuit silkcircuit 1 0.2% MIT
Token (ThorstenRhau) thorsten-token 0 BSD-3-Clause

Licenses are not uniform. Most of the corpus is MIT, but monoglow, jb-nvim and warp-special-edition are Apache-2.0 and thorsten-token is BSD-3-Clause. If you redistribute a subset, check the source field on each theme rather than assuming the whole dataset is MIT — this project's own MIT license covers the conversion pipeline and the native themes, not the upstream artwork.

thorsten-token is configured but currently contributes 0 themes; it is kept so its attribution and license survive a future re-import.

This table and the counts in it are checked against sources.json and data/themes.json by test/attribution.test.ts, so they cannot drift silently.

License

MIT — see LICENSE.

Read the original on github.com ↗