zynactradocs

Themes

Six complete personalities, each in light and dark. Pick one at install time, or let people switch.

A theme changes everything about how the components look: colors, fonts, corner radius, shadows, spacing, how buttons and inputs are shaped, and whether presses ripple. The components and their APIs never change, so an app can switch themes without touching its code.

Click a card, or use the switcher in the sidebar. The whole site, every preview included, follows.

Create project
You can change everything except the slug later.
OwnerNewLivePendingv2.4.0
Build minutes412 / 600
x

How a theme is built

Every theme is three layers:

LayerLives inWhat it decides
Tokenspackages/tokens/src/themes/<name>.tsColors (light and dark), fonts, base radius, the three elevation shadows, the brand gradient, ripple on or off
Structurepackages/ui/src/themes/<name>/structure.cssComponent shapes and spacing. Generated from one of shadcn's styles (Vega, Maia, Lyra, Luma, Sera...), except Zynactra, which is hand-tuned
Personalitypackages/ui/src/themes/<name>/personality.cssA few hand-written touches: gradient CTAs for Sunset, serif titles for Atelier, mono labels for Volt

A theme can also wear the Zynactra signature, the motion and detail pack, with "signature": true in registry.json. Zynactra does; the others don't.

ThemeStructureFeels like
ZynactraHand-tuned (from Nova)Square, warm, flat, precise
SunsetMaia: pill shapes, generous spacingZynactra's original website
HarborVega: the classic shadcn lookCalm, professional SaaS
GroveLuma: very round, soft shadowsFriendly consumer app
AtelierSera: tracked capitals, underline fieldsEditorial, premium
VoltLyra: sharp, boxyDeveloper tooling

Choosing themes for an app

Themes are plain CSS. Import the ones the app uses; nothing else is shipped.

app/globals.css
/* One theme: */
@import "@zynactra/ui/core.css";
@import "@zynactra/ui/themes/harbor.css";
app/globals.css
/* Zynactra by default, plus the themes people can switch to: */
@import "@zynactra/ui/styles.css";          /* core + Zynactra */
@import "@zynactra/ui/themes/grove.css";
@import "@zynactra/ui/themes/atelier.css";

Use @zynactra/ui/themes/all.css to include every theme.

Setting the theme

Fixed theme. Set it on <html> and you're done:

<html data-theme="harbor">

Switchable theme. Add the script (it applies the saved theme before the first paint, so nothing flashes) and the provider:

app/layout.tsx
import { ZynactraThemeProvider, ZynactraThemeScript } from "@zynactra/ui/theme"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <ZynactraThemeScript defaultTheme="harbor" themes={["harbor", "grove"]} />
      </head>
      <body>
        <ZynactraThemeProvider defaultTheme="harbor" themes={["harbor", "grove"]}>
          {children}
        </ZynactraThemeProvider>
      </body>
    </html>
  )
}
theme-picker.tsx
"use client"
import { useZynactraTheme } from "@zynactra/ui/theme"

export function ThemePicker() {
  const { theme, setTheme, themes } = useZynactraTheme()
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      {themes.map((t) => (
        <option key={t.name} value={t.name}>{t.label}</option>
      ))}
    </select>
  )
}

Light and dark

Mode is separate from theme: every theme has both. Add the dark class (or data-mode="dark") to <html>. next-themes with attribute="class" handles that, including following the system setting.

Fonts

Each theme names its fonts; the app loads them. Self-host with Fontsource:

ThemePackages
Zynactra, Sunset@fontsource/instrument-sans, @fontsource/jetbrains-mono
Harbor@fontsource-variable/inter
Grove@fontsource-variable/plus-jakarta-sans
Atelier@fontsource-variable/fraunces, @fontsource-variable/inter
Volt@fontsource-variable/space-grotesk, @fontsource/jetbrains-mono

Adding a theme

pnpm theme:new ember --from harbor --structure mira --label "Ember"
  1. That copies Harbor's tokens to packages/tokens/src/themes/ember.ts and registers the theme. Edit its colors for light and dark, fonts, radius, shadows and ripple, and write its one-line description in registry.json.
  2. pnpm sync:upstream generates its structure from the shadcn style you picked, a personality stub and its CSS entry point.
  3. Add character in packages/ui/src/themes/ember/personality.css if the structure alone isn't enough. Set "signature": true in registry.json if it should move like Zynactra.
  4. pnpm check, then select it in the docs switcher and check every page in light and dark.

Structures to start from: vega (classic), nova (compact), maia (soft, pill), lyra (sharp, boxy), mira (dense), luma (very round), sera (editorial), rhea (rounded, soft cards).

Contrast is your job: body text needs 4.5:1 on its background, and text on brand fills needs 4.5:1 against every color of the brand-fill gradient.

Edit on GitHub

On this page