January 20, 2025
How to combine Tailwind utility classes with shadcn component primitives for a consistent, maintainable design system.

On this page
The two-layer approachColor tokens over arbitrary valuesComposing with CardViewport heightEqual dimensionsAdding new componentsTailwind CSS gives you utility classes. shadcn/ui gives you accessible, unstyled component primitives built on Radix UI. Together, they form a design system where you own every pixel but don't build from scratch.
The key insight: shadcn components use CSS variables for color, so your entire color palette can be swapped by changing a few tokens in globals.css.
Never reach for text-gray-500 or bg-blue-600. Use semantic tokens instead:
// ✗ wrong — hardcoded, breaks dark mode, breaks theming
<p className="text-gray-500">Secondary text</p>
<div className="bg-white border-gray-200">Card</div>
// ✓ correct — respects the active theme
<p className="text-muted-foreground">Secondary text</p>
<div className="bg-card border">Card</div>The token list:
| Token | Use |
|---|---|
bg-background / text-foreground | Page surface and body text |
bg-card / text-card-foreground | Card surfaces |
bg-muted / text-muted-foreground | Subtle areas and captions |
bg-primary / text-primary-foreground | CTAs and active states |
bg-destructive | Errors and destructive actions |
Always use Card for boxed content. Never build a raw div with manual shadow and border classes.
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
const PostCard = ({ title, description }: Props) => (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground text-sm">{description}</p>
</CardContent>
</Card>
);Always use dvh units instead of vh to account for mobile browser chrome:
// ✗ wrong
<div className="min-h-screen" />
// ✓ correct
<div className="min-h-dvh" />When width and height are the same, use size-*:
// ✗ wrong
<div className="h-6 w-6" />
// ✓ correct
<div className="size-6" />If you need a component not already installed, add it from the shadcn registry — never build it from scratch:
pnpm dlx shadcn@latest add tooltip
pnpm dlx shadcn@latest add calendarThe component lands in components/ui/ and is immediately available as @/components/ui/tooltip.
Your laboratory instruments should serve you, not the other way around. We're happy to help you.