# How Styling Is Handled in Plane Frontend Applications: The Tailwind-First Architecture

> Discover how Plane frontend apps use a Tailwind CSS-first architecture with a custom cn helper for streamlined, maintainable styling across all applications. Learn more!

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-08-22

---

**Plane frontend applications use a Tailwind CSS-first architecture where utility classes are composed directly in JSX and intelligently merged through a custom `cn` helper powered by `tailwind-merge`, eliminating traditional CSS files and enabling consistent, maintainable styling across the web, admin, and live apps.**

Plane is an open-source project management platform maintained by Makeplane. Understanding how styling is handled in the Plane frontend applications reveals a modern approach that prioritizes developer experience and build-time optimization. The architecture leverages **Tailwind CSS** processed by PostCSS, with a sophisticated class-merging pipeline that ensures utility conflicts are resolved automatically while keeping bundle sizes minimal.

## The Tailwind-First Architecture

Plane’s frontend applications (web, admin, and live) do not rely on custom CSS files. Instead, the codebase adopts a utility-first methodology where styling logic lives alongside component markup.

### Embedding Utilities Directly in JSX

Developers write Tailwind utility strings directly within the `className` prop of React components. This colocation ensures that visual styles are immediately visible when reading component code, eliminating the context-switching required when navigating between separate stylesheet and markup files. The approach requires no runtime CSS-in-JS library overhead, as Tailwind processes classes at build time via PostCSS.

### Intelligent Class Resolution with `cn`

Long strings of utility classes become difficult to manage when handling conditional states like `disabled` or `loading`. To solve this, Plane implements a small wrapper utility called `cn`. This function concatenates multiple class arguments and automatically resolves conflicting utilities (e.g., `p-2 p-4` resolving to the higher specificity value). The `cn` helper is a thin re-export from the shared `@plane/utils` package, working in tandem with **`tailwind-merge`** to deduplicate and merge classes intelligently.

## Core Implementation Files and Utilities

The styling infrastructure is centralized in specific utility files that handle different levels of customization across Plane’s monorepo structure.

### Standard Class Merging in [`packages/ui/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/utils/classname.tsx)

The foundational styling utility resides in [`packages/ui/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/utils/classname.tsx). This file re-exports the `cn` function, making it available for all UI components. Standard components throughout the design system import `{ cn }` from `@plane/ui` to compose class strings that merge base styles with conditional modifiers without manual string concatenation.

### Extended Configuration in [`packages/propel/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/propel/src/utils/classname.tsx)

For applications requiring Plane-specific design tokens, the [`packages/propel/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/propel/src/utils/classname.tsx) file creates a custom Tailwind-merge instance. Using **`extendTailwindMerge`**, this configuration defines custom class groups for proprietary spacing tokens, shadows, or color scales that extend beyond standard Tailwind defaults. This allows the `cn` utility to understand and correctly merge custom utilities like `spacing-1` or `shadow-md` alongside standard Tailwind classes.

## The Four-Step Styling Pipeline

Plane’s styling workflow follows a predictable pipeline from development to production:

1.  **Write Utilities**: Developers add Tailwind utility strings directly in JSX, such as `className="flex items-center gap-2"`, embedding styling logic within component files.
2.  **Concatenate with `cn`**: The `cn` helper combines static and conditional class strings, handling dynamic expressions like `cn("bg-primary", isActive && "text-white")` safely.
3.  **Merge with `tailwind-merge`**: The `cn` function internally invokes `tailwind-merge` (or the extended version from `propel`) to remove duplicate or conflicting utilities intelligently.
4.  **Build Optimization**: PostCSS processes the Tailwind directives at build time, tree-shaking unused utilities and generating a minimal CSS file containing only the classes actually referenced in the source code.

Because unused utilities are stripped during the build, the final CSS bundle remains lightweight regardless of the size of the Tailwind configuration.

## Code Examples in Practice

The following examples demonstrate how Plane components utilize the `cn` utility for real-world styling scenarios:

```tsx
// Simple button using the `cn` helper from @plane/ui
import { cn } from "@plane/ui";

export function PrimaryButton({
  children,
  disabled,
}: { children: React.ReactNode; disabled?: boolean }) {
  return (
    <button
      className={cn(
        "px-4 py-2 rounded-md font-medium",
        "bg-primary text-white transition-colors",
        disabled && "opacity-50 cursor-not-allowed"
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

```

For components requiring custom design tokens, Plane extends the merge configuration:

```tsx
// Extending tailwind-merge for custom utilities (propel package)
import { extendTailwindMerge } from "tailwind-merge";

export const customMerge = extendTailwindMerge({
  // Add Plane-specific class groups
  classGroups: {
    shadow: [{ shadow: ["shadow-sm", "shadow-md", "shadow-lg"] }],
    spacing: ["spacing-1", "spacing-2", "spacing-3"],
  },
});

/* Usage in a component */
import { cn } from "@plane/ui";

export function Card({ className, children }: { className?: string; children: React.ReactNode }) {
  return (
    <div
      className={customMerge(
        cn("bg-surface-0 rounded-lg p-4 border border-subtle", className)
      )}
    >
      {children}
    </div>
  );
}

```

## Summary

- Plane frontend applications adopt a **Tailwind CSS-first strategy**, eliminating separate CSS files in favor of utility classes embedded in JSX.
- The **`cn` helper function**, re-exported from `@plane/ui` and originating in `@plane/utils`, provides type-safe class concatenation using `tailwind-merge` under the hood.
- **[`packages/ui/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/utils/classname.tsx)** serves as the standard entry point for class merging, while **[`packages/propel/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/propel/src/utils/classname.tsx)** supports extended configurations for custom design tokens.
- The build pipeline leverages **PostCSS** to tree-shake unused utilities, ensuring production CSS bundles contain only referenced classes for optimal performance.
- This architecture enables rapid visual iteration—updating a Tailwind class in a component instantly reflects across the application without touching external stylesheets.

## Frequently Asked Questions

### Why does Plane use `tailwind-merge` instead of simple string concatenation?

String concatenation cannot resolve conflicts where multiple utilities target the same CSS property. According to the Plane source code, `tailwind-merge` intelligently deduplicates conflicting classes (e.g., merging `p-2` and `p-4` into just `p-4`) while preserving the intended override order. This prevents subtle visual bugs that would occur with template literal concatenation when conditional classes overlap.

### How does Plane handle custom design tokens like spacing or colors?

Plane extends the standard Tailwind-merge behavior in [`packages/propel/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/propel/src/utils/classname.tsx) using `extendTailwindMerge`. This creates a custom merge instance that recognizes Plane-specific class groups—such as custom spacing tokens or shadow utilities—allowing the `cn` helper to correctly merge these non-standard utilities alongside standard Tailwind classes without conflicts.

### Can developers use custom CSS files in Plane frontend applications?

While the architecture is optimized for utility-first styling via Tailwind, the build system supports custom CSS through the standard PostCSS pipeline. However, the Plane codebase demonstrates that all styling should ideally be handled through Tailwind utilities and the `cn` helper to maintain consistency across the web, admin, and live applications and to leverage automatic purging of unused styles.

### What is the performance impact of the `cn` utility at runtime?

The `cn` utility introduces negligible runtime overhead. It is a thin wrapper that invokes `tailwind-merge` only when processing dynamic class combinations. Since Tailwind utilities are extracted at build time by PostCSS, no CSS-in-JS runtime processing occurs in the browser. The resulting CSS file contains only static, pre-computed utility classes, ensuring fast rendering without the style recalculation penalties associated with runtime CSS generation.