shadcn/ui
02 / 02

shadcn/ui: Theming, Forms & Customization Patterns

shadcn/ui: Theming, Forms & Customization Patterns

Theming with CSS Variables

/* globals.css -- semantic color roles referenced by every component */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 210 40% 98%;
}

/* Swapping these variable values (e.g. via a .dark class on <html>)
   re-themes every component consistently -- no per-component edits needed */

asChild: Rendering as a Different Element

// Inherited from Radix -- applies Button's styling directly to the
// rendered <a> element, avoiding an invalid nested <a> inside <button>
<Button asChild>
  <Link href="/about">About</Link>
</Button>

Forms with react-hook-form + Zod

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const formSchema = z.object({
  email: z.string().email(),
})

// shadcn/ui's Form components provide styled, accessible field/label/
// error-message UI that wires into react-hook-form + Zod's validation --
// shadcn/ui contributes the UI layer, not form state management itself
function ProfileForm() {
  const form = useForm({ resolver: zodResolver(formSchema) })
  // ... <Form>, <FormField>, <FormLabel>, <FormMessage> from components/ui/form
}

The Maintenance Tradeoff

Since updates aren't automatic (unlike npm update), keeping components current requires manually tracking upstream changes and re-applying relevant ones to your own already-customized copies. A worthwhile tradeoff for many projects: a substantial head start on well-designed, accessible components, with full freedom to diverge from the defaults as needs grow.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free