Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/theme

UI

Framework-agnostic design tokens and theme presets for the Night Owls chat/agent UI.

What it does

@nightowlsdev/theme is the pure, dependency-free design-token layer underneath the Night Owls UI. It ships a set of named themes (ink/Vercel, owl/classic, mono/ChatGPT, warm, midnight, paper, 8-bit, tui, and more), each defining light/dark colors, radius, density, and font stacks (the package ships no fonts, only safe fallbacks). Use getTheme/resolveTheme to resolve a theme to CSS variables, defineTheme/extendTheme to author or override one, and the accent/theme presets plus font-scale helpers to tune accent color and density. It has zero React or @mastra dependency, so it can be consumed by any renderer; @nightowlsdev/react re-exports the whole API for convenience.

Install

pnpm add @nightowlsdev/theme

Key exports

  • themes / themeNames / DEFAULT_THEME / getTheme
  • defineTheme / extendTheme
  • resolveTheme / resolveThemeName
  • accentPresets / themePresets / applyPreset
  • fontSizes / FONT_SCALES / resolveFontScale
  • built-in themes: ink, owl, mono, warm, midnight, compact, paper, eightbit, tui, nightowls
  • types: Theme, ThemeColors, ThemeFont, ThemeMode, ThemeVars, Density, FontSize

Usage

theme.ts
import { getTheme, resolveTheme, themeNames } from "@nightowlsdev/theme";

// Resolve a built-in theme to CSS variables for a given mode.
const vars = resolveTheme(getTheme("nightowls"), "dark");

console.log(themeNames); // ["ink", "owl", "mono", "warm", ... "nightowls"]

What it provides

theme is the pure, dependency-free design-token layer underneath the whole Night Owls UI. A Theme is plain data — light colors, optional dark overrides, radius, density, and font stacks — and resolveTheme turns it into the flat --owl-* CSS-variable map that any renderer applies on a root element. It ships nine named themes (ink, owl, mono, warm, midnight, compact, paper, 8bit, tui), authoring helpers (defineTheme / extendTheme), a curated accent-preset system, and font-scale helpers, with zero React and zero @mastra.

When to use it

  • You want the Night Owls chat/agent look-and-feel's design tokens in a non-React context — a server-rendered stylesheet, a plain CSS build, another framework.
  • You want to author a brand theme, or personalize a built-in one (one-click accent presets), and resolve it to CSS variables.
  • You are building a bespoke renderer and need the same --owl-* contract the built-in components read, so your UI and the chat share one theme.

When not to

  • You are already using @nightowlsdev/react or @nightowlsdev/ui — both re-export / apply this API, so you rarely import theme directly.
  • You need actual components (a provider, buttons) rather than tokens — reach for @nightowlsdev/ui (chrome) or @nightowlsdev/react (chat).

Alternatives

  • @nightowlsdev/ui (ThemeProvider)You want a React provider that resolves a theme, applies the --owl-* vars + a data-theme attribute on a root element, and persists the user's light/dark/system choice for you.
  • @nightowlsdev/reactYou are inside the chat UI — it re-exports the entire theme API, so a single import surface covers both.
  • Hand-rolled CSS variables / Tailwind configYou don't want the token contract at all. You lose the named themes, the light/dark merge, and the accent-preset system.

Strengths

  • Zero dependencies — no React, no @mastra — so any renderer or plain CSS can consume it.
  • Pure functions: resolveTheme is DOM-free and deterministic, so it is SSR-safe and trivially testable.
  • A theme is plain data; extendTheme / applyPreset personalize in one call while keeping identity (radius, density, font, and the theme name that keys theme-scoped CSS all survive).
  • Ships safe font FALLBACKS only, no bundled webfonts — no weight, no FOUT surprises; you supply the actual @font-face.

Limits & trade-offs

  • Tokens only: it does not touch the DOM. Something (an adapter, or you) must put the resolved --owl-* vars on a root element.
  • The token vocabulary is fixed — the semantic --owl-* roles (bg/surface/border/text/muted/accent…). A design that needs different roles has to layer its own vars on top.
  • Ships no fonts. The stacks end in a safe generic; matching the intended face is on the host.

How it works

A Theme carries light colors, an optional dark partial merged over them, a radius, a density, and font stacks. resolveTheme(theme, mode) merges dark over light for the chosen mode and emits the flat { '--owl-bg': …, '--owl-accent': …, '--owl-radius': … } map (deriving accentSoft via color-mix and defaulting accentText when omitted). defineTheme type-checks a literal; extendTheme deep-merges overrides to derive a new theme. accentPresets + applyPreset recolor only the accent tokens in light and dark, leaving everything else intact. Density and font-size are separate axes (FONT_SCALES / resolveFontScale) an adapter maps to attributes.

Examples

Resolve a built-in theme to CSS variables

getTheme by name, resolveTheme for a mode. themeNames lists the built-ins; DEFAULT_THEME is 'ink'.

theme-example-1.ts
import { getTheme, resolveTheme, themeNames } from "@nightowlsdev/theme";

const vars = resolveTheme(getTheme("midnight")!, "dark");
// -> { "--owl-bg": "…", "--owl-accent": "…", "--owl-radius": "…", … }

console.log(themeNames); // ["ink","owl","mono","warm","midnight","compact","paper","8bit","tui"]

Author a brand theme, then recolor its accent

extendTheme derives from a base by deep-merge; applyPreset swaps only the accent tokens (light + dark) and keeps radius/density/font.

theme-example-2.ts
import { extendTheme, getTheme, applyPreset, accentPresets } from "@nightowlsdev/theme";

const brand = extendTheme(getTheme("ink")!, {
  name: "acme",
  colors: { accent: "#E8A33D", accentHover: "#cf8f2f" },
  radius: "0.75rem",
});

const teal = accentPresets.find((p) => p.name === "teal")!;
const brandTeal = applyPreset(brand, teal); // accent recolored, identity kept

Apply the resolved vars on a root element

Tokens are framework-agnostic: spread the map onto any element's style and children read the --owl-* vars.

theme-example-3.tsx
import { getTheme, resolveTheme } from "@nightowlsdev/theme";

export function Themed({ children }: { children: React.ReactNode }) {
  const vars = resolveTheme(getTheme("owl")!, "light") as React.CSSProperties;
  return <div style={{ ...vars, background: "var(--owl-bg)", color: "var(--owl-text)" }}>{children}</div>;
}

Doing the parts it doesn't support

  • Apply tokens to the DOM for youtheme is pure. Use @nightowlsdev/ui's <ThemeProvider> or @nightowlsdev/react's <SwarmProvider> to resolve + apply the vars and set data-theme, or spread resolveTheme(...) onto a root element's style yourself.
  • Persist a user's light/dark choice / follow the OSNot here — the package is stateless. @nightowlsdev/ui's ThemeProvider does the localStorage persistence and matchMedia 'system' following, SSR-safe.
  • A token role the semantic set doesn't haveresolveTheme returns a plain map — spread your own extra --owl-* (or app-specific) variables alongside it, or set the CSS variable directly on the element. ThemeVars is just Record<string,string>.

Related

  • reactRe-exports this whole API and applies the tokens in the chat UI.
  • uiA React <ThemeProvider> that resolves + applies these tokens and persists the mode.
  • graph-reactAnother surface that reads the same --owl-* custom properties (inline SVG, no CSS build).