v0.dev's 9 Default Patterns (And How to Override Each One)
v0 ships the same tokens to everyone: Inter, #3B82F6, rounded-lg, a 3-column grid. Here are all nine defaults and the exact CSS to override each one.
Generate "a SaaS landing page" in v0 five times and the diff is mostly the copy. The shell is identical: Inter on everything, --primary: 221.2 83.2% 53.3% (that's #3B82F6), rounded-lg on every component, a md:grid-cols-3 feature section, a centered hero with an eyebrow badge.
These aren't bad choices. They're Vercel's design-system defaults wired through shadcn/ui, and they're technically clean. The problem is that everyone who types a prompt gets the exact same tokens. Two hundred v0 projects later, your landing page is recognizable at a glance as a v0 output — the same way a Bootstrap site was recognizable in 2014. (We rated the tool itself in the v0.dev honest review; this is the fix list.)
Here are the 9 most common v0 defaults and the exact code to override each one.
Pattern 1: Inter as the only font
/* What v0 outputs in globals.css */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
/* applied to both headings and body */The problem: Inter is the most common typeface on the web, and v0 uses it for headings *and* body. One font doing both jobs produces text that looks like a settings page, not a product. (More on why in Inter is killing your brand.)
The fix: Pair a display font for headings with a different sans for body.
/* Override in globals.css */
@import url('https://fonts.bunny.net/css?family=playfair-display:400,700,800&family=karla:400,500,600');
:root {
--font-display: 'Playfair Display', Georgia, serif;
--font-sans: 'Karla', system-ui, sans-serif;
}
h1, h2, h3 { font-family: var(--font-display); }
body, p, li, td { font-family: var(--font-sans); }Other non-AI display fonts: Fraunces, Bitter, Cormorant Garamond, Vollkorn, Bricolage Grotesque, Archivo Black.
Pattern 2: Primary color in the AI band (hue 200-290)
/* What v0 outputs */
--primary: 221.2 83.2% 53.3%; /* hsl — maps to #3B82F6 */
/* Alternative: */
--primary: 262.1 83.3% 57.8%; /* purple — maps to #7C3AED */The problem: Both defaults sit in a 90-degree slice of the color wheel — the blue-to-violet band every AI tool reaches for. Every site built on it shares a brand with every other one. We break down why this exact range became the tell in the blue-purple gradient signature.
The fix: Pick a hue outside 200-290. Treat that band as off-limits.
:root {
--primary: 347 72% 48%; /* deep rose */
/* or: */
--primary: 22 85% 52%; /* burnt orange */
/* or: */
--primary: 156 60% 38%; /* forest green */
/* or: */
--primary: 320 65% 44%; /* magenta */
}Pattern 3: rounded-lg on everything
/* What v0 outputs — same radius everywhere */
<Button className="rounded-lg">...</Button>
<Card className="rounded-lg">...</Card>
<Input className="rounded-lg">...</Input>
<Badge className="rounded-lg">...</Badge>The problem: When every element shares the same 0.5rem corner, the page reads as kit-assembled. Border radius is a hierarchy signal — spending it uniformly throws it away.
The fix: Two independent radius values, one for interactive elements and one for containers.
:root {
--radius: 0.25rem; /* default (overrides shadcn) */
--radius-card: 0; /* sharp cards — editorial feel */
}
/* Or the inverse: */
:root {
--radius: 0.5rem; /* buttons rounded */
--radius-card: 1rem; /* cards very rounded */
}Never set both to the same value.
Pattern 4: 3-column feature grid with equal columns
/* What v0 generates for features */
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<FeatureCard />
<FeatureCard />
<FeatureCard />
</div>The problem: Three identical columns signal template. Users recognize this as "the features section" before they read a word, and that recognition is what stops them reading. We measured the conversion cost in why 3 identical cards kill conversion.
The fix: Asymmetric layout with the main feature given more space.
/* Asymmetric grid — the primary feature is larger */
<div style={{ display: 'grid', gridTemplateColumns: '5fr 3fr', gap: '3rem 4rem' }}>
<div>
{/* Primary feature — full description */}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
{/* 2-3 secondary features stacked */}
</div>
</div>Alternatively, use a definition list (dl/dt/dd) instead of cards. More semantic, less template-looking.
Pattern 5: Centered hero with eyebrow badge
/* What v0 always generates */
<section className="text-center py-24">
<Badge>New feature</Badge>
<h1>The Future of Your Workflow</h1>
<p className="max-w-2xl mx-auto">AI-powered platform...</p>
<div className="flex justify-center gap-4">
<Button>Get Started</Button>
<Button variant="outline">Learn More</Button>
</div>
</section>The problem: This is the single most replicated hero pattern of 2026. Eyebrow badge + centered h1 + centered subtitle + two-button group — recognizable at a glance as AI-generated. There are 21 compositions that aren't this one.
The fix: Left-aligned text with offset layout. No eyebrow badge. No centering.
<section style={{ paddingBlock: '120px 80px', paddingLeft: '13%' }}>
<h1 style={{ fontFamily: 'var(--font-display)', fontSize: 'clamp(3rem, 8vw, 6rem)', lineHeight: 1.05, textWrap: 'balance' }}>
Your site looks like<br />every other AI site.
</h1>
<p style={{ marginTop: '2rem', maxWidth: '480px', fontSize: '18px', lineHeight: 1.7 }}>
Sailop detects the patterns and rewrites them.
</p>
<a href="/install" style={{ marginTop: '3rem', display: 'inline-block' }}>
npx sailop install →
</a>
</section>Pattern 6: Fade-up animation on everything
/* What v0 generates (usually via a custom hook) */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-in { animation: fadeInUp 0.3s ease-in-out; }The problem: translateY(20px) to 0 is the #1 AI animation default. Fired on every section at the same 300ms duration, it reads as a library effect, not a design choice. (Motion slop covers the full pattern.)
The fix: Different animations per component type.
/* Headline: reveal from left */
@keyframes clipReveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
h1 { animation: clipReveal 0.7s cubic-bezier(0.16, 1, 0.3, 1); }
/* Features: slide from left with stagger */
@keyframes slideLeft {
from { opacity: 0; transform: translateX(-24px); }
to { opacity: 1; transform: translateX(0); }
}
.feature:nth-child(1) { animation: slideLeft 0.5s 0.1s both; }
.feature:nth-child(2) { animation: slideLeft 0.5s 0.25s both; }
.feature:nth-child(3) { animation: slideLeft 0.5s 0.4s both; }
/* CTA: scale up */
@keyframes scaleUp {
from { opacity: 0; transform: scale(0.94); }
to { opacity: 1; transform: scale(1); }
}
.cta { animation: scaleUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); }Pattern 7: Pure white background + pure black text
/* What v0 outputs */
--background: 0 0% 100%; /* #ffffff */
--foreground: 222.2 84% 4.9%; /* near-black */The problem: Pure #ffffff reads as default — nobody chose it. And #0a0a0a text on it runs ~19:1 contrast, well past the 7:1 of WCAG AAA, which is why it looks harsh rather than crisp.
The fix: Slightly warm white with two shades of text.
:root {
--background: 35 20% 96%; /* warm off-white */
--foreground: 220 15% 12%; /* dark blue-grey, not pure black */
--muted-foreground: 220 10% 40%; /* secondary text */
}Pattern 8: "Get Started" and "Learn More" CTAs
The problem: These are the two most common CTA strings on the web, and they're on the banned-phrases list for a reason — they tell the user nothing about what happens on click.
The fix: Specific action language.
| Replace | With | |---------|------| | "Get Started" | "Scan your code" / "npx sailop install" | | "Learn More" | "See how it works" / "Read the docs" | | "Start Free Trial" | "Try it free — no card" | | "Book a Demo" | "See a 3-minute walkthrough" | | "Sign Up Now" | "Create account" |
The rule: name the action.
Pattern 9: Uniform shadow values
/* What v0 applies to cards */
box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
/* shadcn shadow-sm — used on 90% of v0 cards */The problem: When every card carries the same shadow, depth flattens. Shadows are an elevation signal — one value for everything is a monotone.
The fix: Vary shadow by elevation level, or drop it for borders.
/* Two-level shadow system */
.card-low { box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
.card-raised { box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
/* Or: no shadow, use border */
.card { border: 1px solid hsl(220, 15%, 88%); box-shadow: none; }
/* Or: directional shadow (looks designed) */
.card { box-shadow: 3px 3px 0 hsl(347, 72%, 48%); }Run it through Sailop
The fastest way to catch all nine in a v0 project is to scan it:
sailop scan ./srcEach pattern maps to a specific Sailop rule. The scan reports the exact files and lines affected, with a per-dimension score. Fix the F grades first — most v0 projects score worst on color and typography. The transform command rewrites all nine automatically and generates a unique design system for the project.
SHIP CODE THAT LOOKS INTENTIONAL
Scan your frontend for AI patterns. Generate a unique design system. Stop shipping the same blue gradient as everyone else.