Design Engineering
Build

Design System Integration

Work with existing component libraries — shadcn/ui, Radix, Base UI, and others — to accelerate development without sacrificing design quality or customization.

Pre-built component libraries handle accessibility, keyboard navigation, and cross-browser behaviour. For a design engineer, the skill is choosing the right library and customizing it to match your design system without fighting its architecture.

Library Categories

Headless (Radix, Base UI, Ark UI, Ariakit)

Headless libraries provide behaviour without styling. They manage keyboard interactions, focus trapping, ARIA attributes, and state — but render no visual output. You supply all the styles, markup structure, and animation.

LibraryApproachBest for
Radix UIPrimitives with React Context for shared stateComplex composites: dialogs, dropdowns, popovers, tabs. The most mature headless library
Base UIUnstyled, fully headless components with a smaller API surfaceProjects that want Radix-like behaviour with fewer abstraction layers
Ark UIHeadless components with framework-agnostic state machinesProjects using multiple frameworks (React + Vue) or wanting state-machine control
AriakitLightweight accessibility primitivesMinimalist projects that need keyboard + ARIA without overhead
Radix Dialog — fully styled by you
import * as Dialog from "@radix-ui/react-dialog"
import { X } from "lucide-react"

export function Modal({ open, onOpenChange, title, children }) {
  return (
    <Dialog.Root open={open} onOpenChange={onOpenChange}>
      <Dialog.Portal>
        <Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-in" />
        <Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-lg rounded-lg bg-background p-6 shadow-lg">
          <Dialog.Title className="text-lg font-semibold">{title}</Dialog.Title>
          {children}
          <Dialog.Close asChild>
            <button className="absolute right-4 top-4" aria-label="Close">
              <X className="size-4" />
            </button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

Copy-Paste (shadcn/ui)

shadcn/ui is not an npm package — it is a collection of components you copy into your project and own. The source code is your code. You can modify every line, delete what you do not need, and style everything with your Tailwind config.

CharacteristicDetail
DistributionCLI copies source files into components/ui/
StylingTailwind classes + cn() utility, no CSS-in-JS
CustomizationEdit the source directly — it is your code
DependencyRadix UI primitives under the hood for interactive components
ThemeabilityCSS variables in globals.css for the built-in theme
shadcn/ui Button — after customization
// components/ui/button.tsx
// You own this file. Change variants, sizes, styles directly.

import { cva } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
        // Add custom variants here:
        // premium: "bg-gradient-to-r from-purple-600 to-blue-600 text-white",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
        // Add custom sizes:
        // xl: "h-12 rounded-md px-10 text-base",
      },
    },
  }
)

Ownership changes everything. With shadcn/ui, if a component needs different behaviour, you edit the file. No forking a library, no workaround wrapper, no waiting for upstream. This makes it the most design-engineer-friendly distribution model.

Styled + Themeable (Park UI, Chakra, Mantine)

These libraries ship with a default theme that you override. They provide complete, styled components out of the box with a theming API for customization.

LibraryApproachBest for
Park UIBuilt on Ark UI + Panda CSS, styled out of the boxProjects that want Radix-level behaviour with an opinionated look
ChakraComponent library with a comprehensive theme objectRapid prototyping where speed matters more than bundle size
Mantine100+ hooks and components, all styledFeature-heavy apps needing rich inputs, date pickers, notifications
Park UI — styled components with Ark UI internals
import { Dialog } from "@park-ui/dialog"

export function ConfirmDelete() {
  return (
    <Dialog.Root>
      <Dialog.Trigger>Delete project</Dialog.Trigger>
      <Dialog.Backdrop />
      <Dialog.Positioner>
        <Dialog.Content>
          <Dialog.Title>Are you sure?</Dialog.Title>
          <Dialog.Description>
            This action cannot be undone.
          </Dialog.Description>
          <Dialog.CloseTrigger>Cancel</Dialog.CloseTrigger>
        </Dialog.Content>
      </Dialog.Positioner>
    </Dialog.Root>
  )
}

Choosing the Right Library

