# How to Customize shadcn/ui Components for a Stitch Project: Complete Guide

> Customize shadcn/ui components in Stitch projects using CSS variables, Tailwind config, and cva variants. Learn to modify components without touching node_modules for your Stitch skills.

- Repository: [Google Labs Code/stitch-skills](https://github.com/google-labs-code/stitch-skills)
- Tags: how-to-guide
- Published: 2026-07-12

---

**shadcn/ui components in Stitch projects are fully editable source files that you customize through CSS variables, Tailwind configuration, and class-variance-authority (cva) variants without touching node_modules.**

Stitch projects treat shadcn/ui as a copy-and-own architecture rather than a traditional dependency. This pattern, detailed in the `google-labs-code/stitch-skills` repository, gives you direct control over component internals while maintaining separation between base components and project-specific extensions.

## Understanding the Stitch shadcn/ui Architecture

Unlike conventional UI libraries, shadcn/ui components in Stitch are copied directly into your project's `components/ui/` directory. According to the repository's [`SKILL.md`](https://github.com/google-labs-code/stitch-skills/blob/main/SKILL.md) located at [`plugins/stitch-build/skills/shadcn-ui/SKILL.md`](https://github.com/google-labs-code/stitch-skills/blob/main/plugins/stitch-build/skills/shadcn-ui/SKILL.md), this approach means you own the source code entirely. The customization workflow follows a specific hierarchy: global theme tokens define your design system, Tailwind extends the utility layer, and **class-variance-authority (cva)** manages component variants.

The repository's customization guide at [`plugins/stitch-build/skills/shadcn-ui/resources/customization-guide.md`](https://github.com/google-labs-code/stitch-skills/blob/main/plugins/stitch-build/skills/shadcn-ui/resources/customization-guide.md) emphasizes maintaining original component files intact while extending functionality through wrappers or configuration files.

## Method 1: Theming with CSS Variables

### Global Design Tokens in globals.css

The first layer of customization happens in [`app/globals.css`](https://github.com/google-labs-code/stitch-skills/blob/main/app/globals.css) (or your project's global styles). Here, CSS custom properties define your color palette, border radius, and spacing scale. These variables power the Tailwind utility classes that components consume.

Update the `:root` selector to override default values:

```css
/* app/globals.css */
:root {
  /* Override the primary brand color */
  --primary: 270 91% 65%;   /* brand-purple */
  --primary-foreground: 0 0% 100%; /* white text */
  
  /* Adjust border radius globally */
  --radius: 0.5rem;
}

```

For dark mode, add a `.dark` class block with alternate values:

```css
/* app/globals.css */
.dark {
  --primary: 270 60% 50%;   /* darker purple for dark mode */
  --background: 222 47% 11%;
  --foreground: 210 40% 98%;
}

```

## Method 2: Extending Tailwind Configuration

### Custom Fonts and Spacing

Beyond CSS variables, granular control lives in [`tailwind.config.js`](https://github.com/google-labs-code/stitch-skills/blob/main/tailwind.config.js). Extend the theme to inject custom fonts, animation keyframes, or spacing scales that your shadcn/ui components can reference.

```javascript
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        heading: ['Poppins', 'system-ui', 'sans-serif'],
        mono: ['Fira Code', 'monospace'],
      },
      animation: {
        'fade-in': 'fadeIn 0.5s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
      },
    },
  },
};

```

## Method 3: Adding Component Variants with CVA

### Modifying Variant Definitions

shadcn/ui uses **class-variance-authority (cva)** to define variant combinations. As documented in [`SKILL.md`](https://github.com/google-labs-code/stitch-skills/blob/main/SKILL.md) (lines 58-81), each component exports a `cva` call that maps variant names to Tailwind class strings. You add new variants by extending this configuration in the component file.

For example, to add a "success" variant to the Button component in [`components/ui/button.tsx`](https://github.com/google-labs-code/stitch-skills/blob/main/components/ui/button.tsx):

```tsx
// components/ui/button.tsx
import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium",
  {
    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",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
        /* New variant addition */
        success: "bg-green-600 text-white hover:bg-green-700",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

```

**Usage:**

```tsx
<Button variant="success" size="lg">
  Save Changes
</Button>

```

## Method 4: Creating Wrapper Components

The customization guide (lines 98-104) recommends a strict boundary: keep `components/ui/*` files synchronized with the base shadcn/ui patterns, and create thin wrappers in `components/` for project-specific enhancements.

This approach prevents upgrade conflicts while allowing deep customization:

```tsx
// components/custom-button.tsx
import { Button, ButtonProps } from "@/components/ui/button";
import { Loader2 } from "lucide-react";

export function LoadingButton({
  loading,
  children,
  ...props
}: ButtonProps & { loading?: boolean }) {
  return (
    <Button disabled={loading} {...props}>
      {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
      {children}
    </Button>
  );
}

```

Wrappers compose the base component without modifying the underlying `cva` definitions, making it safe to update the original shadcn/ui files later.

## Enabling Dark Mode

### next-themes Integration

Stitch projects implement dark mode through CSS class toggling combined with `next-themes`. First, wrap your application with the provider:

```tsx
// app/providers.tsx
"use client";

import { ThemeProvider } from "next-themes";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </ThemeProvider>
  );
}

```

Then implement a toggle component:

```tsx
// components/theme-toggle.tsx
"use client";

import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";

export function ThemeToggle() {
  const { setTheme, theme } = useTheme();

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  );
}

```

The `.dark` class applied to the HTML element triggers the dark mode CSS variables defined in [`globals.css`](https://github.com/google-labs-code/stitch-skills/blob/main/globals.css), instantly updating all shadcn/ui components.

## Summary

- **CSS variables** in [`app/globals.css`](https://github.com/google-labs-code/stitch-skills/blob/main/app/globals.css) control global design tokens like colors and radius.
- **Tailwind configuration** in [`tailwind.config.js`](https://github.com/google-labs-code/stitch-skills/blob/main/tailwind.config.js) extends utilities for fonts, spacing, and animations.
- **CVA variants** in `components/ui/*.tsx` files define component-specific style combinations through `class-variance-authority`.
- **Wrapper components** in `components/` provide project-specific logic without contaminating base UI files.
- **Dark mode** relies on `next-themes` and CSS class switching, with variables defined alongside light mode tokens.

## Frequently Asked Questions

### Should I edit files in components/ui directly?

You should modify `components/ui/*` files only to add new **cva** variants or fix bugs, as noted in the [`customization-guide.md`](https://github.com/google-labs-code/stitch-skills/blob/main/customization-guide.md) (lines 98-104). For project-specific logic, styling, or behavior, create wrapper components in your `components/` directory. This preserves the ability to diff and update base components when the upstream shadcn/ui skill updates.

### How do I add a new color theme to my Stitch project?

Define your colors as HSL values in CSS variables within [`app/globals.css`](https://github.com/google-labs-code/stitch-skills/blob/main/app/globals.css). Update the `:root` selector for light mode and add a `.dark` selector for dark mode values. Then reference these variables in [`tailwind.config.js`](https://github.com/google-labs-code/stitch-skills/blob/main/tailwind.config.js) if you need to create semantic Tailwind classes like `bg-brand` or `text-brand-foreground`.

### What is class-variance-authority (cva) and why does shadcn/ui use it?

**class-variance-authority** is a utility for creating type-safe variant combinations. It allows shadcn/ui components to accept props like `variant="destructive"` or `size="sm"` and compute the correct Tailwind class string. The source code in [`SKILL.md`](https://github.com/google-labs-code/stitch-skills/blob/main/SKILL.md) (lines 58-81) demonstrates how `cva` merges base styles with conditional variant classes, providing TypeScript autocompletion for props.

### How do I prevent customizations from breaking during updates?

Follow the **wrapper pattern**: keep `components/ui/` files as close to the original shadcn/ui source as possible, only adding new `cva` variants when necessary. Place all business logic, additional props, and composite components in separate files within `components/`. This isolation ensures that skill updates only require reviewing the base component files, not your entire component library.