# How OpenCut Organizes and Customizes shadcn/ui Components: Architecture and Patterns

> Discover how OpenCut organizes and customizes shadcn/ui components using a centralized config file, path aliases, and a cn utility for type-safe Tailwind merging and variants.

- Repository: [OpenCut.app/OpenCut](https://github.com/OpenCut-app/OpenCut)
- Tags: architecture
- Published: 2026-06-23

---

**OpenCut centralizes its shadcn/ui configuration in a single [`components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/components.json) file, uses path aliases for clean imports, and wraps primitives with a `cn` utility to enable type-safe Tailwind class merging and flexible variant systems.**

The OpenCut repository implements shadcn/ui as its foundational design system for reusable UI primitives, creating a maintainable component architecture that separates configuration from implementation. This approach provides a single source of truth for styling while allowing developers to extend and customize components without modifying core library code. Understanding the organization and customization of shadcn/ui components in OpenCut reveals how the project balances consistency with flexibility.

## Centralized Configuration via components.json

OpenCut drives its entire shadcn/ui integration through a configuration file located at [`apps/web/components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/components.json). This JSON schema defines project-wide settings that govern how components are generated, styled, and imported throughout the application.

The configuration specifies `style: "base-mira"` as the visual preset, enables TypeScript support with `tsx: true`, and explicitly disables React Server Components via `rsc: false`. The Tailwind integration points to [`src/styles.css`](https://github.com/OpenCut-app/OpenCut/blob/main/src/styles.css) for global styles and enables CSS variables for theming. By setting `rsc: false`, OpenCut ensures all UI primitives render exclusively on the client, avoiding the complexity of mixing server and client component boundaries in the current Next.js and React Router setup.

## Path Aliases and Import Standards

The `aliases` section in [`apps/web/components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/components.json) establishes short import paths that eliminate lengthy relative traversals. Developers can import components using patterns like `import { Button } from "#/components/ui/button"` rather than deep relative paths such as `../../../components/ui/button`.

This alias system covers `ui`, `utils`, `hooks`, and other common directories, creating a consistent import experience across the monorepo. The `#/` prefix denotes project-root-relative imports, making refactors safer and code more readable.

## The cn Utility and Tailwind Class Management

At [`apps/web/src/lib/utils.ts`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/src/lib/utils.ts), OpenCut defines a `cn` helper function that serves as the backbone of all component styling. This thin wrapper combines `clsx` for conditional class construction with `tailwind-merge` for intelligent class deduplication.

Every UI component uses this utility to merge its internal variant classes with consumer-provided `className` props. The `cn` function ensures that Tailwind utilities are merged correctly without style duplication, respecting specificity and override order. This pattern allows developers to pass custom Tailwind classes to any component while preserving the base design system's integrity.

## Component Architecture

### Wrapping Primitives with data-slot

OpenCut wraps each underlying primitive from `@base-ui/react/*` in a lightweight component that injects a `data-slot` attribute and applies the `cn` helper. This pattern appears in files like [`apps/web/src/components/ui/tooltip.tsx`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/src/components/ui/tooltip.tsx), where the public API remains identical to standard shadcn/ui while allowing OpenCut to attach custom data attributes for styling hooks or automated testing.

The wrapper approach maintains full compatibility with the base shadcn ecosystem while providing extension points for project-specific requirements.

### Type-Safe Variants with CVA

Components requiring multiple visual states, such as [`apps/web/src/components/ui/button.tsx`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/src/components/ui/button.tsx), implement **class-variance-authority (CVA)** for variant management. The `cva` function defines `variant` and `size` options, mapping each to specific Tailwind class strings within a type-safe object.

Default variants are declared within the same definition, ensuring unstyled components still receive appropriate base classes. This system creates a strict contract between design and implementation, catching invalid variant combinations at compile time while generating clean, deterministic class strings at runtime.

## Practical Customization Examples

### Basic Usage with Aliases

Import components using the configured path aliases for clean, maintainable code:

```tsx
import { Button } from "#/components/ui/button"

export default function Example() {
  return <Button>Click me</Button>
}

```

### Applying Variants

Utilize the CVA-defined variants for consistent styling across sizes and intents:

```tsx
import { Button } from "#/components/ui/button"

export default function Example() {
  return (
    <Button variant="destructive" size="lg">
      Delete
    </Button>
  )
}

```

### Overriding Tailwind Classes

Pass custom classes through the `className` prop, which the `cn` utility merges with base styles:

```tsx
import { Button } from "#/components/ui/button"

export default function Example() {
  return (
    <Button className="bg-green-600 hover:bg-green-700">
      Save
    </Button>
  )
}

```

### Working with Wrapped Primitives

UseTooltip components follow the standard shadcn pattern while respecting OpenCut's wrapper architecture:

```tsx
import {
  Tooltip,
  TooltipTrigger,
  TooltipContent,
} from "#/components/ui/tooltip"

export default function Example() {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button>Hover me</Button>
      </TooltipTrigger>
      <TooltipContent side="right">
        This is a tooltip
      </TooltipContent>
    </Tooltip>
  )
}

```

### Extending Component Variants

Add project-specific variants by extending the base CVA configuration:

```tsx
import { buttonVariants } from "#/components/ui/button"
import { cva } from "class-variance-authority"

const extendedButton = cva(buttonVariants(), {
  variants: {
    variant: {
      warning: "bg-yellow-400 text-yellow-900 hover:bg-yellow-500",
    },
  },
  defaultVariants: { variant: "warning" },
})

export function WarningButton(props) {
  return <button className={extendedButton({})} {...props} />
}

```

## Summary

- **Centralized configuration**: The [`apps/web/components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/components.json) file controls styling presets, TypeScript settings, and React Server Component behavior for the entire component library.
- **Clean imports**: Path aliases like `#/components/ui/button` eliminate relative path fragility and improve code readability.
- **Safe class merging**: The `cn` utility in [`apps/web/src/lib/utils.ts`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/src/lib/utils.ts) uses `clsx` and `tailwind-merge` to handle dynamic class combinations without conflicts.
- **Type-safe variants**: CVA (class-variance-authority) powers the variant system in components like [`button.tsx`](https://github.com/OpenCut-app/OpenCut/blob/main/button.tsx), enabling compile-time checks for style combinations.
- **Extensible wrappers**: Components wrap `@base-ui/react/*` primitives to inject `data-slot` attributes while maintaining standard shadcn/ui APIs.

## Frequently Asked Questions

### Where is the shadcn/ui configuration stored in OpenCut?

OpenCut stores its shadcn/ui configuration in [`apps/web/components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/components.json). This file defines the styling preset (`base-mira`), Tailwind CSS settings, path aliases, and whether React Server Components are enabled (`rsc: false`).

### How does OpenCut handle Tailwind class merging?

OpenCut uses a `cn` utility function defined in [`apps/web/src/lib/utils.ts`](https://github.com/OpenCut-app/OpenCut/blob/main/apps/web/src/lib/utils.ts) that combines `clsx` for conditional classes with `tailwind-merge` for deduplication. Every UI component passes its `className` prop through this function, ensuring consumer overrides merge correctly with base styles without duplication.

### Can I add custom variants to existing components?

Yes. You can extend the base variant definitions using class-variance-authority. Import the existing `buttonVariants` (or similar) from the component file, then call `cva()` with additional variant options. This pattern preserves type safety while adding project-specific styles like warning states or custom sizes.

### Why does OpenCut disable React Server Components for UI primitives?

The `rsc: false` setting in [`components.json`](https://github.com/OpenCut-app/OpenCut/blob/main/components.json) forces all shadcn/ui components to render as client components. This decision aligns with OpenCut's current Next.js and React Router architecture, avoiding the complexity of managing server/client component boundaries and ensuring consistent behavior across interactive elements like tooltips and dropdowns.