@hex-core/themes
Premium theme catalog — midnight, ember, and future presets.
Install
0.2.2Runtime dependencypnpm add @hex-core/themesWhat it does
`@hex-core/themes` is the premium-theme catalog. As of 0.2.0 it ships **73 presets**: the two first-party themes (`midnight`, `ember`) re-exported from `@hex-core/tokens`, plus **71 brand-derived presets** auto-generated from the MIT-licensed [voltagent/awesome-design-md](https://github.com/voltagent/awesome-design-md) briefs — Tesla, Stripe, Linear, Notion, Figma, Claude, Cursor, Vercel, Supabase, Raycast, Framer, …
The split between `tokens` and `themes` exists for release independence. `@hex-core/tokens` ships the foundational themes plus the transformers; tagging a new preset shouldn't force every consumer to retest the transformers. Keeping the catalog separate gives both surfaces their own minor cadence.
Catalog presets carry classification metadata (`category`, `tags`, `attribution`, `brand`, `designBrief`) so a UI can group them, search them, or surface their license context. The studio's theme switcher iterates `presetsByCategory` directly to render a grouped picker without barrel-side wiring.
If you only need the default theme, depend on `tokens` and skip this package. If you want the full branded-preset catalog (or want your own switcher to discover new presets automatically), depend on `themes`.
Public API
premiumThemes
import { premiumThemes } from "@hex-core/themes";
import type { Theme } from "@hex-core/registry";
declare const premiumThemes: Record<string, Theme>;Catalog object keyed by theme slug. Includes the two first-party themes (`midnight`, `ember`) re-exported from `@hex-core/tokens` plus all 71 brand-derived presets. Studio's theme picker iterates this map directly so every entry shows up without barrel-side wiring.
import { premiumThemes } from "@hex-core/themes";
console.log(Object.keys(premiumThemes).length); // 73
console.log(Object.keys(premiumThemes).slice(0, 6));
// → ["midnight", "ember", "airbnb", "airtable", "apple", "binance"]listPremiumThemes
function listPremiumThemes(): Array<{
name: string;
displayName: string;
description: string;
category?: ThemeCategory;
tags?: string[];
brand?: string;
}>;Enumerate the catalog with display metadata. Mirrors the shape of `tokens.listThemes()` so theme switchers can concat both arrays without normalizing. Branded presets carry `category` + `tags` + `brand`; the two first-party themes (`midnight`, `ember`) leave those fields undefined.
import { listPremiumThemes } from "@hex-core/themes";
const all = listPremiumThemes();
console.log(all.length); // 73
for (const t of all.filter((x) => x.category === "fintech")) {
console.log(t.name, "—", t.brand ?? "(no brand)");
}
// → binance — Binance
// → coinbase — Coinbase
// → ...getPremiumTheme
function getPremiumTheme(name: string): Theme | undefined;Look up a single premium theme by name. Returns `undefined` if the name isn't in the catalog — callers should fall back to `defaultTheme` from `tokens`.
import { getPremiumTheme } from "@hex-core/themes";
import { defaultTheme } from "@hex-core/tokens";
const theme = getPremiumTheme(slug) ?? defaultTheme;Branded preset catalog (71 themes)
import {
// AI (12)
claudeTheme, cohereTheme, elevenlabsTheme, minimaxTheme,
mistralTheme, ollamaTheme, opencodeTheme, replicateTheme,
runwaymlTheme, togetherTheme, voltagentTheme, xTheme,
// dev-tools (7)
cursorTheme, expoTheme, lovableTheme, raycastTheme,
superhumanTheme, vercelTheme, warpTheme,
// backend (8)
clickhouseTheme, composioTheme, hashicorpTheme, mongodbTheme,
posthogTheme, sanityTheme, sentryTheme, supabaseTheme,
// productivity (8)
calTheme, intercomTheme, linearTheme, mintlifyTheme,
notionTheme, resendTheme, slackTheme, zapierTheme,
// design (6)
airtableTheme, clayTheme, figmaTheme, framerTheme, miroTheme, webflowTheme,
// fintech (7)
binanceTheme, coinbaseTheme, krakenTheme, mastercardTheme,
revolutTheme, stripeTheme, wiseTheme,
// ecommerce (5)
airbnbTheme, metaTheme, nikeTheme, shopifyTheme, starbucksTheme,
// media (11)
appleTheme, ibmTheme, nvidiaTheme, pinterestTheme, playstationTheme,
spacexTheme, spotifyTheme, thevergeTheme, uberTheme, vodafoneTheme, wiredTheme,
// automotive (7)
bmwTheme, bmwMTheme, bugattiTheme, ferrariTheme, lamborghiniTheme,
renaultTheme, teslaTheme,
} from "@hex-core/themes";
// Each export is a fully-validated Theme — same shape as defaultTheme.
import { themeToScopedRuntimeCss } from "@hex-core/tokens";
const css = themeToScopedRuntimeCss(teslaTheme, { mode: "dark" });71 brand-derived presets, each with both light + dark token sets and full `category` / `tags` / `attribution` metadata. Importing the named export (`teslaTheme`) is tree-shakeable and pulls only that preset; importing via `getPremiumTheme("tesla")` works too but loads the catalog index. The 9 categories — `ai`, `dev-tools`, `backend`, `productivity`, `design`, `fintech`, `ecommerce`, `media`, `automotive` — surface in the studio's grouped picker via `presetsByCategory`.
import { stripeTheme } from "@hex-core/themes";
import { themeToScopedRuntimeCss } from "@hex-core/tokens";
// Apply the Stripe palette inside one section without rebuilding Tailwind.
export function StripeSection({ children }: { children: React.ReactNode }) {
const css = themeToScopedRuntimeCss(stripeTheme, {
scope: "[data-theme=stripe]",
mode: "light",
});
return (
<>
<style>{css}</style>
<section data-theme="stripe">{children}</section>
</>
);
}searchThemes
interface SearchThemesInput {
query?: string; // case-insensitive substring on name / displayName / description
category?: ThemeCategory; // one of "ai" | "dev-tools" | "backend" | ...
tags?: string[]; // intersection match (all tags must be present)
}
function searchThemes(input?: SearchThemesInput): Theme[];Filter the 73-preset catalog by free-text query, category, and/or tags. Combine criteria for AND semantics. The two first-party themes (`midnight`, `ember`) only match via `query` since they pre-date the metadata extension.
import { searchThemes } from "@hex-core/themes";
const fintech = searchThemes({ category: "fintech" });
// → [binanceTheme, coinbaseTheme, krakenTheme, mastercardTheme, ...]
const dark = searchThemes({ query: "dark", tags: ["minimal"] });extendTheme
interface ThemeOverrides {
name?: string; displayName?: string; description?: string;
brand?: string; category?: Theme["category"]; tags?: string[];
designBrief?: string;
attribution?: Theme["attribution"]; // FULL replacement, not deep-merged
tokens?: { light?: Theme["tokens"]["light"]; dark?: Theme["tokens"]["dark"]; };
}
function extendTheme(base: Theme, overrides?: ThemeOverrides): Theme;Compose a custom theme by overlaying overrides on a base preset. Token sets merge at the slot level (missing slots fall through from the base; overridden slots replace the entire `{ value, type }` record). Scalar fields replace the base when present. **`attribution` is full-replacement** — a consumer rebranding a preset under their own license / URL almost always wants their attribution to land cleanly without the upstream's leaking through. The composed theme is re-validated via `strictThemeSchema` before return, so consumers can't accidentally produce an under-spec'd Theme.
import { teslaTheme, extendTheme } from "@hex-core/themes";
const myTesla = extendTheme(teslaTheme, {
name: "my-tesla",
displayName: "My Tesla",
tokens: {
light: { primary: { value: "0 100% 50%", type: "color" } },
},
});loadThemeBrief
function loadThemeBrief(slug: string): Promise<string | undefined>;Lazily load the original markdown design brief for a preset. Briefs are NOT inlined on the preset object — the top-level `@hex-core/themes` barrel stays tree-shakeable. Studio's `/studio/copy` flow (the only consumer that actually needs the brief) calls this on demand; agents that just want token data never pay the ~22KB-per-brief cost.
import { loadThemeBrief } from "@hex-core/themes";
const brief = await loadThemeBrief("stripe");
if (brief) {
console.log(brief.slice(0, 200) + "…");
// "# Design System Inspired by Stripe\n\n## 1. Visual Theme & Atmosphere\n\nStripe's website is the gold standard of fintech design …"
}presetsByCategory / presetSlugs / allThemeSlugs
import type { ThemeCategory, Theme } from "@hex-core/registry";
declare const presetsByCategory: Record<ThemeCategory, readonly Theme[]>;
declare const presetSlugs: readonly string[]; // 71 brand-derived slugs
declare const allThemeSlugs: readonly string[]; // 73: midnight + ember + 71 brands
declare const voltagentPresets: Record<string, Theme>; // slug → preset
Pre-built indexes for catalog-driven UIs. `presetsByCategory` keys themes by their 9 category buckets (the studio's grouped picker iterates this directly). `presetSlugs` is the alphabetical list of just the 71 voltagent presets; `allThemeSlugs` adds `midnight` + `ember` for switchers that want to surface every preset uniformly. `voltagentPresets` is the slug → preset map for direct dictionary access.
import { presetsByCategory } from "@hex-core/themes";
for (const [category, themes] of Object.entries(presetsByCategory)) {
console.log(category, themes.map((t) => t.name).join(", "));
}
// → ai claude, cohere, elevenlabs, …
// → fintech binance, coinbase, kraken, mastercard, revolut, stripe, wise
// → automotive bmw, bmw-m, bugatti, ferrari, lamborghini, renault, teslaThemeOverrides type
import type { ThemeOverrides } from "@hex-core/themes";
interface ThemeOverrides {
name?: string;
displayName?: string;
description?: string;
brand?: string;
category?: Theme["category"];
tags?: string[];
designBrief?: string;
attribution?: Theme["attribution"];
tokens?: {
light?: Theme["tokens"]["light"];
dark?: Theme["tokens"]["dark"];
};
}Input type for `extendTheme`. Token slot keys are an open record (registry's `tokenSetSchema` uses a catchall), so any subset of slots is valid. Slots not listed fall through from the base. Pass `attribution: undefined` to remove attribution entirely.
import type { ThemeOverrides } from "@hex-core/themes";
const overrides: ThemeOverrides = {
name: "brand",
displayName: "Brand",
tokens: { light: { primary: { value: "12 100% 50%", type: "color" } } },
};Workflows
Build a unified theme switcher across both packages
Concat the two catalogs and dedupe by name. Drop into the studio sidebar or any settings panel.
import { listThemes } from "@hex-core/tokens";
import { listPremiumThemes } from "@hex-core/themes";
const all = [...listThemes(), ...listPremiumThemes()];
const byName = new Map(all.map((entry) => [entry.name, entry]));
export function ThemePicker({ value, onChange }: { value: string; onChange: (n: string) => void }) {
return (
<select value={value} onChange={(e) => onChange(e.target.value)}>
{[...byName.keys()].map((n) => (
<option key={n} value={n}>{n}</option>
))}
</select>
);
}Resolve a theme name to its strict object
Walk the catalogs in order; if neither owns the name, fall back to `defaultTheme`. Pass the result into the `tokens` transformers.
import { defaultTheme, getTheme } from "@hex-core/tokens";
import { getPremiumTheme } from "@hex-core/themes";
import type { Theme } from "@hex-core/registry";
export function resolveTheme(name: string): Theme {
const builtIn = getTheme(name);
if (builtIn) return builtIn;
return getPremiumTheme(name) ?? defaultTheme;
}Compatibility
- Peer-free — depends only on `@hex-core/registry@^0.3.2` and `@hex-core/tokens@^1.3.2`.
- Zero React dependencies. Same edge/runtime story as `@hex-core/tokens`.
- `@hex-core/tokens` continues to export `midnightTheme` and `emberTheme` directly — moving them into `themes` is non-breaking.
See also
Verified against @hex-core/components@1.12.0 · @hex-core/tokens@1.3.6 · @hex-core/themes@0.2.2 · @hex-core/registry@0.5.1 · @hex-core/payload@0.3.0