ScenarioRecommendedWhy
"I want full control over every pixel"Radix UI or Ark UI + your own stylesHeadless gives maximum control — you write all the CSS
"I want to move fast and customize later"shadcn/uiCopy into your project, modify as needed, no abstraction overhead
"I need 50+ component types out of the box"Mantine or Park UIComplete component sets for complex dashboards and admin panels
"I'm building a design system for multiple teams"Radix UI + custom themed layerHeadless primitives as the foundation, your design system on top
"I'm prototyping and need something fast"shadcn/ui initA full UI in minutes; replace components as the project matures

Customization Strategies

1. Token override (shadcn/ui)

Modify the CSS variable values in globals.css. Every shadcn component references these variables — change one value, update the entire system.

globals.css — shadcn theme customization
:root {
  --radius: 0.5rem;           /* Default: 0.5rem — bump for rounder UI */
  --primary: oklch(0.43 0.12 236);  /* Brand blue */
  --primary-foreground: oklch(0.985 0.003 100);
  --font-sans: "Inter", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", monospace;
}

.dark {
  --primary: oklch(0.72 0.11 210);
  --primary-foreground: oklch(0.14 0.018 250);
}

2. Component override (headless libraries)

Wrap headless primitives in your own themed component. The design engineer pattern is: headless behaviour + your tokens = your design system.

Pattern: Headless primitive + your design system
// components/ui/popover.tsx
import * as Popover from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"

export function PopoverContent({ className, ...props }) {
  return (
    <Popover.Portal>
      <Popover.Content
        className={cn(
          "z-50 rounded-lg border bg-popover p-4 text-popover-foreground shadow-md",
          "data-[state=open]:animate-in data-[state=closed]:animate-out",
          className
        )}
        sideOffset={4}
        {...props}
      />
    </Popover.Portal>
  )
}

3. Extending shadcn/ui

Add new variants and components to your shadcn/ui directory. The component files are yours.

Adding a new variant to shadcn/ui Button
const buttonVariants = cva(/* ... */, {
  variants: {
    variant: {
      // Existing variants...
      premium: "bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-lg hover:from-purple-700 hover:to-blue-700",
    },
  },
})

Combining Multiple Libraries

It is common to use multiple libraries together. For example: shadcn/ui for the component layer (Button, Input, Card, Dialog) plus Radix UI directly for complex patterns (Dropdown Menu, Toolbar, Collapsible) that shadcn does not provide.

Mixed library usage pattern
// components/ui/ dropdown-menu.tsx
// shadcn/ui-style wrapper around Radix UI

import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"

const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger

const DropdownMenuContent = forwardRef<
  React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
  <DropdownMenuPrimitive.Portal>
    <DropdownMenuPrimitive.Content
      ref={ref}
      className={cn(
        "z-50 min-w-32 rounded-lg border bg-popover p-1 shadow-md",
        "data-[state=open]:animate-in data-[state=closed]:animate-out",
        className
      )}
      sideOffset={4}
      {...props}
    />
  </DropdownMenuPrimitive.Portal>
))

Every library you add increases bundle size, maintenance burden, and the risk of conflicting style sheets. Before adding a second library, ask: can I extend the first one? A custom shadcn/ui component is smaller and more maintainable than a second library import for one component.

Agent Workflows with Design Systems

When using AI agents with a design system library, specificity matters. The agent needs to know which components are available and which conventions to follow.

Prompt: agent + design system
This project uses shadcn/ui. The following components are installed:
Button, Input, Dialog, Select, Tabs, Badge, Card

Create a "ProjectSettingsDialog" that:
1. Opens via a Button with variant="outline" and text "Settings"
2. Contains a Dialog with Tabs: "General", "Members", "Billing"
3. Each tab has relevant form fields using shadcn/ui Input and Select
4. Uses CSS variable tokens from globals.css — no inline colors
5. TypeScript, React, "use client"

Reference the existing Dialog component at components/ui/dialog.tsx
for the exact import pattern and style conventions.
Agent prompt additionWhat it enforces
"Use shadcn/ui [Component]"Correct imports, available variants, consistent styling
"This project uses theme tokens — no inline colors or arbitrary values"Design system adherence
"Follow the existing [component] style pattern in components/ui/"File structure, naming, and export conventions
"Keep this as a Server Component if possible"Correct boundary placement

On this page