Design
Outline
<> Step Code
Click on the link below to view the code for this step on GitHub.
GitHub - szymonuryga/optimizely-masterclass-step-by-step at 5-design
Contribute to szymonuryga/optimizely-masterclass-step-by-step development by creating an account 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>
)
}