Bolt.new's 7 Signature Patterns (And How to Fix Each One)
Bolt.new ships the same #667eea→#764ba2 hero, Inter-everything type, and "Get Started Free" CTA in nearly every project. Here are the seven tells and the exact fix for each.
Open any Bolt.new project and check the hero background. Nine times out of ten it's linear-gradient(135deg, #667eea 0%, #764ba2 100%) — the same two hex values, in the same order, at the same 135-degree angle. That gradient has shipped in so many projects it now functions as a watermark.
Bolt is genuinely fast: describe an app, and it scaffolds the whole stack — frontend, backend, database schema, auth — in one pass. The honest review holds up. The problem is the visual layer. Bolt reaches for the same template every time, and after enough projects you stop seeing variety and start seeing the same seven patterns. Here they are, with the exact fix for each.
Pattern 1: The purple gradient hero
What Bolt generates:
.hero {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* hue ~248-280 — deep in the AI color band */
}#667eea is hue 229; #764ba2 is hue 271. Both sit inside the blue-purple band that marks a site as AI-generated. Users have learned to read it as "AI product" without being able to say why. The angle never changes either — 135deg, top-left to bottom-right, every time.
The fix:
/* Replace with a gradient outside the AI band */
.hero {
background: linear-gradient(160deg, hsl(347, 72%, 48%) 0%, hsl(347, 60%, 35%) 100%);
/* or: solid color + subtle texture */
background: hsl(347, 72%, 48%);
background-image: url("data:image/svg+xml,..."); /* SVG noise texture */
}Pick a hue you can defend — see how to choose an accent that isn't Tailwind blue. Or drop the gradient entirely. A solid hero, or a near-white hero carried by type, reads more current in 2026 than any two-stop fade.
Pattern 2: Inter font with no display variant
What Bolt generates:
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* headings use the same font — no separation */One typeface, every size. Headings and body share the same metrics, so nothing reads as a "voice." Inter is fine; Inter as the only decision is the tell.
The fix:
@import url('https://fonts.bunny.net/css?family=fraunces:300,400,700ital&family=work-sans:400,500,600');
:root {
--font-display: 'Fraunces', Georgia, serif;
--font-body: 'Work Sans', system-ui, sans-serif;
}
h1, h2, h3, h4 { font-family: var(--font-display); }
body, p, li, button { font-family: var(--font-body); }Fraunces is optically variable — it ships an opsz axis, so headlines at 48px get tighter, more dramatic letterforms than the same font at 14px. Paired with Work Sans for body, the contrast reads as designed instead of defaulted.
Pattern 3: Equal-column feature grid
What Bolt generates:
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{features.map(feature => (
<div className="bg-white rounded-xl p-6 shadow-lg">
<feature.icon className="h-8 w-8 text-purple-600 mb-4" />
<h3 className="font-semibold mb-2">{feature.title}</h3>
<p className="text-gray-600">{feature.description}</p>
</div>
))}
</div>Three identical cards: same h-8 w-8 icon, same structure, same visual weight. The three-identical-cards layout tells visitors no human chose what to emphasize.
The fix:
{/* Asymmetric layout: primary feature gets more space */}
<div style={{ display: 'grid', gridTemplateColumns: '3fr 5fr', gap: '2rem' }}>
<dl style={{ display: 'flex', flexDirection: 'column', gap: '2.5rem' }}>
{primaryFeatures.map(f => (
<div key={f.title}>
<dt style={{ fontWeight: 600, marginBottom: '0.4rem' }}>{f.title}</dt>
<dd style={{ color: 'var(--fg-muted)', lineHeight: 1.65 }}>{f.desc}</dd>
</div>
))}
</dl>
<aside>
{/* Visual demo, code block, or screenshot */}
</aside>
</div>The is more semantic than a div soup, and the 3fr 5fr split stops it reading as a card template.
Pattern 4: Fade-up scroll animation on every section
What Bolt generates (usually via a custom hook or library):
// useScrollReveal.ts — appears in most Bolt projects
const useScrollReveal = () => {
// IntersectionObserver adds 'visible' class
// CSS: .reveal { opacity: 0; transform: translateY(20px); transition: 0.3s ease; }
// CSS: .reveal.visible { opacity: 1; transform: translateY(0); }
}Every section gets the same translateY(20px), the same 0.3s ease, the same trigger. That uniform fade-in-up is its own slop signature — once you've seen it, you see it everywhere.
The fix: Vary the motion by what's moving. Bolt's hook already adds a .visible class, so you only touch the CSS:
/* Hero headline: clip from left */
.hero-title { clip-path: inset(0 100% 0 0); transition: clip-path 0.7s cubic-bezier(0.16, 1, 0.3, 1); }
.hero-title.visible { clip-path: inset(0 0 0 0); }
/* Features list: slide from left with stagger */
.feature { opacity: 0; transform: translateX(-20px); transition: opacity 0.5s, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
.feature.visible { opacity: 1; transform: translateX(0); }
.feature:nth-child(2) { transition-delay: 0.1s; }
.feature:nth-child(3) { transition-delay: 0.2s; }
/* CTA: scale up */
.cta-section { opacity: 0; transform: scale(0.96); transition: 0.6s ease-out; }
.cta-section.visible { opacity: 1; transform: scale(1); }Three components, three easings. And wrap it in @media (prefers-reduced-motion: reduce) { * { transition: none } } — Bolt's default skips that, which is its own tell.
Pattern 5: Full-width centered CTA section
What Bolt generates:
<section className="bg-gradient-to-r from-purple-600 to-indigo-600 py-20">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-4xl font-bold text-white mb-4">Ready to get started?</h2>
<p className="text-xl text-purple-100 mb-8">Join thousands of users...</p>
<button className="bg-white text-purple-600 px-8 py-4 rounded-full font-bold">
Get Started Free
</button>
</div>
</section>Full-width purple gradient, everything centered, "Ready to get started?" above "Join thousands of users." It's the most templated closing section on the web.
The fix: structural asymmetry and language a human would actually write.
<section style={{ borderTop: '1px solid var(--border)', paddingBlock: '80px 100px' }}>
<div style={{ maxWidth: '700px' }}>
<h2 style={{ fontFamily: 'var(--font-display)', fontSize: 'clamp(2rem, 5vw, 3rem)', lineHeight: 1.1, textWrap: 'balance' }}>
Your code scores D on design originality.
</h2>
<p style={{ marginTop: '1.5rem', fontSize: '17px', lineHeight: 1.7, color: 'var(--fg-muted)' }}>
Scan it. See exactly what's flagged. Fix it in one command.
</p>
<a href="/install" style={{ marginTop: '2.5rem', display: 'inline-block', fontFamily: 'var(--font-mono)', fontSize: '15px', borderBottom: '1px solid currentColor', paddingBottom: '2px' }}>
npx sailop install →
</a>
</div>
</section>Left-aligned, specific, no gradient, no "thousands of users."
Pattern 6: Identical testimonial cards
What Bolt generates:
<div className="grid grid-cols-3 gap-6">
{testimonials.map(t => (
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center mb-4">
<img className="w-10 h-10 rounded-full" src={t.avatar} />
<div className="ml-3">
<p className="font-semibold">{t.name}</p>
<p className="text-gray-500 text-sm">{t.role}</p>
</div>
</div>
<p className="text-gray-600">{t.quote}</p>
<div className="flex mt-4">
{'★★★★★'}
</div>
</div>
))}
</div>Three cards, same shadow-md, five stars on every one. Nobody believes a wall of unanimous five-star quotes — it reads as filler, not proof.
The fix: kill the grid. One specific, attributed quote reads as evidence; three identical ones read as generated.
<figure style={{ maxWidth: '600px', borderLeft: '3px solid var(--brand)', paddingLeft: '2rem' }}>
<blockquote style={{ fontFamily: 'var(--font-display)', fontSize: '1.4rem', lineHeight: 1.4, fontStyle: 'italic' }}>
"{testimonial.quote}"
</blockquote>
<figcaption style={{ marginTop: '1rem', fontSize: '14px', color: 'var(--fg-muted)' }}>
— {testimonial.name}, {testimonial.role} at {testimonial.company}
</figcaption>
</figure>The // markup is also what screen readers and search crawlers expect from a real quotation.
Pattern 7: "Get Started Free" and "Learn More" CTAs
What Bolt generates:
- Primary: "Get Started Free"
- Secondary: "Learn More"
The two most common button strings on the web. Combined with the #667eea gradient above them, they announce the site was generated before a visitor reads a word.
The fix: language tied to what actually happens when you click.
| Replace | With | |---------|------| | "Get Started Free" | "Scan your code free" | | "Learn More" | "See a 3-minute walkthrough" | | "Start Building" | "Run your first scan" | | "Try for Free" | "npx sailop install" |
The rule: name the action, not the permission to take the action. "Learn More" promises nothing; "See a 3-minute walkthrough" tells the visitor exactly what the next 180 seconds cost them.
Scanning Bolt projects
Bolt exports standard React + Vite or Next.js, so the output scans like any other repo:
# Export from Bolt → unzip → scan
sailop scan ./src
# Common Bolt output scores:
# Color: D (gradient hue 248-270)
# Typography: F (Inter only)
# Layout: C (grid-cols-3 × 2-3)
# Animation: D (fade-up uniform)
# Slop score: 72/100 → Grade Dsailop transform rewrites all seven patterns at once. On Bolt projects, fix color and typography first — Patterns 1 and 2 move the slop score more than the other five combined, because they're the two tells a visitor clocks in the first second.
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.