Skip to content
Lesson 10 of 22

Frontend Patterns with AI

13 min read

Thinking in Components

Every modern UI is built from components — small, reusable pieces that snap together like building blocks. A navigation bar is a component. A button is a component. A card showing a user profile is a component. Even the entire page layout is a component made of smaller components.

This mental model is your secret weapon when working with AI. Instead of asking for an entire page at once, you break the UI into pieces and describe each one. The AI builds them individually, and you compose them into something bigger.

Atomic Design for Vibe Coders

Brad Frost's atomic design gives us a helpful vocabulary:

  • Atoms — the smallest pieces: buttons, inputs, labels, icons, badges
  • Molecules — small groups of atoms: a search bar (input + button), a form field (label + input + error message)
  • Organisms — complex groups: a navigation bar, a hero section, a pricing table
  • Templates — page-level layouts that arrange organisms into a structure
  • Pages — templates filled with real content

When you prompt AI, you're usually working at the molecule and organism level. Here's how the thinking goes:

"Build me a pricing table"  →  organism (good prompt scope)
"Build me a pricing card"   →  molecule (even more focused)
"Build me a button"         →  atom (sometimes useful for establishing variants)

The sweet spot is describing one organism at a time, then composing them. You'll build faster and get higher quality results than asking for everything at once.

Describing Components to AI

The better your description, the better the output. Compare these two prompts:

Vague: "Make a card component."

Specific: "Create a feature card component with an icon at the top (using Lucide React icons), a title in semibold text, a two-line description in muted gray, and a subtle border with rounded corners. The card should have a hover effect that lifts it slightly with a shadow."

The second prompt gives AI everything it needs: structure, styling details, behavior, and even the icon library. This is the vibe coding way — you think about what you want, describe it clearly, and AI handles the implementation.

React Component Patterns for Vibe Coders

You don't need to master React syntax to vibe code. But understanding the core patterns helps you read what AI generates and describe what you want more effectively.

Functional Components and Props

Every React component is a function that returns JSX (HTML-like syntax). Props are inputs to the component:

function FeatureCard({ icon, title, description }: {
  icon: React.ReactNode
  title: string
  description: string
}) {
  return (
    <div className="p-6 border rounded-xl hover:shadow-lg transition-shadow">
      <div className="mb-4 text-blue-500">{icon}</div>
      <h3 className="text-lg font-semibold mb-2">{title}</h3>
      <p className="text-gray-600">{description}</p>
    </div>
  )
}

When prompting AI, you can say: "Create a FeatureCard component that accepts icon, title, and description as props." AI knows what to do with that.

Children and Composition

The children prop lets components wrap other components:

function Card({ children }: { children: React.ReactNode }) {
  return (
    <div className="p-6 border rounded-xl shadow-sm">
      {children}
    </div>
  )
}

// Usage
<Card>
  <h3>Any content goes here</h3>
  <p>The Card just provides the container styling</p>
</Card>

This is the composition pattern — building complex UIs by nesting simple components. When you tell AI "create a Card wrapper component that accepts children," it generates exactly this pattern.

Conditional Rendering

Sometimes you want to show or hide elements based on data:

function UserGreeting({ user }: { user: User | null }) {
  return (
    <div>
      {user ? (
        <p>Welcome back, {user.name}!</p>
      ) : (
        <button>Sign In</button>
      )}
    </div>
  )
}

Prompt pattern: "Show the user's name if logged in, otherwise show a sign-in button." AI translates your intent into conditional rendering automatically.

Tailwind CSS Crash Course

Tailwind CSS is the most AI-friendly styling approach because it's descriptive. Instead of writing CSS in separate files, you apply utility classes directly to your HTML elements.

The Utility-First Approach

Traditional CSS:

