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