Expressive

Getting started

Theming

A theme is every token system combined into one object. Expressive turns it into CSS variables, and every component reads those variables.

Theme from the CLI

Most apps never touch theme code. init writes your theme as CSS variables into your global stylesheet, and expressive theme regenerates it:

globals.css
@import "tailwindcss";
@import "./expressive.css";

/* expressive:theme:start */
:root {
  --ex-color-primary: #65558f;
  --ex-color-on-primary: #ffffff;
  --ex-color-surface-container: #f3edf7;
  --ex-type-title-large-size: 1.375rem;
  --ex-shape-corner-large: 16px;
  --ex-spring-spatial-fast-easing: linear(0, 0.0292, …, 1);
  /* … */
}
.dark {
  --ex-color-primary: #cfbdfe;
  /* … */
}
/* expressive:theme:end */

Everything between the markers is replaced when you run expressive theme. Put your own overrides below the end marker.

createTheme

For full control, build a theme in code. createTheme starts from the defaults and deep-merges whatever you pass:

import { createTheme, themeToCss } from "@/lib/expressive/tokens/theme";

const theme = createTheme({
  colors: { seed: "#0B57D0", variant: "tonal-spot", contrast: 0 },
  typography: { fonts: { brand: "var(--font-display)", plain: "var(--font-sans)" } },
  shape: { corners: { large: 20 } },
  motion: { scheme: "standard" },
  interaction: { focusRing: { width: 2 } },
});

const css = themeToCss(theme); // ":root { --ex-color-primary: … }"
  • colors: a seed with options, or a full scheme you generated yourself.
  • typography, shape, motion, elevation, spacing, interaction: partial overrides of each token system.

Changing the theme at runtime

When the theme depends on something only known while the app runs, like a color the user picks or a per-customer brand, add the theme provider:

npx @grenishrai/expressive add theme-provider
"use client";

import { useMemo, useState } from "react";
import { ThemeProvider } from "@/lib/expressive/theme-provider";
import { createTheme } from "@/lib/expressive/tokens/theme";

export function BrandedApp({ children }: { children: React.ReactNode }) {
  const [seed, setSeed] = useState("#6750A4");
  const theme = useMemo(() => createTheme({ colors: { seed } }), [seed]);
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

Using tokens in your own code

Tokens are Tailwind utilities, so your own components match without extra work:

<section className="rounded-shape-extra-large bg-surface-container p-ex-lg text-on-surface shadow-elevation-1">
  <h2 className="text-headline-small">Recent trips</h2>
  <p className="text-body-medium text-on-surface-variant">Three upcoming</p>
</section>

Or use the variables directly: var(--ex-color-primary), var(--ex-shape-corner-large). See each foundation page for the full list: color, typography, shape, motion.