.card {
  padding: 1.5rem;
  border-radius: 0.75rem;
  background-color: white;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

Tailwind equivalent:

<div class="p-6 rounded-xl bg-white shadow-sm">

Every Tailwind class does one thing. You compose them to create any design. Here are the classes you'll see most often:

Layout: flex, grid, grid-cols-3, gap-4, items-center, justify-between, w-full, max-w-7xl, mx-auto

Spacing: p-4 (padding), m-4 (margin), px-6 (horizontal padding), py-3 (vertical padding), space-y-4 (vertical gap between children)

Typography: text-lg, text-2xl, font-bold, font-semibold, text-gray-600, text-center, leading-relaxed

Colors: bg-blue-500, text-white, border-gray-200, bg-gradient-to-r, from-blue-600, to-purple-600

Effects: shadow-md, rounded-lg, opacity-75, hover:shadow-lg, transition-all

Responsive Prefixes

Tailwind is mobile-first. Unprefixed classes apply to all screens. Prefixed classes apply at that breakpoint and above:

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  • No prefix → mobile (all sizes)
  • sm: → 640px and up
  • md: → 768px and up
  • lg: → 1024px and up
  • xl: → 1280px and up
  • 2xl: → 1536px and up

Dark Mode

Tailwind's dark: prefix applies styles when dark mode is active:

<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">

When prompting AI, simply say "add dark mode support" and it will add dark: variants to the appropriate classes.

Describing Tailwind Styling to AI

You don't need to memorize classes. Describe what you want visually:

  • "A card with generous padding, rounded corners, and a subtle shadow" → p-6 rounded-xl shadow-sm
  • "Blue gradient background going left to right" → bg-gradient-to-r from-blue-600 to-blue-800
  • "Center everything horizontally with a max width" → max-w-7xl mx-auto

AI translates your visual language into Tailwind classes. That's the whole point.

Building Complete UI Sections

Let's walk through the most common UI patterns you'll build. For each one, I'll show the prompt you'd use and the kind of output you'll get.

Hero Sections

The hero is the first thing visitors see. It sets the tone.

Prompt: "Create a hero section with a gradient background from blue-600 to purple-600, white centered text with a large heading, a subtitle paragraph, and two CTA buttons — one solid white and one outlined. Make it responsive with more padding on larger screens."

function Hero() {
  return (
    <section className="bg-gradient-to-r from-blue-600 to-purple-600 py-20 md:py-32 px-4">
      <div className="max-w-4xl mx-auto text-center">
        <h1 className="text-4xl md:text-6xl font-bold text-white mb-6">
          Build Faster with AI
        </h1>
        <p className="text-lg md:text-xl text-blue-100 mb-10 max-w-2xl mx-auto">
          Ship production apps in days, not months. Let AI handle the code
          while you focus on the product.
        </p>
        <div className="flex flex-col sm:flex-row gap-4 justify-center">
          <button className="px-8 py-3 bg-white text-blue-600 rounded-lg font-semibold hover:bg-blue-50 transition-colors">
            Get Started Free
          </button>
          <button className="px-8 py-3 border-2 border-white text-white rounded-lg font-semibold hover:bg-white/10 transition-colors">
            Watch Demo
          </button>
        </div>
      </div>
    </section>
  )
}

Feature Grids

Feature grids showcase your product's capabilities. The pattern is a grid of cards, each with an icon, title, and description.

Prompt: "Create a 3-column feature grid with 6 feature cards. Each card has a colored icon container, a bold title, and a description. The grid should stack to 1 column on mobile and 2 on tablet."

This generates a responsive grid using grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 with individual cards inside.

Pricing Tables

Pricing tables are one of the trickiest UI patterns to get right. Here's how to prompt for them:

Prompt: "Create a pricing section with 3 tiers: Free, Pro ($19/mo), and Enterprise (custom pricing). The Pro plan should be highlighted as 'Most Popular' with a colored border and badge. Each plan shows a list of features with checkmarks. Add a CTA button at the bottom of each card."

The key detail is highlighting the popular plan — AI handles this with conditional styling, usually a colored border and a floating badge.

Testimonials

Prompt: "Create a testimonials section with 3 testimonial cards. Each card has a quote in italic text, the person's avatar (use a placeholder), their name, title, and company. Add quotation mark decorations."

Navigation is critical and needs to work on all screen sizes.

Prompt: "Create a responsive navbar with a logo on the left, navigation links in the center (hidden on mobile), and a CTA button on the right. On mobile, show a hamburger menu that toggles a slide-down menu with all links. Use React state for the mobile menu toggle."

This generates a component with useState for the mobile menu, a hamburger icon that toggles visibility, and responsive classes to show/hide elements.

Footers

Prompt: "Create a footer with 4 columns: Company (about, blog, careers), Product (features, pricing, docs), Resources (guides, API, community), and Legal (privacy, terms, cookies). Add social media icons at the bottom with a copyright line."

Forms and Validation

Forms are where users interact with your app. Getting them right matters for both UX and data integrity.

Prompt: "Create a contact form with name, email, message fields, and a submit button. Add client-side validation — name required, email must be valid format, message must be at least 20 characters. Show error messages below each field in red text. Disable the submit button while submitting."

function ContactForm() {
  const [errors, setErrors] = useState<Record<string, string>>({})
  const [isSubmitting, setIsSubmitting] = useState(false)

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    const newErrors: Record<string, string> = {}

    if (!formData.get("name")) newErrors.name = "Name is required"
    if (!formData.get("email")?.toString().includes("@"))
      newErrors.email = "Valid email is required"
    if ((formData.get("message")?.toString().length ?? 0) < 20)
      newErrors.message = "Message must be at least 20 characters"

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }

    setIsSubmitting(true)
    // Submit to API...
    setIsSubmitting(false)
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto">
      <div>
        <label className="block text-sm font-medium mb-1">Name</label>
        <input name="name" className="w-full px-4 py-2 border rounded-lg" />
        {errors.name && <p className="text-red-500 text-sm mt-1">{errors.name}</p>}
      </div>
      {/* Similar for email and message */}
      <button
        type="submit"
        disabled={isSubmitting}
        className="w-full py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
      >
        {isSubmitting ? "Sending..." : "Send Message"}
      </button>
    </form>
  )
}

