FeaturesPricingBlogContactLog in
Log in
FeaturesPricingBlogContact
Back to blog

January 20, 2025

Styling with Tailwind CSS and shadcn/ui

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

Styling with Tailwind CSS and shadcn/ui

On this page

The two-layer approachColor tokens over arbitrary valuesComposing with CardViewport heightEqual dimensionsAdding new components

The two-layer approach

Tailwind 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.

Color tokens over arbitrary values

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:

TokenUse
bg-background / text-foregroundPage surface and body text
bg-card / text-card-foregroundCard surfaces
bg-muted / text-muted-foregroundSubtle areas and captions
bg-primary / text-primary-foregroundCTAs and active states
bg-destructiveErrors and destructive actions

Composing with Card

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>
);

Viewport height

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" />

Equal dimensions

When width and height are the same, use size-*:

// ✗ wrong
<div className="h-6 w-6" />
 
// ✓ correct
<div className="size-6" />

Adding new components

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 calendar

The component lands in components/ui/ and is immediately available as @/components/ui/tooltip.

Let's Get In Touch.

Your laboratory instruments should serve you, not the other way around. We're happy to help you.

Book a discovery call

Building the future, one project at a time.

Product

  • Features
  • Pricing
  • Blog

Company

  • Contact

Legal

  • Privacy
  • Terms
© 2026 Brand. All rights reserved.