Solo developers who use Material-UI ship faster than those who build from scratch. They also spend weeks fighting generic aesthetics that signal "bootstrapped template" to every visitor who lands on their product.
The layered stack below solves both problems: Tailwind CSS handles utilities, FramingUI adds your design token layer, shadcn/ui gives you copy-paste components you own entirely. Setup takes a few hours. After that, it runs itself.
Why These Three Tools Together
Tailwind gives you 1,000 styling options. That freedom causes drift. FramingUI narrows those options to the set your brand actually uses — a specific neutral scale, two brand colors, one spacing rhythm — and turns those decisions into CSS variables. shadcn/ui takes Radix UI primitives and gives you accessible, production-ready components you copy directly into your codebase.
The key property of shadcn: you own the source. There's no npm package to fight. When you need to change how a Button looks, you edit components/ui/button.tsx directly. No theme override system, no !important hacks.
The combination means AI tools like Cursor and Claude generate on-brand output by default. Your token names — --foreground-accent, --background-page — appear in the context, not arbitrary palette values.
Layer 1: Tailwind CSS
Utility classes eliminate naming decisions. You stop arguing with yourself about whether to write .card-header-title or .content-heading. The bundle only includes classes you actually use.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
This is the foundation. Every other layer builds on top.
Layer 2: FramingUI
FramingUI sits between Tailwind and your components. It converts your design decisions into CSS variables that every component can consume.
Install with pnpm:
pnpm add @framingui/ui @framingui/core @framingui/tokens tailwindcss-animate
The init command wires the full runtime contract automatically — provider bootstrap, global stylesheet import, and MCP connection for AI tooling:
npx -y @framingui/mcp-server@latest init
Your tokens become CSS variables like var(--foreground-accent) and var(--background-page). Changing your brand color means updating the token definition, not hunting through component files.
Layer 3: shadcn/ui
npx shadcn@latest init
This prompts you for style preferences and base color. Choose CSS variables for maximum compatibility with your FramingUI token layer.
Add components as you need them:
npx shadcn@latest add button input label card dialog form
Each component lands in components/ui/ in your codebase. You own it completely. The components come with full keyboard navigation and screen reader support because they're built on Radix UI primitives.
Layer 4: Your Product Components
Generic components get you 80% there. The remaining 20% is the UI that makes your product recognizable — pricing cards, analytics displays, domain-specific data views.
These live in components/features/ and compose from layers below:
import { Button } from '@/components/ui/button';
export function PricingCard({ plan, price, features }: PricingCardProps) {
return (
<div className="bg-[var(--background-page)] border border-[var(--border-default)] rounded-lg p-[var(--spacing-6)]">
<h3 className="text-2xl font-bold text-[var(--foreground-primary)]">{plan}</h3>
<p className="text-4xl font-bold text-[var(--foreground-accent)] my-[var(--spacing-4)]">
${price}/mo
</p>
<ul className="space-y-[var(--spacing-2)] mb-[var(--spacing-6)]">
{features.map((f) => (
<li key={f} className="text-[var(--foreground-secondary)]">
{f}
</li>
))}
</ul>
<Button className="w-full">Get Started</Button>
</div>
);
}
Every value references a token. Rebranding this component means changing the tokens, not the component.
Three Things That Break This Stack
Customizing shadcn components immediately. Install them, use them for a month, then customize only when you have a clear repeated need. Early customization loses upstream bug fixes.
Skipping token definition. Decide your brand colors, neutral scale, and spacing rhythm before building your first component. Inconsistency without upfront tokens is not fixable by discipline alone.
Over-engineering before shipping. Storybook, component tests, and monorepo structure are useful at scale. They cost weeks when you need to ship. Build your product first. Add tooling when you feel specific pain.
Accessibility Without Extra Work
shadcn components are accessible by default. Dialog manages focus trapping and escape key handling. Form components wire label associations and error announcements automatically. Checkbox and Switch respond to keyboard correctly.
You get full keyboard navigation without implementing it yourself. The main thing to add: aria-label on icon-only buttons.
The entire stack costs nothing and ships in days rather than weeks. That's the case for using it.