The pattern is: controlled state for errors, validation in the submit handler, and visual feedback for the user.

Modals and Dialogs

Modals are overlays that demand user attention. They're common for confirmations, forms, and detail views.

Prompt: "Create a reusable Modal component that takes isOpen, onClose, title, and children props. It should have a semi-transparent backdrop, center the modal vertically and horizontally, close when clicking the backdrop or pressing Escape, and include a close button in the header. Add a fade-in animation."

Key accessibility details to mention in your prompts:

  • Close on Escape key press
  • Close on backdrop click
  • Trap focus inside the modal (so Tab doesn't go to elements behind it)
  • Use role="dialog" and aria-modal="true"

If you use a component library like shadcn/ui, you get all of this for free. More on that shortly.

Responsive Design with Prompts

Mobile-first means you design for small screens first, then add complexity for larger ones. When prompting AI, be explicit about breakpoints:

Prompt pattern: "On mobile, stack everything vertically with full width. On tablet (md), switch to a 2-column grid. On desktop (lg), use 3 columns with more horizontal padding."

You can also ask AI to adjust existing components:

  • "Make this hero section responsive — larger text and more padding on desktop"
  • "On mobile, turn this horizontal feature list into a vertical stack"
  • "Hide the sidebar on mobile and show a bottom navigation bar instead"

Testing responsive layouts is simple: resize your browser or use dev tools device emulation. When something looks off on a specific size, tell AI: "The pricing cards overlap on tablet. Fix the grid breakpoints."

Animations and Transitions

Subtle animations make your UI feel polished and alive.

CSS Transitions with Tailwind

Tailwind has built-in transition utilities:

<button class="bg-blue-500 hover:bg-blue-600 transition-colors duration-200">
  Hover me
</button>

<div class="hover:scale-105 transition-transform duration-300">
  Card that scales on hover
</div>

Common transition classes: transition-all, transition-colors, transition-transform, transition-opacity, duration-200, duration-300, ease-in-out.

Tailwind Animation Utilities

Tailwind ships with a few built-in animations:

  • animate-spin — loading spinners
  • animate-ping — notification dots
  • animate-pulse — skeleton loading states
  • animate-bounce — attention-grabbing elements

Framer Motion Basics

For complex animations, Framer Motion is the go-to library in React:

import { motion } from "framer-motion"

function FadeInCard({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  )
}

Prompt: "Add a fade-in-up animation to each feature card using Framer Motion. Stagger the animations so each card appears 0.1 seconds after the previous one."

AI handles Framer Motion well because the API is declarative — you describe the animation states, not the mechanics.

Dark Mode Implementation

Dark mode is expected in modern apps. Here's the cleanest implementation approach.

The Toggle Pattern

  1. Store the preference in localStorage and respect the system preference as default
  2. Add or remove a dark class on the <html> element
  3. Use Tailwind's dark: prefix for all dark mode styles
function ThemeToggle() {
  const [isDark, setIsDark] = useState(false)

  useEffect(() => {
    const stored = localStorage.getItem("theme")
    const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
    const dark = stored === "dark" || (!stored && prefersDark)
    setIsDark(dark)
    document.documentElement.classList.toggle("dark", dark)
  }, [])

  function toggle() {
    const newDark = !isDark
    setIsDark(newDark)
    document.documentElement.classList.toggle("dark", newDark)
    localStorage.setItem("theme", newDark ? "dark" : "light")
  }

  return (
    <button onClick={toggle} className="p-2 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700">
      {isDark ? "☀️" : "🌙"}
    </button>
  )
}

Prompt: "Add dark mode support to the entire app. Detect system preference, allow manual toggle, persist the choice in localStorage, and add dark: variants to all components."

Component Libraries vs. Custom

You don't always need to build from scratch. Component libraries give you accessible, tested components out of the box.

shadcn/ui

shadcn/ui is the most AI-friendly library because it copies components into your project as actual files you can customize. It's not a dependency — it's a collection of copy-paste components built on Radix UI and Tailwind.

npx shadcn@latest add button dialog card

Prompt: "Use shadcn/ui for the dialog component and style the content with Tailwind."

Headless UI and Radix

These libraries provide behavior without styling. You get accessible dropdowns, modals, and menus that you style yourself. Perfect when you want full control over the look.

When to Use What

  • shadcn/ui — when you want beautiful defaults with easy customization. Best for most projects.
  • Headless UI / Radix — when you need accessible primitives but want complete styling control.
  • Custom — when you need something truly unique or when the libraries don't cover your use case.

Decision prompt: "I'm building a dashboard with modals, dropdowns, and data tables. Should I use a component library?" AI will usually recommend shadcn/ui for this use case — and that's the right call.

What's Next

You now have the frontend toolkit to build any UI with AI. You know how to think in components, describe layouts and styling, build responsive designs, and choose the right tools for the job.

But a beautiful frontend needs a backend to power it. In the next lesson, we'll dive into backend and API development — building server-side logic, API routes, data validation, and error handling. You'll learn how to tell AI to build the server that feeds your frontend with real data.