Files
"use client";
import { PlusIcon } from "lucide-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
import { Button } from "@/components/ui/button";
import {
Message,
MessageAvatar,
MessageContent,
} from "@/components/ui/message";
import { Separator } from "@/components/ui/separator";
const FAQS = [
{
id: "getting-started",
question: "How quickly can I get started?",
answer:
"You can be up and running in a few minutes. Install the package, choose the components you need, and customize them directly in your codebase.",
},
{
id: "customize",
question: "Can I customize every component?",
answer:
"Yes. You own the source code, so every detail is yours to adapt — from tokens and typography to layout, behavior, and motion.",
},
{
id: "frameworks",
question: "Which frameworks are supported?",
answer:
"The components are designed for React and work especially well with Next.js. They use standard TypeScript, Tailwind CSS, and accessible primitives.",
},
{
id: "accessibility",
question: "Are the components accessible?",
answer:
"Accessibility is built into the underlying primitives, including keyboard navigation, focus management, and the appropriate ARIA attributes.",
},
{
id: "updates",
question: "Do I get future updates?",
answer:
"Yes. You can pull newer versions whenever they are useful while keeping full control over the local changes you have made.",
},
];
export function Faq03() {
return (
<section
aria-labelledby="faq-03-heading"
className="w-full bg-background py-20 font-sans sm:py-28"
>
<header className="mx-auto flex max-w-4xl flex-col items-center gap-4 text-center">
<h2
className="text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id="faq-03-heading"
>
Questions, answered
</h2>
<p className="max-w-xl text-balance text-base text-muted-foreground leading-relaxed sm:text-lg">
Everything you need to know before adding the library to your next
project.
</p>
</header>
<div className="relative mx-auto mt-14 max-w-4xl px-1 py-6 sm:mt-16 sm:px-6 sm:py-8">
<Separator
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
/>
<Separator
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
/>
<Accordion className="gap-3" defaultValue={["getting-started"]}>
{FAQS.map((faq) => (
<AccordionItem
className="border-0 not-last:border-b-0"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/question [&>[data-slot=accordion-trigger-icon]]:hidden! ml-auto w-fit max-w-[92%] flex-none touch-manipulation items-center justify-end gap-2 rounded-full border-0 py-0 font-normal transition-none hover:no-underline focus-visible:ring-2 sm:max-w-[82%]">
<span className="flex size-9 shrink-0 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-xs transition-[transform,background-color,border-color,color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-focus-visible/question:scale-100 group-focus-visible/question:transition-none group-active/question:scale-[0.96] group-aria-expanded/question:border-primary group-aria-expanded/question:bg-primary group-aria-expanded/question:text-primary-foreground motion-reduce:transition-colors motion-reduce:group-active/question:scale-100">
<PlusIcon
aria-hidden="true"
className="size-4 transition-transform duration-180 ease-[cubic-bezier(0.77,0,0.175,1)] group-focus-visible/question:transition-none group-aria-expanded/question:rotate-45 motion-reduce:transition-none"
/>
</span>
<span className="rounded-full border border-border bg-background px-5 py-3 text-left font-medium text-base leading-relaxed shadow-xs transition-[transform,background-color,border-color,color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-focus-visible/question:scale-100 group-focus-visible/question:transition-none group-active/question:scale-[0.98] group-aria-expanded/question:border-primary group-aria-expanded/question:bg-primary group-aria-expanded/question:text-primary-foreground motion-reduce:transition-colors motion-reduce:group-active/question:scale-100">
{faq.question}
</span>
</AccordionTrigger>
<AccordionContent className="pt-3 pb-5 sm:pb-6">
<Message align="start">
<MessageAvatar>
<Avatar size="lg">
<AvatarFallback>TS</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble
className="max-w-[92%] sm:max-w-[80%]"
variant="muted"
>
<BubbleContent className="rounded-2xl rounded-bl-sm px-5 py-4 text-base leading-relaxed">
{faq.answer}
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
<div className="mx-auto mt-6 flex max-w-4xl flex-col items-center justify-between gap-4 px-1 sm:flex-row sm:px-6">
<p className="text-center text-base text-muted-foreground sm:text-left">
Still have a question?
</p>
<Button
nativeButton={false}
render={<a href="mailto:hello@example.com" />}
size="lg"
>
Send us a message
</Button>
</div>
</section>
);
}
A conversational FAQ section with chat bubbles and a compact contact prompt
@tentui/faq-03
Files
"use client";
import { PlusIcon } from "lucide-react";
import { type ReactNode, useId } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { cn } from "@/lib/utils";
export interface FaqItem {
id: string;
question: string;
answer: ReactNode;
}
export interface Faq02Props {
title?: ReactNode;
description?: ReactNode;
faqs?: FaqItem[];
className?: string;
}
const FAQS = [
{
id: "lorem-ipsum",
question: "Lorem ipsum dolor sit amet?",
answer:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
},
{
id: "consectetur",
question: "Consectetur adipiscing elit sed do?",
answer:
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
},
{
id: "occaecat",
question: "Excepteur sint occaecat cupidatat?",
answer:
"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos.",
},
] satisfies FaqItem[];
export function Faq02({
title = "Everything you need to know",
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.",
faqs = FAQS,
className,
}: Faq02Props = {}) {
const headingId = useId();
const defaultValue = faqs.length > 0 ? [faqs[0].id] : [];
return (
<section
aria-labelledby={headingId}
className={cn(
"w-full bg-background px-4 py-20 font-sans sm:px-6 sm:py-28",
className,
)}
>
<div className="mx-auto w-full max-w-5xl">
<header className="mx-auto flex max-w-2xl flex-col items-center text-center">
<h2
className="text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id={headingId}
>
{title}
</h2>
<p className="mt-5 max-w-xl text-balance text-base text-muted-foreground leading-relaxed sm:text-lg">
{description}
</p>
</header>
<div className="relative mt-14 sm:mt-16">
<Accordion className="border-y" defaultValue={defaultValue}>
{faqs.map((faq) => (
<AccordionItem
className="border-border"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/faq-trigger [&>svg]:hidden! grid touch-manipulation grid-cols-[minmax(0,1fr)_2.25rem] items-center gap-x-4 rounded-none border-0 py-6 transition-none hover:no-underline sm:gap-x-5 sm:py-7">
<span className="text-left font-normal text-base text-foreground leading-6 sm:text-lg">
{faq.question}
</span>
<span className="flex size-9 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-xs transition-[transform,background-color,border-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/faq-trigger:border-foreground/15 group-hover/faq-trigger:bg-muted group-active/faq-trigger:scale-[0.96] [&_svg]:size-4">
<PlusIcon
aria-hidden="true"
className="transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] group-aria-expanded/faq-trigger:rotate-45 motion-reduce:transition-none"
/>
</span>
</AccordionTrigger>
<AccordionContent className="pr-12 pb-7 sm:pr-16 sm:pb-8">
<div className="text-muted-foreground text-sm leading-relaxed sm:text-base">
{faq.answer}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
<p className="mt-8 text-center text-muted-foreground text-sm">
Still curious?{" "}
<a
className="font-medium text-foreground underline decoration-border underline-offset-4 transition-[text-decoration-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] hover:decoration-foreground focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
href="mailto:hello@example.com"
>
Talk to our team
</a>
.
</p>
</div>
</section>
);
}
A numbered editorial FAQ section with focused disclosure rows
@tentui/faq-02
Files
"use client";
import { MinusIcon, PlusIcon } from "lucide-react";
import { type ReactNode, useId } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { cn } from "@/lib/utils";
export interface FaqItem {
id: string;
question: string;
answer: ReactNode;
}
export interface FaqSectionProps {
title?: ReactNode;
faqs?: FaqItem[];
className?: string;
}
const FAQS = [
{
id: "go-live",
question: "Lorem ipsum dolor sit amet?",
answer: (
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</>
),
},
{
id: "tech-stack",
question: "Consectetur adipiscing elit?",
answer: (
<>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.
</>
),
},
{
id: "visitor-messages",
question: "Sed do eiusmod tempor incididunt?",
answer: (
<>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur.
</>
),
},
{
id: "brand-customization",
question: "Ut labore et dolore magna aliqua?",
answer: (
<>
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
</>
),
},
{
id: "domain-controls",
question: "Quis nostrud exercitation ullamco?",
answer: (
<>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
accusantium doloremque laudantium, totam rem aperiam.
</>
),
},
{
id: "free-trial",
question: "Excepteur sint occaecat cupidatat?",
answer: (
<>
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut
fugit, sed quia consequuntur magni dolores eos.
</>
),
},
] satisfies FaqItem[];
function GridTick({ className }: { className?: string }) {
return (
<span
aria-hidden="true"
className={cn(
"pointer-events-none absolute z-10 flex size-4 items-center justify-center",
className,
)}
>
<span className="absolute h-px w-full bg-border" />
<span className="absolute h-full w-px bg-border" />
</span>
);
}
function FaqBackdrop() {
return (
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
<div className="absolute inset-0 bg-[linear-gradient(to_right,var(--color-border)_1px,transparent_1px),linear-gradient(to_bottom,var(--color-border)_1px,transparent_1px)] bg-size-[32px_32px] opacity-40 [mask-image:radial-gradient(ellipse_at_center,black,transparent_78%)]" />
<div className="absolute top-[12%] left-[8%] size-64 rounded-full bg-muted/80 blur-3xl" />
</div>
);
}
export function Faq01({
title = "Frequently asked questions",
faqs = FAQS,
className,
}: FaqSectionProps = {}) {
const headingId = useId();
return (
<section
aria-labelledby={headingId}
className={cn(
"w-full bg-background px-4 py-20 font-sans sm:px-6 sm:py-24",
className,
)}
>
<div className="relative mx-auto w-full max-w-6xl border-border border-y bg-background md:border-x">
<GridTick className="-top-2 -left-2" />
<GridTick className="-top-2 -right-2" />
<GridTick className="-bottom-2 -left-2" />
<GridTick className="-right-2 -bottom-2" />
<div className="relative grid grid-cols-1 md:grid-cols-12">
<div className="relative flex min-h-64 flex-col items-start overflow-hidden border-border border-b p-8 md:col-span-5 md:min-h-0 md:border-r md:border-b-0 md:p-10 lg:p-12">
<FaqBackdrop />
<h2
className="relative max-w-md text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id={headingId}
>
{title}
</h2>
</div>
<div className="relative md:col-span-7">
<GridTick className="-top-2 -left-2 hidden md:flex" />
<GridTick className="-bottom-2 -left-2 hidden md:flex" />
<Accordion>
{faqs.map((faq) => (
<AccordionItem
className="px-6 sm:px-8"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/faq-trigger [&>svg]:hidden! touch-manipulation items-center rounded-none py-6 transition-none hover:no-underline sm:py-7">
<span className="flex flex-1 items-center pr-4">
<span className="text-left text-base text-foreground leading-6 sm:text-lg">
{faq.question}
</span>
</span>
<span className="ml-auto flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-[transform,background-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/faq-trigger:bg-muted/80 group-active/faq-trigger:scale-[0.97] [&_svg]:size-4">
<PlusIcon
aria-hidden="true"
className="group-aria-expanded/faq-trigger:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden group-aria-expanded/faq-trigger:block"
/>
</span>
</AccordionTrigger>
<AccordionContent className="pr-10 pb-7 sm:pr-12">
<div className="text-muted-foreground text-sm leading-relaxed sm:text-base">
{faq.answer}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>
</div>
</section>
);
}
A split-layout FAQ section with subtle grid detailing
@tentui/faq-01