@hex-core/themes

Premium theme catalog — midnight, ember, and future presets.

Install

Version 0.2.3Runtime dependency
pnpm
pnpm add @hex-core/themes

What 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

ts
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.

tsx
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

ts
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.

tsx
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

ts
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`.

tsx
import { getPremiumTheme } from "@hex-core/themes";
import { defaultTheme } from "@hex-core/tokens";

const theme = getPremiumTheme(slug) ?? defaultTheme;

Branded preset catalog (71 themes)

ts
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`.

tsx
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 (with SearchThemesInput)

ts
import { searchThemes, type SearchThemesInput } from "@hex-core/themes";

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. `SearchThemesInput` is exported alongside `searchThemes`, so you can type a filter object you assemble elsewhere (`const filter: SearchThemesInput = { ... }`).

tsx
import { searchThemes } from "@hex-core/themes";

const fintech = searchThemes({ category: "fintech" });
// → [binanceTheme, coinbaseTheme, krakenTheme, mastercardTheme, ...]

const dark = searchThemes({ query: "dark", tags: ["minimal"] });

extendTheme

ts
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.

tsx
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

ts
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.

tsx
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

ts
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.

tsx
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, tesla

ThemeOverrides type

ts
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.

tsx
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.

tsx
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.

tsx
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.14.0 · @hex-core/tokens@1.3.7 · @hex-core/themes@0.2.3 · @hex-core/registry@0.6.0 · @hex-core/payload@0.4.1