← FAQOpen in v0
FAQ 02 — Two Columns
Static two-column FAQ grid with numbered markers, staggered reveals and a glowing contact CTA row.
$ npx shadcn@latest add @ludusvibe/faq-02Code
Installed to components/sections/faq-02.tsx — it's yours to edit.
"use client"
import * as React from "react"
import { motion } from "motion/react"
import { ArrowRight } from "lucide-react"
import { buttonVariants } from "@/components/ui/button"
import { Reveal } from "@/components/ludus/reveal"
import { fadeUp, stagger, viewport } from "@/lib/motion"
import { cn } from "@/lib/utils"
export type Faq02Item = {
question: string
answer: React.ReactNode
}
export type Faq02Props = {
eyebrow?: string
title?: React.ReactNode
description?: string
items?: Faq02Item[]
contactTitle?: string
contactDescription?: string
contactCta?: { label: string; href: string }
/** Heading element — use "h1" when this is the page's main heading. */
as?: "h1" | "h2"
className?: string
}
const defaultItems: Faq02Item[] = [
{
question: "How does the 14-day free trial work?",
answer:
"Every plan starts with a full-featured 14-day trial — no credit card required. If you don't upgrade, your account simply drops to read-only until you do.",
},
{
question: "Can I change plans later?",
answer:
"Yes. Upgrade, downgrade or switch billing intervals at any time. Upgrades take effect immediately and are prorated; downgrades apply next cycle.",
},
{
question: "What counts as a monthly page view?",
answer:
"A single load of any page you publish with Lumen. Bot and crawler traffic is filtered out automatically and never counts against your quota.",
},
{
question: "How do you handle data privacy?",
answer:
"All data is encrypted in transit and at rest, and we're GDPR and CCPA compliant. On Pro and above you can choose EU-only data residency.",
},
{
question: "Can I cancel anytime?",
answer:
"Absolutely — two clicks from your billing settings. Your plan stays active until the end of the paid period, and you can export all of your data.",
},
{
question: "What kind of support is included?",
answer:
"Community support on Starter, priority email with a 4-hour first-response target on Pro, and a dedicated success manager on Enterprise.",
},
]
export function Faq02({
eyebrow = "FAQ",
title = "Answers, before you even ask",
description = "The short version of everything teams ask us about Lumen, billing and data.",
items = defaultItems,
contactTitle = "Didn't find your answer?",
contactDescription = "Our team replies to every message — usually within a couple of hours.",
contactCta = { label: "Contact support", href: "#" },
as: Heading = "h2",
className,
}: Faq02Props) {
return (
<section className={cn("bg-background px-6 py-24 md:py-32", className)}>
<div className="mx-auto max-w-5xl">
<div className="mx-auto max-w-2xl text-center">
<Reveal>
<p className="text-sm font-medium tracking-widest text-primary uppercase">
{eyebrow}
</p>
</Reveal>
<Reveal delay={0.1}>
<Heading className="mt-4 text-balance font-heading text-4xl font-semibold tracking-tight md:text-5xl">
{title}
</Heading>
</Reveal>
<Reveal delay={0.2}>
<p className="mt-5 text-pretty text-muted-foreground md:text-lg">
{description}
</p>
</Reveal>
</div>
<motion.div
initial="hidden"
whileInView="visible"
viewport={viewport}
variants={stagger()}
className="mt-14 grid gap-x-12 gap-y-10 md:mt-16 md:grid-cols-2"
>
{items.map((item, index) => (
<motion.div key={index} variants={fadeUp} className="flex gap-4">
<span
aria-hidden
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 font-heading text-sm font-semibold text-primary"
>
{String(index + 1).padStart(2, "0")}
</span>
<div>
<h3 className="font-medium">{item.question}</h3>
<p className="mt-2 text-sm text-pretty text-muted-foreground">
{item.answer}
</p>
</div>
</motion.div>
))}
</motion.div>
<Reveal delay={0.2} className="mt-14 md:mt-16">
<div className="relative overflow-hidden rounded-2xl border bg-card p-6 sm:px-8">
<div
aria-hidden
className="absolute -top-16 -right-16 size-40 rounded-full bg-primary/15 blur-3xl"
/>
<div className="relative flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div>
<p className="font-heading font-semibold tracking-tight">
{contactTitle}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{contactDescription}
</p>
</div>
<a
href={contactCta.href}
className={cn(buttonVariants({ size: "lg" }), "group shrink-0")}
>
{contactCta.label}
<ArrowRight className="transition-transform group-hover:translate-x-0.5" />
</a>
</div>
</div>
</Reveal>
</div>
</section>
)
}