Plane's Tailwind CSS Configuration and Theming Strategies: The Complete Guide

Plane centralizes its Tailwind CSS configuration in a dedicated workspace package and implements theming through CSS custom properties with Tailwind 4's @custom-variant and @theme directives, enabling instant runtime theme switching without rebuilds.

Plane is an open-source project management platform built with a monorepo architecture that demands consistent styling across multiple packages. The project implements a sophisticated Tailwind CSS configuration and theming strategy that leverages CSS variables and Tailwind 4 features to support light, dark, and high-contrast modes from a single source of truth. This approach eliminates the need for separate theme builds while maintaining strict design token consistency across the entire UI layer.

Centralized Tailwind Configuration as a Workspace Package

Plane manages its styling infrastructure through a dedicated package called @plane/tailwind-config located at packages/tailwind-config. This package acts as the single source of truth for all Tailwind-related configuration, ensuring that every consumer uses identical PostCSS plugins, base styles, and utility classes.

The package exports three critical assets through its package.json:

Other packages in the monorepo, such as the UI package at packages/ui, consume this configuration via a workspace alias:

// packages/ui/package.json
{
  "dependencies": {
    "@plane/tailwind-config": "workspace:*"
  }
}

The UI layer imports the shared stylesheet directly, guaranteeing that every component builds with the same Tailwind core:

/* packages/ui/styles/globals.css */
@import "@plane/tailwind-config/index.css";

PostCSS configuration is similarly centralized. The UI package forwards its PostCSS processing to the shared configuration:

// packages/ui/postcss.config.js
import postcssConfig from "@plane/tailwind-config/postcss.config.js";
export default postcssConfig;

The shared PostCSS configuration at packages/tailwind-config/postcss.config.js wires the Tailwind plugins:

// packages/tailwind-config/postcss.config.js
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

CSS-Variable-Driven Theming Strategy

Plane's theming system is purely CSS-variable based, eliminating the need for separate Tailwind builds per theme. All design tokens are defined in packages/tailwind-config/variables.css using Tailwind 4's experimental directives.

Custom Variants for Theme Detection

The configuration defines custom variants that map data-theme attributes to CSS selectors. These variants enable Tailwind to generate utilities that only apply under specific theme conditions:

/* packages/tailwind-config/variables.css */
@custom-variant dark (&:where([data-theme*="dark"], [data-theme*="dark"] *));
@custom-variant dark-high-contrast (&:where([data-theme="dark-contrast"], [data-theme="dark-contrast"] *));
@custom-variant light-high-contrast (&:where([data-theme="light-contrast"], [data-theme="light-contrast"] *));

These selectors target any ancestor element (typically <html> or <body>) carrying a data-theme attribute, allowing Tailwind to scope styles precisely.

Token Architecture in variables.css

The file organizes tokens into layered blocks. First, the @layer base defines default light-mode values using OKLCH color space for perceptually uniform colors:

/* Light mode (default) */
@layer base {
  :root, :host {
    --neutral-100: oklch(0.9848 0.0003 230.66);
    --brand-500: oklch(0.4511 0.087024 237.37);
    --bg-accent-primary: var(--brand-default);
    /* … additional tokens … */
  }
}

Dark mode and high-contrast variants override these values within @variant blocks:

/* Dark mode overrides */
@variant dark {
  --neutral-100: oklch(0.1932 0.002 230.81);
  --brand-500: oklch(0.4511 0.087024 237.37);
  --bg-accent-primary: var(--brand-default);
  /* … dark palette values … */
}

Semantic Design Token Mapping

After defining the raw color variables, a @theme block exposes semantic tokens that Tailwind utilities can reference directly:

/* packages/tailwind-config/variables.css */
@theme inline {
  --background-color-accent-primary: var(--bg-accent-primary);
  --text-color-primary: var(--txt-primary);
  --border-color-subtle: var(--border-subtle);
  /* … semantic mappings … */
}

This architecture allows components to use intuitive class names like bg-accent-primary, text-primary, and border-subtle. Because these map to CSS variables rather than static values, the visual appearance updates instantly when the underlying variable values change.

Runtime Theme Switching Without Rebuilds

Changing themes in Plane requires no JavaScript state management for styling logic. The system relies entirely on the data-theme attribute:

