# What Is the @plane/ui Package? A Complete Guide to Plane's Shared UI Component Library

> Discover the @plane/ui package, Plane's type-safe React component library. Ensure visual consistency across all front-end apps with reusable UI elements.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: deep-dive
- Published: 2026-08-23

---

**`@plane/ui` is the shared UI component library that provides type‑safe, reusable React components to all of Plane's front‑end applications, ensuring visual consistency across the monorepo.**

The `@plane/ui` package sits at the heart of the [makeplane/plane](https://github.com/makeplane/plane) monorepo, serving as the single source of truth for every button, avatar, modal, and tooltip rendered in Plane's web, admin, live, and space applications. Built with **TypeScript** and **Tailwind CSS**, it bundles proven UI primitives from libraries like Blueprint, Headless UI, and Radix into a cohesive, tree‑shakable package that any Plane developer can import with a single line.

## Purpose and Architecture of @plane/ui

The `@plane/ui` package solves a critical problem in large monorepos: **UI fragmentation**. Without a centralized component library, each application risks implementing its own buttons, dropdowns, and modals—leading to inconsistent styling, duplicated effort, and maintenance nightmares.

### Key Design Principles

| Principle | Implementation | Source Location |
|-----------|---------------|---------------|
| **Single source of truth** | One barrel export exposes all components | [[`packages/ui/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/ui/src/index.ts)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/index.ts) |
| **Type safety** | Every component written in TypeScript with explicit prop interfaces | Component files like [[`button/button.tsx`](https://github.com/makeplane/plane/blob/main/button/button.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/button/button.tsx) |
| **Utility‑first styling** | Tailwind CSS classes applied consistently | Throughout `src/` folders |
| **Tree‑shakable exports** | Named exports allow bundlers to eliminate unused code | Barrel file structure |
| **Peer dependency isolation** | React and React DOM required from host, not bundled | [[`package.json`](https://github.com/makeplane/plane/blob/main/package.json) peerDependencies](https://github.com/makeplane/plane/blob/preview/packages/ui/package.json) |

## How @plane/ui Is Organized

The package follows a **folder‑per‑feature** structure. Each UI pattern lives in its own directory with co‑located implementation, types, and tests.

```

packages/ui/src/
├── index.ts           # Barrel re‑export

├── avatar/            # User/image avatars

├── badge/             # Status and count badges

├── button/            # Primary, secondary, destructive buttons

├── dropdown/          # Select, multi‑select, combobox

├── modals/            # ModalCore, AlertModal, ConfirmModal

├── tooltip/           # Hover and focus tooltips

├── loader/            # Skeleton and spinner states

├── tables/            # Data table primitives

└── utils/             # Tailwind class merging utilities

```

This organization appears in the barrel file at [[`packages/ui/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/ui/src/index.ts)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/index.ts), which re‑exports every sub‑module:

```typescript
// From packages/ui/src/index.ts
export * from "./avatar";
export * from "./badge";
export * from "./button";
export * from "./dropdown";
export * from "./modals";
// ... etc

```

Consumers import exactly what they need:

```tsx
import { Button, Avatar, ModalCore } from '@plane/ui';

```

## Dependencies and External Integrations

The `@plane/ui` package strategically blends **internal Plane utilities** with **battle‑tested third‑party primitives**.

### Internal Dependencies

- **`@plane/constants`** – Shared design tokens and magic values
- **`@plane/hooks`** – Reusable React hooks (focus traps, click‑outside, etc.)
- **`@plane/utils`** – Helper functions for formatting and validation

### External UI Primitives

| Library | Purpose | Example Usage |
|---------|---------|---------------|
| `@blueprintjs/core` | Complex overlays, non‑ideal states | [`Popover2`](https://github.com/makeplane/plane/blob/preview/packages/ui/src/dropdown/single-select.tsx) internals |
| `@headlessui/react` | Accessible, unstyled components | Dropdown transitions |
| `@radix-ui/react-scroll‑area` | Custom scrollable regions | Scrollable modals and lists |
| `lucide-react` | Consistent iconography | [`Button`](https://github.com/makeplane/plane/blob/preview/packages/ui/src/button/button.tsx) icons |
| `tailwind-merge` | Class name deduplication | [[`utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/utils/classname.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/utils/classname.tsx) |

The peer dependency declaration for `react` and `react-dom` ensures that host applications supply their own React version, preventing the "multiple React copies" error common in monorepo setups.

## Build and Developer Experience

The [[`package.json`](https://github.com/makeplane/plane/blob/main/package.json)](https://github.com/makeplane/plane/blob/preview/packages/ui/package.json) scripts reveal a modern, fast toolchain:

```json
{
  "scripts": {
    "build": "tsdown",
    "dev": "tsdown --watch",
    "lint": "oxlint",
    "format": "oxfmt"
  }
}

```

- **tsdown**: Tiny wrapper around `tsc` + `esbuild` for rapid TypeScript compilation
- **oxlint/oxfmt**: Rust‑based linter and formatter for sub‑second feedback loops
- **Storybook**: Component documentation and visual testing (implied by dependencies)

## Practical Usage Example

Here's how `@plane/ui` components compose together in a real Plane feature:

```tsx
import React, { useState } from 'react';
import {
  Button,
  Avatar,
  Tooltip,
  ModalCore,
  SingleSelect,
} from '@plane/ui';

export const IssueCreator = () => {
  const [open, setOpen] = useState(false);
  const [issueType, setIssueType] = useState(null);

  const typeOptions = [
    { value: 'bug', label: 'Bug', color: '#ef4444' },
    { value: 'feature', label: 'Feature', color: '#3b82f6' },
    { value: 'improvement', label: 'Improvement', color: '#22c55e' },
  ];

  return (
    <div className="flex items-center gap-3 p-4">
      {/* Current user avatar */}
      <Avatar
        src="/api/users/me/avatar"
        alt="Current user"
        size="md"
        fallback="👤"
      />

      {/* Primary action with contextual help */}
      <Tooltip content="Create a new issue (⌘N)" placement="top">
        <Button
          variant="primary"
          onClick={() => setOpen(true)}
        >
          New Issue
        </Button>
      </Tooltip>

      {/* Issue type selector */}
      <SingleSelect
        options={typeOptions}
        value={issueType}
        onChange={setIssueType}
        placeholder="Select type..."
        className="w-48"
      />

      {/* Creation modal */}
      {open && (
        <ModalCore
          title="Create new issue"
          onClose={() => setOpen(false)}
          size="lg"
          footer={
            <div className="flex justify-end gap-2">
              <Button variant="secondary" onClick={() => setOpen(false)}>
                Cancel
              </Button>
              <Button variant="primary" onClick={handleCreate}>
                Create Issue
              </Button>
            </div>
          }
        >
          <p className="text-gray-600">
            Fill in the details for your {issueType?.label?.toLowerCase() || 'issue'}.
          </p>
        </ModalCore>
      )}
    </div>
  );
};

```

Key patterns demonstrated:

- **Consistent prop interfaces** – `size="md"` on `Avatar`, `variant="primary"` on `Button`, `placement="top"` on `Tooltip`
- **Composability** – `Tooltip` wraps any element, `ModalCore` accepts arbitrary footer content
- **Tailwind compatibility** – `className` props pass through to underlying elements
- **Type inference** – `SingleSelect` infers option shape from the `options` array

## Critical Source Files

| File | Responsibility |
|------|---------------|
| [[`packages/ui/package.json`](https://github.com/makeplane/plane/blob/main/packages/ui/package.json)](https://github.com/makeplane/plane/blob/preview/packages/ui/package.json) | Package manifest, dependency graph, build scripts |
| [[`packages/ui/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/ui/src/index.ts)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/index.ts) | Public API surface—every export consumers can import |
| [[`packages/ui/src/button/button.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/button/button.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/button/button.tsx) | Core button with variants (primary, secondary, ghost, destructive) |
| [[`packages/ui/src/avatar/avatar.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/avatar/avatar.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/avatar/avatar.tsx) | User avatar with fallback initials and image optimization |
| [[`packages/ui/src/modals/modal-core.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/modals/modal-core.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/modals/modal-core.tsx) | Foundation for all modal dialogs |
| [[`packages/ui/src/modals/alert-modal.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/modals/alert-modal.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/modals/alert-modal.tsx) | Pre‑built confirmation dialog |
| [[`packages/ui/src/dropdown/single-select.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/dropdown/single-select.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/dropdown/single-select.tsx) | Accessible single‑option dropdown |
| [[`packages/ui/src/tooltip/tooltip.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/tooltip/tooltip.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/tooltip/tooltip.tsx) | Hover/focus tooltips with smart positioning |
| [[`packages/ui/src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/packages/ui/src/utils/classname.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/utils/classname.tsx) | `cn()` utility wrapping `tailwind-merge` and `clsx` |

## Summary

- **`@plane/ui` is Plane's shared React component library**, consumed by all front‑end applications in the monorepo.
- **TypeScript + Tailwind CSS** provide type safety and consistent styling without CSS‑in‑JS overhead.
- **Barrel exports** from [[`src/index.ts`](https://github.com/makeplane/plane/blob/main/src/index.ts)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/index.ts) enable clean, tree‑shakable imports.
- **Hybrid dependency strategy** combines internal Plane packages with proven external primitives (Blueprint, Headless UI, Radix).
- **Peer dependencies** on React keep the bundle lightweight and prevent version conflicts.
- **Modern toolchain** (tsdown, oxlint, oxfmt) delivers fast build and feedback cycles.

## Frequently Asked Questions

### What is the entry point for importing @plane/ui components?

The entry point is [[`packages/ui/src/index.ts`](https://github.com/makeplane/plane/blob/main/packages/ui/src/index.ts)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/index.ts), a barrel file that re‑exports every component folder. This allows consumers to write `import { Button, Avatar } from '@plane/ui'` rather than deep‑importing individual files. The [`package.json`](https://github.com/makeplane/plane/blob/main/package.json) specifies `"main": "dist/index.js"` and `"types": "dist/index.d.ts"` so TypeScript and bundlers resolve correctly.

### Does @plane/ui bundle its own copy of React?

No. The [[`package.json`](https://github.com/makeplane/plane/blob/main/package.json)](https://github.com/makeplane/plane/blob/preview/packages/ui/package.json) declares `react` and `react-dom` as **peer dependencies**, not regular dependencies. This means the host application (web, admin, space, etc.) provides the React instance. This design prevents the "Hooks can only be called inside the body of a function component" errors that occur when multiple React copies exist in a single runtime.

### How does @plane/ui handle Tailwind class conflicts?

The package includes a `cn()` utility in [[`src/utils/classname.tsx`](https://github.com/makeplane/plane/blob/main/src/utils/classname.tsx)](https://github.com/makeplane/plane/blob/preview/packages/ui/src/utils/classname.tsx) that wraps `tailwind-merge` and `clsx`. This function intelligently merges Tailwind classes, resolving conflicts like `p-2 p-4` to `p-4` and handling conditional class application. Component authors use this internally, and consumers can rely on predictable class resolution when extending components.

### Can I use @plane/ui outside of the Plane monorepo?

Technically yes, but practically no. While the package is published‑ready with proper exports and peer dependencies, it depends heavily on internal Plane packages (`@plane/constants`, `@plane/hooks`, `@plane/utils`) and assumes specific Tailwind configuration conventions. Extracting it for external use would require either publishing those dependencies or refactoring the components to remove Plane‑specific logic.