Skip to main content

Theme System

The frontend uses a semantic design system built on Tailwind CSS v4 and CSS custom properties. Components receive styling through semantic IDs rather than explicit color props, and all colors adapt to the user's selected theme.

Color Architecture

Each theme defines 6 semantic colors:

TokenCSS VariablePurpose
Primary--primaryMain actions, focus states, active selections
Secondary--secondaryHover states, accents
Tertiary--tertiaryMuted elements, icons
Light--lightBorders, dividers
Background--backgroundBackgrounds, disabled states
Text Dark--text-darkText content

21 pre-built themes are available in lib/config/themes.ts, including Metallic Chic (default), Ocean Blue, Forest Green, Royal Purple, Ruby Red, Sunset Orange, Slate Gray, and more.

CSS Variable Usage

Colors are referenced via CSS variables in both inline styles and Tailwind arbitrary values:

/* Direct CSS */
border-color: var(--light);
color: var(--text-dark);

/* Tailwind arbitrary values */
className="border-[var(--primary)] text-[var(--text-dark)]"

The STYLE_VARIABLES object in components/ux/TWSharedStyles.tsx provides pre-built Tailwind class combinations:

colors.borderDefault    // "border-[var(--light)]"
colors.borderFocus // "focus-within:border-[var(--primary)]"
colors.textDefault // "text-[var(--text-dark)]"
colors.textSelected // "text-[var(--primary)]"

Dark / Light Mode

Dark mode uses a data-attribute selector configured in Tailwind v4:

@variant dark (&:where([data-theme="dark"]))

NextThemesProvider manages the data-theme attribute. User theme preference (themeId) is stored in the user profile in the database. On first visit, the system defaults to light.

Semantic ID Mapping

Components resolve their styling through semantic IDs defined in lib/theme/designTokens.ts:

Buttons

Style KeyAppearance
positive-primaryFilled with theme primary color
positive-strokeOutlined with theme border
positive-textGhost / text-only
negative-primaryDestructive (always red)

Mapped IDs: save --> positive-primary, cancel --> positive-stroke, delete --> negative-primary (30+ IDs total).

Inputs

Style KeyAppearance
standardDefault borders with theme focus ring
searchEmphasized focus for search fields
Style KeyAppearance
primaryAction links (theme primary)
navigationMenu links (text-dark, primary on hover)
inlineAlways underlined content links
subtleLow-emphasis footer links

TW* Component Integration

Custom TW* components (TWButton, TWModal, TWInput, TWSelect, etc.) use the aiqlick plugin prefix. Theme colors flow into TW* components via CSS custom properties so that buttons, selects, and inputs receive consistent styling without explicit color props.

Adding a New Theme

Add an entry to lib/config/themes.ts:

"my-theme": {
id: "my-theme",
name: "My Theme",
colors: {
primary: "#FF6B6B",
secondary: "#4ECDC4",
tertiary: "#95E1D3",
light: "#F7F7F7",
background: "#FAFAFA",
textDark: "#2C3E50",
},
}

All components update automatically -- no per-component changes needed.

info

The interactive theme preview is available at /devtools/colors in development mode. It shows all 21 themes with live button, input, and link examples.