@xata.io/theme
v1.0.10
Published
Readme
@xata.io/theme
The design system package for the monorepo. This package serves as the source of truth for all design tokens, color palettes, and Tailwind CSS configuration used across applications.
Overview
This package generates a theme CSS file that includes:
- Design tokens (colors, spacing, typography)
- Color palettes in OKLCH color space
- Tailwind CSS custom properties
- Light and dark theme support
- Animation keyframes
- Code syntax highlighting colors
Architecture
Core Files
- src/color-tokens.ts - Defines all color tokens for light and dark themes
- src/theme.css - Generated CSS file (DO NOT edit manually!)
- scripts/generate-theme.ts - Script that generates the theme CSS file
- scripts/check-generate-theme.ts - Validates that the generated file is up to date
Exports
import '@xata.io/theme'; // Imports generated theme.css
import '@xata.io/theme/theme.css'; // Explicit CSS import
import { backgroundColors, textColors, chartColors, borderColors, ringColors } from '@xata.io/theme';
import { typographyTokens } from '@xata.io/theme';
import { parseColorHex } from '@xata.io/theme'; // Converts an OKLCH (or any chroma-js-parsable) color to hexpurplePaletteOklch, grayPaletteOklch, and colorTokensOklch are defined in src/color-tokens.ts but are internal to the package's build script (scripts/generate-theme.ts) — they are not part of the public export surface, since no app consumes them directly.
Color System
Color Palettes
The theme uses two main color palettes:
Purple Palette
The purple palette is generated from three key colors using bezier interpolation:
- Input colors:
#f5f3ff,#8566ff,#504297 - Generated using https://gka.github.io/palettes/
- Available shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900
Gray Palette
The gray palette uses Tailwind's Zinc color scale:
- Available shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950
Color Tokens
Color tokens are defined in src/color-tokens.ts and organized into categories:
- Background colors:
background,card,popover,primary,secondary,muted,accent,destructive - Text colors:
foreground,card-foreground,muted-foreground,link,success,danger,warning - Border colors:
border,input,ring - Chart colors:
chart-1throughchart-5 - Sidebar colors:
sidebar-background,sidebar-foreground, etc. - Code syntax colors:
code-text,code-bg,code-text-accent, etc.
Dark & Light Themes
We aim to fully support both light and dark themes. For this reason, each token has both light and dark theme variants.
Marketing Scope and Surfaces
The marketing site is dark-only. Add data-marketing to a dark-themed container to apply the marketing overrides: a gray-900 background, alpha-based secondary-foreground and muted-foreground, --heading-weight: 400 and the illustration-* colors.
Individual sections can switch palette with data-surface:
light: a light island inside the dark pagebrand: brand background with brand foregroundmuted: muted backgroundfooter: card background with a stronger bordersubtle/comparison: a 3% tint over the current background
Marketing utilities: border-hairline (0.5px on 2x displays, 1px otherwise), bg-lines-<gap> (with /x or /y for direction), bg-grid-<gap> and font-pixel (expects --font-geist-pixel-line from the app layout).
OKLCH Color Space
Color tokens are authored directly in OKLCH — it is the source-of-truth format, not a conversion target. OKLCH was chosen for better perceptual uniformity:
- L (Lightness): 0-1 (0% to 100%)
- C (Chroma): 0+ (color intensity)
- H (Hue): 0-360 (color angle)
OKLCH provides more predictable color interpolation and better accessibility compared to RGB/HEX. Read more in the Understanding OKLCH section.
Typography
The design system uses a carefully selected font stack for different purposes:
Font Families
- Geist Sans (
--font-geist-sans,--font-regular) - Primary typeface for body text and UI elements - Geist Mono (
--font-geist-mono,--font-code,--font-mono) - Monospace font for code blocks and technical content - Geist Sans (
--font-heading) - Display font for headings and emphasis (configurable per app, normally we use Geist Sans)
Font Variables
The theme defines the following CSS custom properties for typography:
--font-mono: var(--font-code), monospace;
--font-code: var(--font-geist-mono);
--font-regular: var(--font-geist-sans);
--font-heading: var(--font-heading);Usage in Applications
Applications load fonts using Next.js font optimization and assign them to CSS variables. Example from an app's layout:
import { GeistSans } from 'geist/font/sans';
import { Geist_Mono, Unbounded } from 'next/font/google';
const geistMono = Geist_Mono({
variable: '--font-code',
subsets: ['latin']
});
const heading = Unbounded({
subsets: ['latin'],
weight: ['400', '500', '700'],
variable: '--font-heading'
});
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={`${GeistSans.className} ${geistMono.variable} ${heading.variable} font-regular`}>
{children}
</body>
</html>
);
}Tailwind Classes
Use these utility classes for typography:
font-regular- Apply Geist Sans (body text)font-mono- Apply Geist Mono (code)font-heading- Apply the heading font (Unbounded or custom)
Typography Plugin
The theme includes @tailwindcss/typography plugin for rich text content styling. Use the prose class for formatted content:
<article className="prose dark:prose-invert">
{/* Markdown or rich text content */}
</article>The typography plugin automatically styles:
- Headings (h1-h6)
- Paragraphs and lists
- Links
- Code blocks
- Blockquotes
- Tables
Font Loading
Fonts are loaded at the application level, not in the theme package. Each app is responsible for:
- Importing font packages (Geist from npm, others from Google Fonts)
- Configuring font variables
- Applying the font classes to the root layout
This approach allows:
- Apps to customize heading fonts while sharing body/code fonts
- Optimal font loading with Next.js automatic optimization
- Flexibility for different use cases across the monorepo
How It Works
Generation Process
- Color tokens are defined directly in OKLCH in TypeScript (src/color-tokens.ts)
- The
generate-theme.tsscript reads these tokens - CSS custom properties are generated for both light and dark themes
- The output is written to src/theme.css
- Biome formats the generated CSS file
Theme Structure
The generated theme.css file contains:
@theme {
/* Direct palette definitions */
--color-purple-500: oklch(0.602 0.208 287.67);
--color-gray-900: oklch(21% 0.006 285.885);
}
@theme inline {
/* Tailwind theme tokens that map to CSS variables */
--color-background: var(--background);
--color-primary: var(--primary);
/* ... */
}
@layer base {
:root {
/* Light theme token values */
--background: oklch(0.985 0 0);
--primary: oklch(0.562 0.19 287.499);
/* ... */
}
.dark {
/* Dark theme token values */
--background: oklch(14.1% 0.005 285.823);
--primary: oklch(0.562 0.19 287.499);
/* ... */
}
[data-marketing] {
/* Marketing overrides, applied on top of .dark */
}
[data-surface='light'] {
/* Section-level token sets */
}
}The color mappings use @theme inline, so utilities such as bg-background compile to var(--background) and resolve on the element they are used on. Overriding a token on any element (.dark, [data-marketing], [data-surface]) therefore restyles every utility inside it.
Usage
Generating the Theme
To regenerate the theme after modifying color tokens:
pnpm --filter @xata.io/theme run generateThis command:
- Runs scripts/generate-theme.ts
- Writes the OKLCH color tokens as CSS custom properties
- Generates src/theme.css
- Formats the output with Biome
Checking Theme Integrity
To verify the generated theme matches the source tokens:
pnpm --filter @xata.io/theme run check-generate-themeThis script:
- Regenerates the theme file
- Compares it with the committed version
- Exits with error if they don't match
This is typically run in CI to ensure changes to color tokens are properly generated.
Editing the Theme
We aim to keep the theme consistent and simple across applications. Only modify the theme when necessary and follow these guidelines:
Adding or Modifying Color Tokens
❕ Note : Always run the generate script after modifying tokens to update the CSS file.
- Edit src/color-tokens.ts
- Modify the
colorTokensOklchobject with your changes. Tokens are OKLCH literals — if you're starting from a hex value (e.g. picked in Figma), convert it first withchroma('#hex').oklch()(chroma-js) or any OKLCH converter, then paste the literal in:
export const colorTokensOklch = {
root: {
// Add or modify light theme colors
'new-token': 'oklch(0.7 0.15 30)',
primary: purplePaletteOklch[600],
},
dark: {
// Add or modify dark theme colors
'new-token': 'oklch(0.75 0.18 150)',
primary: purplePaletteOklch[600],
}
};- Regenerate the theme:
pnpm --filter @xata.io/theme run generate- The new token will be available as
--color-new-tokenin CSS andbg-new-token/text-new-tokenin Tailwind
Modifying Color Palettes
⚠️ Avoid changing palettes unless necessary to maintain design consistency across products.
Purple Palette
To change the purple palette, edit purplePaletteOklch in src/color-tokens.ts:
export const purplePaletteOklch = {
'50': 'oklch(0.969 0.016 293.847)',
'100': 'oklch(0.890 0.059 293.718)',
// ... modify shades
'900': 'oklch(0.440 0.134 287.123)'
};For a completely new palette, generate hex shades at https://gka.github.io/palettes/, then convert each to OKLCH (e.g. chroma('#hex').oklch()) before pasting them in.
Gray Palette
The gray palette uses Tailwind's Zinc color scale (already expressed as OKLCH strings by Tailwind). To use a different gray scale, edit grayPaletteOklch in src/color-tokens.ts, pulling from tailwindcss/colors and converting to hex/oklch as needed for shades not already in OKLCH:
import colors from 'tailwindcss/colors';
export const grayPaletteOklch = {
'50': colors.slate[50], // Change from zinc to slate
'100': colors.slate[100],
// ... rest of shades
};Adding New Color Categories
To add a new semantic color category (like status colors):
- Define the color in src/color-tokens.ts. Values can be OKLCH literals, or reused directly from
tailwindcss/colors(Tailwind's palette is already expressed in OKLCH):
import colors from 'tailwindcss/colors';
const colorTokensOklch = {
root: {
// ... existing tokens
'status-online': colors.green[500],
'status-offline': 'oklch(0.552 0.016 285.938)',
},
dark: {
// ... existing tokens
'status-online': colors.green[400],
'status-offline': 'oklch(0.442 0.017 285.786)',
}
};- Regenerate the theme:
pnpm --filter @xata.io/theme run generate- Use in your application:
<div className="bg-status-online text-white">
User is online
</div>Understanding OKLCH
OKLCH (Lightness, Chroma, Hue) is a perceptually uniform color space:
- Lightness (0-1): How light or dark a color appears
- Chroma (0+): Color intensity (0 = grayscale, higher = more vivid)
- Hue (0-360): Color angle (0/360 = red, 120 = green, 240 = blue)
Why OKLCH?
- Perceptual uniformity: Equal numerical changes = equal perceived changes
- Better accessibility: Easier to maintain consistent contrast ratios
- Predictable lightness: L=0.5 looks "50% light" across all hues
- Smooth gradients: Interpolation produces natural-looking color transitions
Hex Conversion
Tokens are authored in OKLCH, but some consumers (e.g. the Monaco editor theme, which requires hex/rgb) need hex values. src/utils/parse-color.ts exports parseColorHex for that:
import { parseColorHex } from '@xata.io/theme';
parseColorHex('oklch(0.602 0.208 287.67)'); // "#8566ff"Figma Export
This package remains the single source of truth for color tokens. generate-figma-tokens produces a one-way, on-demand export for pulling the current token values into Figma — it is not a live sync.
pnpm --filter @xata.io/theme run generate-figma-tokensThis writes one file per mode to packages/theme/figma-tokens/ (gitignored, regenerated on demand):
light.tokens.json,dark.tokens.json,purple.tokens.json,gray.tokens.json
Each file matches the schema Figma's native "Export mode" produces, so it can be imported via Figma's native Import mode on a variable collection's mode column (not a third-party plugin). Since these are freshly generated files without existing com.figma.variableIds, importing creates new variables in the target collection/mode rather than updating existing ones.
Scripts
generate- Generates the theme CSS file from color tokenscheck-generate-theme- Validates that the generated theme is up to dategenerate-figma-tokens- Exports color tokens as Figma-native variable mode JSON files (see Figma Export)tsc- Type checks the package using the native TypeScript compiler
Dependencies
tailwindcss- CSS framework@tailwindcss/typography- Typography plugintailwindcss-animate- Animation pluginchroma-js- Color conversion and manipulation
Best Practices
- Always run
generateafter modifying color tokens - Never edit src/theme.css directly - it will be overwritten
- Use semantic token names (e.g.,
primary,danger) instead of specific colors (e.g.,purple-500,red-600) - Test both light and dark themes when adding new colors
- Maintain sufficient contrast ratios for accessibility (WCAG AA minimum: 4.5:1 for text)
- Keep color palettes consistent by using palette generators
- Document the purpose of new color tokens in code comments
- Before adding new colors, check if existing tokens can be reused or if it fits within existing palettes.
