Step 7 of 21 (33% complete)
Design
Step Code
The code for this specific step can be found on the following branch:
Click on a link to view the code for this step on GitHub.
This guide explains how to use free Figma resources and Vercel's v0 AI assistant to speed up your design and development process.
Introducing v0 from Vercel
v0 is an AI coding assistant by Vercel that helps developers quickly create React components with styling. It's useful for:
- Rapid prototyping
- Generating styled components
- Learning new React patterns
Using Figma and v0 Together
- Start with a free Figma template
- Use v0 to turn Figma designs into code
Note
The feature for directly generating files from Figma designs in v0 is a premium feature. Only users with a paid subscription have this ability. Free users can still benefit by describing Figma designs to v0.
Example v0 Prompt
When using v0, provide context about your project. For example:
Create a React component based on the Figma design, considering: - The project uses Optimizely SaaS CMS - Components should be reusable blocks in components/blocks/ - Include separate Header and Footer components
v0 will generate a React component with styling that you can then customize for your project.
By combining Figma resources and v0, you can quickly create professional-looking components that fit your project's needs.
Generated Components with v0
To add components from shadcn/ui run the following commands:
npx shadcn@latest add button npx shadcn@latest add input npx shadcn@latest add textarea npx shadcn@latest add card npx shadcn@latest add avatar
// components\block\contact-block.tsx import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" interface ContactBlockProps { title: string description: string } export default function ContactBlock({ title, description }: ContactBlockProps) { return ( <section className="container mx-auto px-4 py-16"> <Card className="max-w-xl mx-auto"> <CardHeader> <CardTitle>{title}</CardTitle> <p className="text-muted-foreground">{description}</p> </CardHeader> <CardContent> <form className="space-y-6"> <Input placeholder="Name" /> <Input placeholder="Email" type="email" /> <Textarea placeholder="Message" /> <Button className="w-full">Send</Button> </form> </CardContent> </Card> </section> ) }
// components\block\hero-block.tsx interface HeroBlockProps { title: string subtitle?: string showDecoration?: boolean decorationColorsPrimary?: string decorationColorsSecondary?: string } export default function HeroBlock({ title, subtitle, showDecoration = true, decorationColorsPrimary = "#009379", decorationColorsSecondary = "#ffd285" }: HeroBlockProps) { return ( <section className="container mx-auto px-4 pt-20 pb-16 relative"> <h1 className="text-4xl md:text-6xl font-bold max-w-xl mb-4">{title}</h1> {subtitle && <p className="text-xl text-muted-foreground max-w-xl mb-8">{subtitle}</p>} {showDecoration && ( <div className="absolute right-20 top-10"> <div className="relative w-48 h-48"> <div className="absolute right-0 w-32 h-32 rounded-full" style={{ backgroundColor: decorationColorsPrimary }} /> <div className="absolute bottom-0 left-0 w-40 h-40 rounded-full" style={{ backgroundColor: decorationColorsSecondary }} /> </div> </div> )} </section> ) }
//components\block\logos-block.tsx import Image from "next/image" interface Logo { src: string alt: string } interface LogosBlockProps { logos: Logo[] } export default function LogosBlock({ logos }: LogosBlockProps) { return ( <section className="container mx-auto px-4 py-16"> <div className="flex justify-center gap-12 flex-wrap"> {logos.map((logo, index) => ( <div key={index} className="flex items-center"> <Image src={logo.src || "/placeholder.svg"} alt={logo.alt} width={100} height={40} /> </div> ))} </div> </section> ) }
//components\block\portfolio-grid-block.tsx import Image from "next/image" import { Card, CardContent } from "@/components/ui/card" import Link from "next/link" interface PortfolioItem { title: string description: string imageUrl: string link: string } interface PortfolioGridBlockProps { title: string items: PortfolioItem[] } export default function PortfolioGridBlock({ title, items }: PortfolioGridBlockProps) { return ( <section className="container mx-auto px-4 py-16"> <h2 className="text-3xl font-bold mb-12">{title}</h2> <div className="grid md:grid-cols-3 gap-6"> {items.map((item, index) => ( <Card key={index}> <CardContent className="p-0"> <Image src={item.imageUrl || "/placeholder.svg"} alt={item.title} width={400} height={300} className="w-full h-48 object-cover" /> <div className="p-4"> <Link href={item.link ?? ''}> <h3 className="font-semibold mb-2">{item.title}</h3> </Link> <p className="text-sm text-muted-foreground">{item.description}</p> </div> </CardContent> </Card> ))} </div> </section> ) }
//components\block\services-block.tsx import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import type React from "react" // Import React import Image from 'next/image' interface Service { title: string description: string icon?: string } interface ServicesBlockProps { services: Service[] } export default function ServicesBlock({ services }: ServicesBlockProps) { return ( <section className="container mx-auto px-4 py-16"> <div className="grid md:grid-cols-3 gap-8"> {services.map((service, index) => ( <Card key={index}> <CardHeader> {service?.icon && <div className="mb-4"><Image src={service.icon} alt={service.title} width={50} height={50} /></div>} <CardTitle>{service.title}</CardTitle> </CardHeader> <CardContent> <p className="text-muted-foreground">{service.description}</p> </CardContent> </Card> ))} </div> </section> ) }
//components\block\testimonials-block.tsx import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" interface Testimonial { name: string position: string content: string avatarSrc?: string } interface TestimonialsBlockProps { title: string testimonials: Testimonial[] } export default function TestimonialsBlock({ title, testimonials }: TestimonialsBlockProps) { return ( <section className="container mx-auto px-4 py-16"> <h2 className="text-3xl font-bold mb-12">{title}</h2> <div className="grid md:grid-cols-3 gap-8"> {testimonials.map((testimonial, index) => ( <Card key={index}> <CardHeader> <div className="flex items-center gap-4"> <Avatar> <AvatarImage src={testimonial.avatarSrc} alt={testimonial.name} /> <AvatarFallback>{testimonial.name.charAt(0)}</AvatarFallback> </Avatar> <div> <CardTitle className="text-sm font-medium">{testimonial.name}</CardTitle> <p className="text-sm text-muted-foreground">{testimonial.position}</p> </div> </div> </CardHeader> <CardContent> <p className="text-muted-foreground">{testimonial.content}</p> </CardContent> </Card> ))} </div> </section> ) }
Have questions? I'm here to help!
Course Content
21 lessons • 10 hours