Prodular UI

Theming

Ten themes, each a full personality: brand color, tinted grays, corner radius, typeface and the colors that mark AI output. Every one passes WCAG AA in light and dark.

Themes

GraphiteMonochrome and exact. The default.
Geist
AI summary
CobaltConfident blue for B2B tools.
Inter
AI summary
IrisViolet, the native color of AI products.
Geist
AI summary
PlumMagenta with a creative edge.
Figtree
AI summary
RoseWarm red for consumer and health.
Manrope
AI summary
EmberEnergetic orange for growth teams.
Instrument Sans
AI summary
SaffronGolden yellow with dark ink on solids.
Instrument Sans
AI summary
MossCalm green for finance and climate.
IBM Plex Sans
AI summary
VerdigrisTeal with a quiet, technical tone.
IBM Plex Sans
AI summary
ClayMuted rust on sand. Editorial and warm.
Inter
AI summary

Five switches

Theme, mode, radius, density and font are independent. Set them as attributes on any element. Most apps set them once on html.

<html data-theme="iris" class="dark" data-radius="lg" data-density="compact" data-font="inter">

Themes nest, so a preview, an embed or a marketing section can use a different theme from the app around it.

<section data-theme="ember">  {/* Everything in here renders in Ember, the rest of the page stays as it is. */}</section>

Density

Density sets control heights, row heights, panel padding, icon sizes and the small type sizes, all in whole pixels, so borders stay sharp. The 4px spacing unit never changes. Use dense for workstation screens, compact for busy SaaS screens and comfortable for sparse ones. Like the other switches it works on any element, so one panel can be denser than the page.

DensityControlRowLabel textBody textPanel padding
dense24px24px11px12px12px
compact28px28px12px13px16px
default32px32px13px14px20px
comfortable36px36px13px14px24px

Components read --control-md, --row-height, --icon-size and --text-ui. Use them in your own controls the same way: h-(--control-md), h-(--row-height). For metadata below the body size use text-2xs (11px), never smaller.

Tokens

Components only use semantic tokens. Each one maps to a Tailwind utility (bg-card, text-muted-foreground) and to a Figma variable with the same name.

Surfaces

--backgroundbg-background
--cardbg-card
--popoverbg-popover
--mutedbg-muted
--accentbg-accent
--secondarybg-secondary

Text

--foregroundtext-foreground
--muted-foregroundtext-muted-foreground
--brand-subtle-foregroundtext-brand-subtle-foreground

Brand

--primarybg-primary
--primary-hoverbg-primary-hover
--primary-foregroundtext-primary-foreground
--brand-subtlebg-brand-subtle
--ringborder-ring

Borders

--borderborder-border
--border-strongborder-border-strong
--inputborder-input

Status

--successbg-success
--warningbg-warning
--infobg-info
--destructivebg-destructive
--success-subtlebg-success-subtle
--warning-subtlebg-warning-subtle
--info-subtlebg-info-subtle
--destructive-subtlebg-destructive-subtle

AI

--ai-1bg-ai-1
--ai-2bg-ai-2
--ai-3bg-ai-3
--ai-subtlebg-ai-subtle
--ai-borderborder-ai-border
--ai-foregroundtext-ai-foreground

Colors for canvas, maps and charts

Tokens are OKLCH in CSS. Renderers that take color strings instead of CSS variables, such as canvas, WebGL, MapLibre and D3, read them resolved to sRGB with tokenColor. It follows the theme, mode and any scoped data-theme, and returns null during server rendering, so draw in an effect.

import { tokenColor, useTokenColors } from "@/lib/token-color"
// Once, for example in an event handler:const ink = tokenColor("chart-1")              // "#2563eb" in this theme
// In React, updated when the theme or mode changes:const colors = useTokenColors(["chart-1", "severity-high", "border"])useEffect(() => {  if (colors) map.setPaintProperty("sites", "circle-color", colors["chart-1"])}, [colors])

Severity has its own scale (severity-critical, -high, -elevated, -routine), apart from status and chart colors. tertiary-foreground is for metadata that should recede further than muted text and still reads at 4.5:1.

Make your own theme

Themes are generated from palette definitions in OKLCH. Add an entry, run npm run tokens, and the generator builds both modes, tunes lightness until every text pair passes contrast, and fails the build if it cannot.

// tokens/themes.ts{  name: "harbor",  label: "Harbor",  description: "Deep teal for logistics.",  brand: "verdigris",  gray: "slate",  radius: "md",  font: "inter",  ai: ["verdigris", "sky", "iris"],  chart: ["sky", "moss", "ember", "plum"],}

Starting from a brand color or a logo? Create your own theme in the browser. The generator runs the same code, shows the result in light and dark, and exports the CSS, the Figma tokens and the entry above.