| name | frontend-design |
| description | Apply when building or auditing UI. Enforces design tokens, Shadcn-first patterns, alignment rules, and the "fun but functional" aesthetic for Recipe App components. |
Recipe App Frontend Design Skill
Purpose
This skill acts as the "Senior Frontend Designer" for the Recipe App. It enforces a strict alignment policy, ensures dark/light mode consistency, and maintains a "fun but functional" aesthetic.
1. Tech Stack (Strict)
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS v3.4+
- UI Library: Shadcn/UI (Radix Primitives)
- Rule: All base UI components MUST use Shadcn/UI — never build from scratch
- Use the
shadcnMCP server to search/install components:mcp__shadcn__search_items_in_registries
- Icons: Lucide React (Use
stroke-width={1.5}for elegance,2for active states) - Motion: Framer Motion (for "fun & engaging" micro-interactions)
2. Design System Tokens
Rule: Always use CSS variables — never hardcode color values.
For the complete token reference including values, Tailwind classes, and usage examples, see tokens.md.
Typography & Shape
- Font:
Geist Sans/Geist Mono - Radius: Cards
rounded-xl, Buttonsrounded-md, Badgesrounded-full
3. The "Alignment & Uniformity" Mandate (Critical)
The user hates misalignment. Follow these rules strictly:
- Vertical Rhythm: Use standard Tailwind spacing scales (
gap-2,gap-4,py-6). Never use arbitrary values likemt-[3px]. - Icon + Text: ALWAYS align icons with text visually.
- Bad:
<div><Icon /> Text</div> - Good:
<div className="flex items-center gap-2"><Icon className="h-4 w-4" /> <span>Text</span></div>
- Bad:
- Grid Consistency: When displaying recipe cards, use
grid-cols-1 md:grid-cols-2 lg:grid-cols-3withgap-6to ensure cards are equal width. - Touch Targets: Ensure clickable elements are at least 44px height (
h-11) on mobile for "fun & engaging" tactility.
4. "Fun & Engaging" Vibe Guidelines
To make the app feel alive without breaking utility:
- Hover States: Every interactive element must have a hover state.
- Tailwind:
hover:bg-primary/90,transition-colors,active:scale-95 - Custom CSS: Use
var(--primary-hover)for explicit hover color control.
- Tailwind:
- Empty States: Never leave a blank page. Use a Lucide icon + friendly text (e.g., "No recipes found yet! Time to cook something up?").
- Images: Use
aspect-squareoraspect-videoconsistently. Always addoverflow-hiddento image containers so zoom effects on hover don't spill out.
5. Responsive Dimensions (Critical)
Rule: Never use fixed pixel values for layout dimensions. Use responsive alternatives.
❌ Prohibited Patterns
| Pattern | Problem |
|---|---|
w-[300px], h-[120px] |
Fixed pixels don't scale with viewport |
grid-cols-[1fr_300px] |
Fixed column widths break responsiveness |
max-h-[300px] |
Fixed heights cause scroll issues on smaller screens |
✅ Approved Alternatives
| Use Case | Bad | Good |
|---|---|---|
| Sidebar widths | w-[300px] |
w-[clamp(200px,25%,400px)] |
| Card heights | h-[120px] |
aspect-[4/3] or min-h-24 |
| Scrollable areas | max-h-[300px] |
max-h-[40vh] or flex-1 overflow-auto |
| Grid columns | grid-cols-[1fr_300px] |
grid-cols-[1fr_clamp(200px,30%,350px)] |
| Thumbnails | w-[80px] h-[80px] |
w-20 h-20 (Tailwind preset) or w-20 aspect-square |
When Fixed Values ARE Acceptable
- Tailwind preset sizes (
w-20,h-24, etc.) — semantic and consistent - Icon sizes (
h-4 w-4,h-6 w-6) — standardized across the app - Min/max bounds in clamp() — guardrails, not fixed sizes
- Viewport-relative units (
vh,vw) — scales with screen size
Responsive Sizing Priority
Prefer these approaches in order:
- Aspect ratios (
aspect-square,aspect-[3/1]) — scales proportionally - Viewport units (
max-h-[40vh]) — adapts to screen size - Clamp with bounds (
clamp(min, preferred, max)) — flexible with guardrails - Tailwind presets (
w-20,h-24) — consistent, semantic - Fixed pixels — LAST RESORT, requires justification in comments
6. Motion & Animation Guidelines
Rule: Use animation tokens for consistent timing. Never use arbitrary durations.
Duration Tokens
| Token | Value | Use Case |
|---|---|---|
--duration-instant |
100ms | Button press, toggle |
--duration-fast |
150ms | Hover states, micro-feedback |
--duration-normal |
300ms | Page transitions, modals |
--duration-slow |
500ms | Complex animations, emphasis |
Easing Tokens
| Token | Use Case |
|---|---|
--ease-default |
General purpose, smooth deceleration |
--ease-in |
Exit animations |
--ease-out |
Enter animations |
--ease-bounce |
Playful interactions, confirmations |
Standard Patterns
| Element | Animation | Implementation |
|---|---|---|
| Button hover | Color shift | transition-colors duration-150 |
| Button press | Scale down | active:scale-95 transition-transform duration-100 |
| Card hover | Subtle lift | hover:shadow-lg transition-shadow duration-200 |
| Modal enter | Fade + scale | animate-scale-in (custom class) |
| Page transition | Fade in | animate-fade-in (custom class) |
| Loading spinner | Continuous | animate-spin (Tailwind built-in) |
Custom Animation Classes
These utility classes are defined in globals.css:
.animate-fade-in/.animate-fade-out.animate-slide-up/.animate-slide-down.animate-scale-in
Framer Motion Conventions
For complex animations, use Framer Motion with these defaults:
duration: 0.3(matches--duration-normal)ease: [0.4, 0, 0.2, 1](matches--ease-default)- Use
AnimatePresencefor exit animations - Prefer
motion.divwithinitial,animate,exitprops
7. Weight System & Interactive Utilities
Rule: Use weight utilities for tactile, physical-feeling interactions. Don't apply to static elements.
The Weight System creates depth hierarchy and tactile feedback. These utilities are defined in globals.css.
Interactive Composites
Choose the right interaction pattern for clickable elements:
| Utility | Effect | Use When |
|---|---|---|
pressable |
Shrinks on click (scale 0.97) | Simple tactile feedback needed |
liftable |
Rises on hover + deeper shadow | Cards/panels that feel "pickable" |
interactive |
Lift on hover + press on click | Primary interactive cards |
bouncy |
Playful elastic hover/press | Fun, emphasis elements |
interactive-subtle |
Gentler lift/press | Small buttons, list items |
Decision Tree:
Is this element clickable?
└─ No → Don't use interactive utilities
└─ Yes → What type of element?
├─ Recipe card / content card → `interactive` or `liftable`
├─ Small button / list item → `interactive-subtle`
├─ Fun/playful element → `bouncy`
└─ Just needs click feedback → `pressable`
Example:
// Interactive recipe card
<div className="bg-card rounded-xl overflow-hidden interactive">
<img src={recipe.image} />
<h3>{recipe.title}</h3>
</div>
// Subtle list item
<li className="flex items-center gap-2 p-2 rounded-md interactive-subtle">
<Icon /> {item.name}
</li>
Surface Classes
Surface classes combine background, border, and shadow for consistent depth hierarchy:
| Utility | Components | Use Case |
|---|---|---|
surface-base |
bg + border | Page-level containers |
surface-raised |
elevated bg + border + raised shadow | Standard cards |
surface-elevated |
elevated bg + strong border + elevated shadow | Emphasized panels |
surface-floating |
popover bg + strong border + floating shadow | Dropdowns, tooltips, modals |
Hierarchy (bottom to top):
surface-base → Background level
↓
surface-raised → Cards, panels
↓
surface-elevated → Emphasized content
↓
surface-floating → Popovers, modals
Button Weight Utilities
For enhanced button feedback beyond standard Shadcn styling:
| Utility | Effect | Use When |
|---|---|---|
button-weighted |
Raised shadow, lift on hover, inset on press | Primary/secondary CTAs needing extra presence |
button-bouncy |
Spring effect, scale on hover/press | Playful, fun CTAs |
Example:
// Weighted primary button
<Button className="button-weighted">Save Recipe</Button>
// Bouncy fun button
<Button className="button-bouncy">Let's Cook!</Button>
Scrollbar Utilities
For cleaner scroll experiences:
| Utility | Effect | Use When |
|---|---|---|
scrollbar-hidden |
Hide scrollbar, keep scroll | Modals, horizontal carousels |
scrollbar-overlay |
Overlay scrollbar (no layout shift) | Sidebars, long lists |
Navigation Utilities
| Utility | Effect | Use When |
|---|---|---|
nav-dot-pulse |
Pulsing animation | Notification indicator dots |
8. Workflow for Generating Components
When asked to create or fix a UI component:
- Check Shadcn First: Before building anything, search the Shadcn registry for an existing component.
- Use
mcp__shadcn__search_items_in_registriesto find components - Use
mcp__shadcn__get_item_examples_from_registriesto see usage patterns - Install with
npx shadcn@latest add <component>
- Use
- Choose the Right Variant: See component-usage.md for decision trees on:
- Button variants (primary vs secondary vs destructive vs ghost)
- Badge variants (when to use which color)
- Card patterns (which surface + radius to use)
- Icon sizing rules
- Color application guidelines
- Check Context: Is this a Page (
page.tsx) or a Component (/components/uior/_components)? - Scaffold: Extend Shadcn components using the pattern (
forwardRef,cn()utility). - Choose the Right Surface:
bg-background— Page-level backgroundsbg-cardorbg-elevated— Raised cards and panelsbg-popover— Dropdowns, tooltips, menusbg-sidebar— Sidebar areas
- Apply Dark/Light Mode:
- Use
text-foregroundfor primary text,text-mutedfor secondary. - Check: Will this border be visible in Light Mode? (Use
border-borderorborder-input).
- Use
- Recipe Badges: Use the utility classes for consistency:
.recipe-category-badge— Purple (primary).recipe-meal-type-badge— Teal (secondary).recipe-dietary-badge— Gray (accent)
- Align & Polish:
- Verify
flexalignments. - Add
transition-all duration-200to interactive elements.
- Verify
- Self-Correction Question: "Are the icons aligned with the text? Is the spacing uniform? Does it look good in Light Mode?"
9. Auditing Existing Pages
When reviewing or revising an existing page, follow the comprehensive checklist in audit-checklist.md.
10. Anti-Patterns
See the Anti-Patterns section in component-usage.md for common mistakes to avoid.
11. Theme Mode
- Dark mode is the DEFAULT — defined in
:root - Light mode is opt-in — activated via
:root.lightclass on<html> - All CSS variables automatically adapt between modes — no conditional styling needed
- Test both modes when creating new components