Building UI with shadcn/ui and Tailwind v4
Implements components using shadcn/ui and Tailwind v4 while adhering to a strict design system based on 4-size typography, 8pt grid spacing, and 60/30/10 color distribution.
Core Design Principles
1. Typography System: 4 Sizes, 2 Weights Only
Font Sizes:
- •Size 1: Large headings (largest)
- •Size 2: Subheadings and important content
- •Size 3: Body text (default)
- •Size 4: Small text and labels (smallest)
Font Weights:
- •Semibold: Headings and emphasis only
- •Regular: Body text and general UI elements
Never introduce additional font sizes or weights—maintain visual hierarchy through consistent sizing patterns.
2. 8pt Grid System
All spacing values MUST be divisible by 8 or 4:
✅ Correct:
- •p-4 (16px), p-6 (24px), p-8 (32px)
- •m-2 (8px), m-4 (16px), m-6 (24px)
- •gap-2 (8px), gap-4 (16px), gap-8 (32px)
- •Custom: 8, 16, 24, 32, 40, 48, 56, 64px
❌ Incorrect:
- •25px padding → Use 24px instead
- •11px margin → Use 12px instead
- •15px gap → Use 16px instead
- •7px, 13px, arbitrary values
Why: Creates visual harmony, simplifies decisions, establishes predictable patterns.
3. 60/30/10 Color Rule
Color Distribution:
- •
60%: Neutral color (bg-background)
- •Light mode: White or light gray
- •Dark mode: Dark gray or black
- •Usage: Primary backgrounds, cards, containers
- •
30%: Complementary color (text-foreground)
- •Light mode: Dark gray or black
- •Dark mode: Light gray or white
- •Usage: Text, icons, subtle UI elements
- •
10%: Accent color (brand color)
- •Your primary brand color (red, blue, etc.)
- •Usage: Call-to-action buttons, highlights, important indicators
- •⚠️ Avoid overuse—reserve for elements needing attention
4. Clean Visual Structure
- •Logical Grouping: Related elements visually connected
- •Deliberate Spacing: Follow 8pt grid system
- •Proper Alignment: Elements aligned within containers
- •Simplicity First: Clarity and function before flashiness
Foundation
Tailwind v4 Setup
npx create-next-app@latest my-app cd my-app npx shadcn-ui@latest init
Configuration
tailwind.config.js (Tailwind v4):
import defaultConfig from 'tailwindcss/defaultConfig'
export default {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
colors: {
background: 'var(--background)',
foreground: 'var(--foreground)',
primary: 'var(--primary)',
'primary-foreground': 'var(--primary-foreground)',
},
},
},
}
CSS Variables
app/globals.css (Tailwind v4):
@import "tailwindcss";
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0 0);
--color-primary-foreground: oklch(0.985 0 0);
--color-secondary: oklch(0.15 0 0);
--color-secondary-foreground: oklch(0.98 0 0);
--color-muted: oklch(0.96 0 0);
--color-muted-foreground: oklch(0.5 0 0);
--color-destructive: oklch(0.62 0.22 29.23);
--color-destructive-foreground: oklch(0.985 0 0);
}
@dark {
--color-background: oklch(0.08 0 0);
--color-foreground: oklch(0.98 0 0);
--color-primary: oklch(0.85 0.1 60);
--color-primary-foreground: oklch(0.12 0 0);
}
Typography System
Font Sizes Implementation
Use consistent sizing throughout your app:
// Heading 1 (Size 1 - largest) <h1 className="text-4xl font-semibold">Main Title</h1> // Heading 2 (Size 2) <h2 className="text-2xl font-semibold">Section Title</h2> // Body (Size 3 - default) <p className="text-base font-regular">Body text content here</p> // Small/Label (Size 4 - smallest) <label className="text-sm font-regular">Label Text</label>
Typography Hierarchy
Always maintain clear hierarchy with limited options:
✅ Good hierarchy:
<div className="space-y-6"> <h1 className="text-4xl font-semibold">Page Title</h1> <p className="text-base font-regular text-muted-foreground">Intro text</p> <h2 className="text-2xl font-semibold mt-8">Section</h2> <p className="text-base font-regular">Content</p> <label className="text-sm font-regular">Input label</label> </div>
❌ Inconsistent hierarchy:
// Too many different sizes and weights <h1 className="text-5xl font-bold">Title</h1> <h2 className="text-xl font-medium">Subtitle</h2> <p className="text-lg font-semibold">Body</p>
8pt Grid System
Spacing Implementation
Always use multiples of 4 or 8:
// Correct spacing
<div className="p-6 space-y-4">
<div className="flex gap-2">Content</div>
<div className="m-4">Properly spaced</div>
</div>
// Incorrect spacing
<div className="p-5 space-y-3"> {/* ❌ 5 and 3 not divisible */}
<div className="flex gap-1.5">Content</div>
<div className="m-3">Inconsistent</div>
</div>
Common Spacing Values
| Purpose | Tailwind | Pixels | When to Use |
|---|---|---|---|
| Tight spacing | gap-2, p-2 | 8px | Small component padding |
| Standard spacing | gap-4, p-4 | 16px | Default padding/gaps |
| Loose spacing | gap-6, p-6 | 24px | Section padding |
| Very loose | gap-8, p-8 | 32px | Large section separation |
| Extra loose | gap-12, p-12 | 48px | Major section breaks |
Responsive Grid
Grid system applies to all breakpoints:
<div className="flex gap-4 md:gap-6 lg:gap-8">
{/* Gap increases from 16px → 24px → 32px */}
</div>
Color Implementation
Color Variables
Use CSS variables following OKLCH color format:
/* Light mode */
:root {
--background: oklch(1 0 0); /* White */
--foreground: oklch(0.145 0 0); /* Dark gray/black */
--primary: oklch(0.205 0 0); /* Brand color */
--secondary: oklch(0.15 0 0); /* Secondary */
--muted: oklch(0.96 0 0); /* Light gray bg */
--muted-foreground: oklch(0.5 0 0); /* Medium gray text */
--destructive: oklch(0.62 0.22 29.23); /* Red */
}
/* Dark mode */
@dark {
--background: oklch(0.08 0 0); /* Very dark */
--foreground: oklch(0.98 0 0); /* Light text */
--primary: oklch(0.85 0.1 60); /* Bright brand */
--muted: oklch(0.15 0 0); /* Dark gray bg */
--muted-foreground: oklch(0.7 0 0); /* Light gray text */
}
60/30/10 in Practice
// Correct color distribution
export function Card() {
return (
<div className="bg-background p-6"> {/* 60% - neutral background */}
<h2 className="text-foreground font-semibold">Title</h2> {/* 30% - text */}
<button className="bg-primary text-primary-foreground"> {/* 10% - accent */}
Action
</button>
</div>
);
}
❌ Overusing accent:
// Too many accent colors <div className="bg-primary p-4"> <h2 className="text-primary font-semibold">Title</h2> <button className="bg-primary">Button</button> <div className="bg-secondary">More accents</div> </div>
Component Architecture
shadcn/ui Structure
2-layered architecture:
- •Behavior Layer: Radix UI primitives (accessibility, keyboard nav)
- •Style Layer: Tailwind CSS + CVA (variants)
Component with data-slot
import { Button } from '@/components/ui/button'
// shadcn/ui v4 components use data-slot for styling
export function MyComponent() {
return (
<Button
variant="default"
size="lg"
className="w-full"
>
Click me
</Button>
)
}
Creating Custom Components
// components/ui/card.tsx
import { ReactNode } from 'react'
interface CardProps {
children: ReactNode
className?: string
}
export function Card({ children, className }: CardProps) {
return (
<div className={`bg-background border border-border rounded-lg p-6 ${className}`}>
{children}
</div>
)
}
// Usage with 8pt grid
<Card className="space-y-4">
<h2 className="text-2xl font-semibold">Title</h2>
<p className="text-base font-regular">Content</p>
</Card>
Installing Components
# Install individual components npx shadcn-ui@latest add button npx shadcn-ui@latest add card npx shadcn-ui@latest add dialog npx shadcn-ui@latest add input
Visual Hierarchy
Implementation Checklist
- • Headings use Size 1 or 2 with semibold weight
- • Body text uses Size 3 with regular weight
- • Labels use Size 4 with regular weight
- • Related elements grouped with consistent gap-4 or gap-6
- • Important elements use accent color (10% max)
- • Sufficient contrast between text and background
- • Spacing follows 8pt grid throughout
Layout Example
export function Section() {
return (
<div className="space-y-6"> {/* Major spacing: 24px */}
<div>
<h2 className="text-2xl font-semibold">Section Title</h2>
<p className="text-sm text-muted-foreground mt-2">Subtitle</p>
</div>
<div className="space-y-4"> {/* Content spacing: 16px */}
<div className="flex gap-4"> {/* Item spacing: 16px */}
<img src="..." className="w-16 h-16 rounded" />
<div>
<h3 className="text-base font-semibold">Item</h3>
<p className="text-sm text-foreground">Description</p>
</div>
</div>
</div>
<button className="bg-primary text-primary-foreground px-6 py-3">
Action {/* Accent color: 10% */}
</button>
</div>
)
}
AI-Assisted Code Generation with Gemini CLI
Generate UI code using Google's Gemini AI while maintaining design system compliance.
Prerequisites
- •
Get a Gemini API key:
- •Visit Google AI Studio
- •Create a new API key
- •Save it securely
- •
Set environment variable:
bashexport GEMINI_API_KEY="your-api-key-here"
- •
Install Gemini CLI:
bash# Install globally (recommended) npm install -g @google/generative-ai-cli # Or use with npx (no installation) npx @google/generative-ai-cli
Prompting Gemini for UI Code
Use specific, design-system-aware prompts to generate compliant code:
Good prompt:
Create a React shadcn/ui component for a user profile card with: - Title using text-2xl font-semibold - Subtitle using text-sm font-regular - Spacing following 8pt grid (use only gap-4, gap-6, gap-8, p-4, p-6, p-8) - Colors: 60% neutral background, 30% text-foreground, 10% primary accent - Only 4 font sizes and 2 weights (semibold, regular) - Use Tailwind v4 utilities
Better prompt (with constraints):
Create a React component using shadcn/ui and Tailwind v4. DESIGN CONSTRAINTS: - Typography: Use ONLY text-4xl, text-2xl, text-base, text-sm with font-semibold or font-regular - Spacing: Use ONLY gap-2, gap-4, gap-6, gap-8, p-2, p-4, p-6, p-8, m-2, m-4, m-6, m-8 - Colors: Follow 60/30/10 rule (bg-background 60%, text-foreground 30%, bg-primary 10%) - Use className with Tailwind utilities, no custom CSS Component: [Your specific component request here]
Running Gemini CLI
Interactive mode:
gemini # Select "Use Gemini API key" # Paste your prompt
Single query:
gemini "Create a button component with text-base font-semibold, p-4, bg-primary text-primary-foreground"
Workflow: Generate → Review → Integrate
- •
Generate code with Gemini CLI:
bashgemini "Create a login form with email and password inputs, using 4 font sizes, 8pt grid spacing, and shadcn/ui components"
- •
Copy the generated code to your component file
- •
Review against checklist:
- • Only 4 font sizes (text-4xl, text-2xl, text-base, text-sm)
- • Only 2 weights (font-semibold, font-regular)
- • Spacing divisible by 4 or 8 (8px, 16px, 24px, 32px, etc.)
- • Colors follow 60/30/10 rule
- • Uses shadcn/ui components
- • No custom CSS
- •
Fix any violations:
- •Replace non-compliant spacing: 5px → 4px, 11px → 12px
- •Remove extra font weights: font-medium → use text size instead
- •Adjust color distribution if needed
- •
Integrate into your project:
tsx// components/features/LoginForm.tsx import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' // [Paste generated code here after review]
Example: Generating a Dashboard Widget
Prompt:
Create a React component for a dashboard metric widget using shadcn/ui and Tailwind v4. REQUIREMENTS: - Display a number metric with label - Show trend indicator (up/down) - Typography: Use only text-2xl (metric), text-base (label), text-sm (description) - All spacing values must be 8, 16, 24, 32, 48 pixels (p-2, p-4, p-6, p-8, p-12) - Colors: bg-background, text-foreground, accent with bg-primary only on one element (trend indicator) - Include border and rounded corners using Tailwind Component name: MetricWidget
What you get back:
export function MetricWidget({ metric, label, trend }) {
return (
<div className="bg-background border border-border rounded-lg p-6 space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-regular text-muted-foreground">
{label}
</p>
<p className="text-2xl font-semibold text-foreground mt-2">
{metric}
</p>
</div>
<div className={`px-4 py-2 rounded ${trend > 0 ? 'bg-primary' : 'bg-destructive'}`}>
<span className="text-sm font-regular text-primary-foreground">
{trend > 0 ? '↑' : '↓'} {Math.abs(trend)}%
</span>
</div>
</div>
<p className="text-sm font-regular text-muted-foreground">
Last 30 days
</p>
</div>
)
}
After review: This code already follows all guidelines! Copy it directly into your project.
Bypassing Generated Code to Main Agent
Once generated and validated:
// In your main agent or build process
import { MetricWidget } from '@/components/features/MetricWidget'
// Use in your application
export default function Dashboard() {
return (
<div className="space-y-6">
<MetricWidget metric="2,345" label="Total Users" trend={12} />
<MetricWidget metric="$45,231" label="Revenue" trend={8} />
</div>
)
}
The main agent can:
- •Import and use the component directly
- •Test it against the design system
- •Request modifications if needed
- •Integrate into larger layouts
Dark Mode
Configuration
// Automatic dark mode detection
<html className="dark">
{/* Dark mode CSS variables apply */}
</html>
Testing Dark Mode
# Toggle dark mode in browser DevTools
# Or use Tailwind's class-based approach
<html className={theme === 'dark' ? 'dark' : ''}>
Code Review Checklist
Design Principles
- • Typography: Exactly 4 font sizes, only semibold/regular weights
- • Spacing: All values divisible by 8 or 4 (no arbitrary values)
- • Colors: 60% neutral, 30% complementary, 10% accent
- • Structure: Elements logically grouped with consistent gaps
Technical
- • Uses OKLCH color variables
- • Leverages @theme directive for variables
- • Components use proper data-slot attributes
- • Class Variance Authority for variants
- • Dark mode implemented consistently
- • Accessibility: Good contrast, keyboard nav, semantic HTML
Common Issues to Flag
- •❌ More than 4 font sizes
- •❌ Extra font weights (medium, bold, etc.)
- •❌ Spacing not divisible by 4 or 8 (5px, 7px, 11px, etc.)
- •❌ Random padding/margins (inconsistent grid)
- •❌ Overused accent colors (exceeding 10%)
- •❌ Poor contrast between text and background
- •❌ Unnecessary custom CSS (when Tailwind utilities exist)