<body data-theme="dark"> … </body>

For high-contrast accessibility modes:

<body data-theme="dark-contrast"> … </body>

When the data-theme attribute updates, the @custom-variant selectors automatically match the new state, causing the CSS variables to recalculate. The UI repaints with the appropriate palette immediately, with zero runtime overhead or rebuild requirements.

Implementation Examples

Consuming the Shared Configuration

Application entry points import the centralized stylesheet to ensure Tailwind utilities are available:

// src/main.tsx (UI entry point)
import "@plane/tailwind-config/index.css";
import "./styles/globals.css";   // pulls the same import again, safe

Using Themed Utilities in Components

Components reference semantic tokens through standard Tailwind class names:

// packages/ui/src/components/Button.tsx
export const Button = () => (
  <button className="px-4 py-2 rounded-md bg-accent-primary text-primary hover:bg-accent-primary-hover">
    Save
  </button>
);

The classes bg-accent-primary and text-primary resolve to the CSS variables defined in variables.css, automatically adapting to the current theme.

Implementing Theme Controls

A simple hook updates the theme by manipulating the data-theme attribute:

// packages/ui/src/hooks/useTheme.ts
export const useTheme = () => {
  const setTheme = (theme: "light" | "dark" | "dark-contrast" | "light-contrast") => {
    document.documentElement.dataset.theme = theme;
  };
  return { setTheme };
};

// usage
const { setTheme } = useTheme();
<button onClick={() => setTheme("dark")}>Dark mode</button>

When setTheme("dark") executes, the @custom-variant dark selectors activate, and all components instantly adopt the dark palette through CSS variable recalculation.

Summary

  • Centralized configuration lives in packages/tailwind-config and is consumed via workspace aliases, ensuring every package uses identical Tailwind and PostCSS settings.
  • CSS-variable architecture stores all design tokens in variables.css using OKLCH color values for consistent perceptual uniformity across themes.
  • Tailwind 4 directives including @custom-variant and @theme enable semantic token mapping and variant-based theme detection without JavaScript overhead.
  • Runtime switching occurs through the data-theme attribute, triggering immediate visual updates via CSS variable recalculation with no rebuild required.
  • Zero-runtime-cost theming means theme changes happen entirely in the browser's CSS engine, eliminating flash-of-unstyled-content or JavaScript-based style injection.

Frequently Asked Questions

Where is Plane's Tailwind CSS configuration located?

Plane's Tailwind CSS configuration is centralized in packages/tailwind-config within the monorepo. This package exports tailwind.config.js, index.css, and postcss.config.js, which are consumed by other packages like packages/ui through workspace dependencies (workspace:*). The configuration uses Tailwind 4's @tailwindcss/postcss plugin as defined in packages/tailwind-config/postcss.config.js.

How does Plane handle dark mode and high-contrast themes?

Plane implements dark mode and high-contrast themes through CSS custom properties rather than separate Tailwind theme objects. The system uses @custom-variant directives in packages/tailwind-config/variables.css to define selectors that target data-theme attributes (such as data-theme="dark" or data-theme="dark-contrast"). Color values are defined in @layer base for light mode and overridden in @variant dark and @variant dark-high-contrast blocks, allowing the browser to switch themes instantly by updating CSS variables.

What is the purpose of the @theme directive in Plane's setup?

The @theme directive in packages/tailwind-config/variables.css maps low-level CSS variables (like --bg-accent-primary) to semantic design tokens that Tailwind utilities can reference. This allows developers to use intuitive class names such as bg-accent-primary or text-primary while the underlying values remain dynamic CSS variables. When the data-theme attribute changes, these variables update automatically, causing all themed components to repaint with the correct colors without requiring class name changes or React re-renders for styling purposes.

Can I use Plane's theming strategy in my own React project?

Yes, you can adapt Plane's theming strategy to any React project using Tailwind 4. The key requirements are: creating a CSS file with @custom-variant rules that target data-theme attributes, defining color tokens as CSS variables in @layer base with variant-specific overrides, and using @theme to expose semantic tokens to Tailwind's utility classes. Your theme switching logic only needs to update the data-theme attribute on a root element (like <html>), and Tailwind's generated selectors will handle the rest through CSS variable scoping.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →