Let's be honest about vibe coding
You tell Claude Code "build me a SaaS dashboard." Thirty seconds later you have a working app. Revenue chart, user table, activity feed. Everything functional.
And it looks like every other AI-generated app on the internet.
Spacing that doesn't breathe. Seven accent colors fighting for attention. Cards that float on the page with zero separation from the background. The components are all shadcn — the technical stack is fine. But the result is amateur.
I spent a week trying to fix this through prompts. "Use only one accent color." "Card background #FFF, page background #FAFAFA." "Smaller shadows." Every fix worked for one screen, then the next screen forgot.
Then it clicked: the model doesn't need better prompts. It needs a design brain.
Pro designers aren't using better components — they're using invisible rules
Ask a senior designer why their dashboard looks refined and they'll say things like:
"Never use pure black. Use
#2A2A2A.""One accent color per app. Everything else grayscale."
"Shadows at 4% opacity. If you can see it, it's already too much."
Nobody writes these down. They're baked into years of experience, invisible to outsiders — which means invisible to LLMs. No matter how many shadcn components Claude has in its training data, it has never been told when to use which.
So I sat down and extracted 69 of these rules from Toss, Stripe, Linear, and Vercel — the brands whose output I wanted Claude to imitate — and wrote them into a single DESIGN-LANGUAGE.md file that Claude reads automatically.
The 69 rules, organized
The rules cluster into six groups:
-
Color discipline (12 rules) —
#2A2A2Aas the refined black, 5-level grayscale (#2A → #3C → #6A → #7A → #9B), one accent color maximum, no pure white on pure white. -
Spatial rhythm (14 rules) — never repeat the same section type consecutively, alternate tall/compact,
2:1number-to-unit ratios (48pxvalue,24pxunit). -
Information hierarchy (9 rules) — card/background separation (
#FFFcard on#FAFAFApage) matters more than any border, density increases as you scroll down, top is big numbers, bottom is dense lists. -
Shadow & elevation (8 rules) — max
4%opacity, elevation is neverz-index, dark mode replaces shadows with borders. - Component variance (11 rules) — never 4 identical KPI cards, stagger content types (2 with trend, 1 with progress, 1 with comparison), avoid "filled form of sameness."
-
Motion & feedback (15 rules) —
200msnormal, spring for entrance,ease-outfor exit, hover lifts by6pxmax, tap scales to0.98.
Drop the file into a project, Claude reads it, Cursor reads it (via .cursorrules). Same prompt, same model — the output quality jumps.
One rule, unpacked
Take the #2A2A2A rule. Pure #000 on #FFF has a contrast ratio of 21:1 — the highest possible. Sounds like a win. But at that contrast the eye gets fatigued fast, and the text starts to feel harsh and heavy on a white page.
#2A2A2A drops the contrast to about 15:1. Still WCAG AAA for body text. But the page stops "vibrating." Every refined design system in the last decade — Apple, Linear, Vercel, Notion, Toss — uses a softened near-black for body text. Claude picks #000 by default because it's the highest-contrast option, which is technically correct and aesthetically wrong.
One rule. Fixes thousands of screens.
Now multiply that by 69.
Same component, three brand DNAs
The live demo up top is a real app — styleseed-demo.vercel.app. Click the Toss / Raycast / Arc switcher. Same chat component, three totally different brand identities — colors, radius, shadows, motion durations, gradients, even the phone frame's ambient light.
The switcher flips a single data-skin attribute on the wrapper. That's it. No conditional rendering, no theme provider, no re-mount.
How it actually works
Every visual value is a CSS variable scoped to [data-skin="..."]:
[data-skin="toss"] {
--brand: #3182F6;
--radius: 0.875rem;
--shadow-card: 0 1px 3px rgba(0,0,0,0.04);
--gradient-brand: linear-gradient(135deg, #3182F6, #4A90F7);
--duration-normal: 200ms;
}
[data-skin="raycast"] {
--brand: #FF4E8B;
--radius: 0.5rem;
--shadow-card: 0 0 0 1px rgba(255,255,255,0.05),
0 8px 24px rgba(0,0,0,0.4);
--gradient-brand: linear-gradient(135deg, #FF6363, #FF8E3C 30%,
#E84A8E 65%, #A855F7);
--duration-normal: 220ms;
}
Components never touch the raw values — only the tokens:
<motion.div
style={{
background: "var(--card)",
borderRadius: "var(--radius-xl)",
boxShadow: "var(--shadow-card)",
}}
/>
Swap the data attribute → the entire tree morphs in one frame. Framer Motion animates the transitions for free because the underlying CSS properties are inherited.
This is the trick the 69 rules depend on. Rules reference semantic tokens (--brand, --card, --shadow-card), not literal values. So the same rulebook works whether your app looks like Toss or Vercel or your client's weird purple brand.
Why this matters for vibe coders
The point of vibe coding is shipping fast. The problem with vibe coding is the output screams "I was made in 30 minutes."
StyleSeed closes that gap in two moves:
-
The 69 rules fix the judgment. Claude stops picking
#000for text andpy-4for everything. The output starts looking designed, not generated. - The token structure fixes the brand. Your MVP can ship with Toss skin today and re-skin to a client's brand tomorrow by swapping one file. Same codebase. Zero rewrites.
Both drop in with one command. Engine is brand-agnostic — the rules don't know what color your brand is.
Stack
- Next.js 16 (App Router, Turbopack)
-
Tailwind CSS v4 (the
@theme inlinesyntax makes token sharing trivial) - Framer Motion for the morph animation
- Radix under the components
- Free, MIT
Try it
- 🎬 Live demo: styleseed-demo.vercel.app
- ⭐ GitHub: bitjaru/styleseed
- 📚 11 Claude Code slash commands included (
/ss-page,/ss-review,/ss-audit, ...). Works with Cursor.
If you're vibe coding anything right now, drop the engine in and compare the output. Honest critique on the rules themselves very welcome — which one feels wrong? Which one's missing? The components are downstream. The rules are the actual product.

Top comments (0)