Expandable FAQs

An FAQ section that expands to show more faqs.

Preview

Code

Code

app/page.tsx

1import Faqs from "@/sections/faqs"; 2 3const Home = () => { 4 return ( 5 <main className="min-h-screen w-full flex items-center justify-center"> 6 <Faqs /> 7 </main> 8 ); 9} 10 11export default Home;

Installation

Initialized a project and installed the necessary dependencies to use this component.

1. Initialize a new Next.js project:

Code
npx create-next-app@latest my-app

2. Install Framer Motion

Code
npm install framer-motion

3. Initialize Shadcn UI

Code
npx shadcn@latest init

4. Install Shadcn Components

Code
npx shadcn@latest add accordion button

Add FAQ Section

Code

src/sections/faq.tsx

1"use client" 2 3import { 4 Accordion, 5 AccordionContent, 6 AccordionItem, 7 AccordionTrigger, 8} from "@/components/ui/accordion"; 9import { Button } from "@/components/ui/button"; 10import { faqData } from "@/constants"; 11 12import { motion } from "framer-motion"; 13import { useState } from "react"; 14 15const Faqs = () => { 16 const [open, setOpen] = useState(false); 17 18 return ( 19 <section className=" w-full py-20"> 20 <div className=" container mx-auto px-4 md:px-0 flex flex-col items-center"> 21 <motion.div 22 initial={false} 23 animate={open ? "open" : "closed"} 24 style={{ 25 overflow: "hidden", 26 }} 27 variants={{ 28 open: { 29 height: "fit-content", 30 }, 31 closed: { 32 height: 380, 33 }, 34 }} className=" relative w-full h-fit mt-12"> 35 36 <Accordion 37 type="single" 38 collapsible 39 className="w-full max-w-3xl mx-auto" 40 defaultValue="item-1" 41 > 42 {faqData.map((faq, index) => ( 43 <AccordionItem key={index} value={faq.id}> 44 <AccordionTrigger>{faq.question}</AccordionTrigger> 45 <AccordionContent className="flex flex-col gap-4 text-balance"> 46 {faq.answer} 47 </AccordionContent> 48 </AccordionItem> 49 ))} 50 </Accordion> 51 <motion.div 52 variants={{ 53 open: { 54 bottom: "0%", 55 zIndex: -10, 56 }, 57 closed: { 58 bottom: "0%", 59 }, 60 }} 61 className="absolute inset-x-0 bottom-0 left-0 right-0 bg-gradient-to-b from-transparent to-background h-1/2" 62 /> 63 </motion.div> 64 <Button variant="secondary" onClick={() => setOpen((pv: boolean) => !pv)} className="mt-4 w-fit"> 65 {open ? ( 66 <> 67 <p>Show less</p> 68 </> 69 ) : ( 70 <> 71 <p>Show all FAQs</p> 72 </> 73 )} 74 </Button> 75 </div> 76 </section > 77 ); 78} 79 80export default Faqs;

Add Accordion Component

Code

src/components/ui/accordion.tsx

1"use client" 2 3import * as AccordionPrimitive from "@radix-ui/react-accordion" 4import { Plus } from "lucide-react" 5import * as React from "react" 6 7import { cn } from "@/lib/utils" 8 9function Accordion({ 10 ...props 11}: React.ComponentProps<typeof AccordionPrimitive.Root>) { 12 return <AccordionPrimitive.Root data-slot="accordion" {...props} /> 13} 14 15function AccordionItem({ 16 className, 17 ...props 18}: React.ComponentProps<typeof AccordionPrimitive.Item>) { 19 return ( 20 <AccordionPrimitive.Item 21 data-slot="accordion-item" 22 className={cn("border-b first:border-t last:border-b-0", className)} 23 {...props} 24 /> 25 ) 26} 27 28function AccordionTrigger({ 29 className, 30 children, 31 ...props 32}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { 33 return ( 34 <AccordionPrimitive.Header className="flex"> 35 <AccordionPrimitive.Trigger 36 data-slot="accordion-trigger" 37 className={cn( 38 "cursor-pointer focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-base md:text-lg font-medium transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-45", 39 className 40 )} 41 {...props} 42 > 43 {children} 44 <Plus className=" text-white pointer-events-none size-5 shrink-0 translate-y-0.5 transition-transform duration-200" /> 45 </AccordionPrimitive.Trigger> 46 </AccordionPrimitive.Header> 47 ) 48} 49 50function AccordionContent({ 51 className, 52 children, 53 ...props 54}: React.ComponentProps<typeof AccordionPrimitive.Content>) { 55 return ( 56 <AccordionPrimitive.Content 57 data-slot="accordion-content" 58 className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm md:text-[15px] text-white/70" 59 {...props} 60 > 61 <div className={cn("pt-0 pb-4", className)}>{children}</div> 62 </AccordionPrimitive.Content> 63 ) 64} 65 66export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }

Add Button Component

Code

src/components/ui/button.tsx

1import * as React from "react" 2import { Slot } from "@radix-ui/react-slot" 3import { cva, type VariantProps } from "class-variance-authority" 4 5import { cn } from "@/lib/utils" 6 7const buttonVariants = cva( 8 "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-full text-[15px] font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 { 10 variants: { 11 variant: { 12 default: 13 "bg-primary text-primary-foreground hover:bg-primary/90", 14 destructive: 15 "bg-destructive text-white hover:bg-destructive/90", 16 outline: 17 "border border-white/15 text-white/80 bg-background hover:border-white/25 hover:text-white", 18 secondary: 19 "bg-secondary text-secondary-foreground hover:bg-secondary/80", 20 ghost: 21 "text-white/70 hover:text-white", 22 link: "text-primary underline-offset-4 hover:underline", 23 }, 24 size: { 25 default: " h-8 md:h-9 px-2.5 md:px-4 py-2 has-[>svg]:px-3", 26 sm: " h-7 md:h-8 text-sm rounded-full gap-1.5 px-3 has-[>svg]:px-2.5", 27 lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 icon: "size-8", 29 }, 30 }, 31 defaultVariants: { 32 variant: "default", 33 size: "default", 34 }, 35 } 36) 37 38function Button({ 39 className, 40 variant, 41 size, 42 asChild = false, 43 ...props 44}: React.ComponentProps<"button"> & 45 VariantProps<typeof buttonVariants> & { 46 asChild?: boolean 47 }) { 48 const Comp = asChild ? Slot : "button" 49 50 return ( 51 <Comp 52 data-slot="button" 53 className={cn(buttonVariants({ variant, size, className }))} 54 {...props} 55 /> 56 ) 57} 58 59export { Button, buttonVariants }

Add Constants

Code

src/constants/index.ts

1export const faqData = [ 2 { 3 id: "item-1", 4 question: "What is Astrae?", 5 answer: 6 "Astrae is a modern landing page template designed for startups, SaaS, and businesses to create stunning websites quickly.", 7 }, 8 { 9 id: "item-2", 10 question: "How do I use Astrae?", 11 answer: 12 "You can use Astrae by downloading the template files and integrating them into your project. Detailed documentation is available.", 13 }, 14 { 15 id: "item-3", 16 question: "Is Astrae free?", 17 answer: 18 "Astrae offers both free and premium versions. The free version includes basic features, while the premium version unlocks advanced functionalities.", 19 }, 20 { 21 id: "item-4", 22 question: "Can I customize Astrae?", 23 answer: 24 "Yes, Astrae is fully customizable. You can modify styles, layouts, and content to fit your brand's needs.", 25 }, 26 { 27 id: "item-5", 28 question: "What happens after I subscribe?", 29 answer: 30 "After subscribing, you'll get instant access to all premium templates, components, and resources. You'll also receive a welcome email with your download links and getting started guide.", 31 }, 32 { 33 id: "item-6", 34 question: "Can I cancel anytime?", 35 answer: 36 "Yes, you can cancel your subscription at any time. There are no long-term commitments, and you'll retain access to all downloaded templates even after cancellation.", 37 }, 38 { 39 id: "item-7", 40 question: "Do I get access to future templates too?", 41 answer: 42 "Absolutely! Your subscription includes all future template releases. We regularly add new templates and components, and you'll get access to everything as long as your subscription is active.", 43 }, 44 { 45 id: "item-8", 46 question: "Can I use the templates for client projects?", 47 answer: 48 "Yes, you can use Astrae templates for client projects. Our license allows commercial use, including client work, freelance projects, and agency services without any additional fees.", 49 }, 50 { 51 id: "item-9", 52 question: "Do you offer refunds?", 53 answer: 54 "We offer a 30-day money-back guarantee. If you're not satisfied with your purchase for any reason, contact our support team within 30 days for a full refund.", 55 }, 56];

Add Global Styles

Code

app/globals.css

1@import "tailwindcss"; 2@import "tw-animate-css"; 3 4@custom-variant dark (&:is(.dark *)); 5 6@theme inline { 7 --color-background: var(--background); 8 --color-foreground: var(--foreground); 9 --font-sans: var(--font-geist-sans); 10 --font-mono: var(--font-geist-mono); 11 --color-sidebar-ring: var(--sidebar-ring); 12 --color-sidebar-border: var(--sidebar-border); 13 --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 14 --color-sidebar-accent: var(--sidebar-accent); 15 --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 16 --color-sidebar-primary: var(--sidebar-primary); 17 --color-sidebar-foreground: var(--sidebar-foreground); 18 --color-sidebar: var(--sidebar); 19 --color-chart-5: var(--chart-5); 20 --color-chart-4: var(--chart-4); 21 --color-chart-3: var(--chart-3); 22 --color-chart-2: var(--chart-2); 23 --color-chart-1: var(--chart-1); 24 --color-ring: var(--ring); 25 --color-input: var(--input); 26 --color-border: var(--border); 27 --color-destructive: var(--destructive); 28 --color-accent-foreground: var(--accent-foreground); 29 --color-accent: var(--accent); 30 --color-muted-foreground: var(--muted-foreground); 31 --color-muted: var(--muted); 32 --color-secondary-foreground: var(--secondary-foreground); 33 --color-secondary: var(--secondary); 34 --color-primary-foreground: var(--primary-foreground); 35 --color-primary: var(--primary); 36 --color-popover-foreground: var(--popover-foreground); 37 --color-popover: var(--popover); 38 --color-card-foreground: var(--card-foreground); 39 --color-card: var(--card); 40 --radius-sm: calc(var(--radius) - 4px); 41 --radius-md: calc(var(--radius) - 2px); 42 --radius-lg: var(--radius); 43 --radius-xl: calc(var(--radius) + 4px); 44} 45 46:root { 47 --radius: 0.625rem; 48 --background: oklch(0% 0 0); 49 --foreground: oklch(0.985 0 0); 50 --card: oklch(0.205 0 0); 51 --card-foreground: oklch(0.985 0 0); 52 --popover: oklch(15.43% 0 0); 53 --popover-foreground: oklch(0.985 0 0); 54 --primary: oklch(100% 0.00011 271.152); 55 --primary-foreground: oklch(0.205 0 0); 56 --secondary: oklch(0.269 0 0); 57 --secondary-foreground: oklch(0.985 0 0); 58 --muted: oklch(19.13% 0 0); 59 --muted-foreground: oklch(0.708 0 0); 60 --accent: oklch(55.9% 0.238 260.78); 61 --accent-foreground: oklch(0.985 0 0); 62 --destructive: oklch(59.56% 0.234 24.23); 63 --border: oklch(1 0 0 / 10%); 64 --input: oklch(1 0 0 / 15%); 65 --ring: oklch(0.556 0 0); 66 --chart-1: oklch(0.488 0.243 264.376); 67 --chart-2: oklch(0.696 0.17 162.48); 68 --chart-3: oklch(0.769 0.188 70.08); 69 --chart-4: oklch(0.627 0.265 303.9); 70 --chart-5: oklch(0.645 0.246 16.439); 71 --sidebar: oklch(0.205 0 0); 72 --sidebar-foreground: oklch(0.985 0 0); 73 --sidebar-primary: oklch(0.488 0.243 264.376); 74 --sidebar-primary-foreground: oklch(0.985 0 0); 75 --sidebar-accent: oklch(0.269 0 0); 76 --sidebar-accent-foreground: oklch(0.985 0 0); 77 --sidebar-border: oklch(1 0 0 / 10%); 78 --sidebar-ring: oklch(0.556 0 0); 79} 80 81.dark { 82 --background: oklch(0% 0 0); 83 --foreground: oklch(0.985 0 0); 84 --card: oklch(0.205 0 0); 85 --card-foreground: oklch(0.985 0 0); 86 --popover: oklch(15.43% 0 0); 87 --popover-foreground: oklch(0.985 0 0); 88 --primary: oklch(100% 0.00011 271.152); 89 --primary-foreground: oklch(0.205 0 0); 90 --secondary: oklch(0.269 0 0); 91 --secondary-foreground: oklch(0.985 0 0); 92 --muted: oklch(19.13% 0 0); 93 --muted-foreground: oklch(0.708 0 0); 94 --accent: oklch(55.9% 0.238 260.78); 95 --accent-foreground: oklch(0.985 0 0); 96 --destructive: oklch(59.56% 0.234 24.23); 97 --border: oklch(1 0 0 / 10%); 98 --input: oklch(1 0 0 / 15%); 99 --ring: oklch(0.556 0 0); 100 --chart-1: oklch(0.488 0.243 264.376); 101 --chart-2: oklch(0.696 0.17 162.48); 102 --chart-3: oklch(0.769 0.188 70.08); 103 --chart-4: oklch(0.627 0.265 303.9); 104 --chart-5: oklch(0.645 0.246 16.439); 105 --sidebar: oklch(0.205 0 0); 106 --sidebar-foreground: oklch(0.985 0 0); 107 --sidebar-primary: oklch(0.488 0.243 264.376); 108 --sidebar-primary-foreground: oklch(0.985 0 0); 109 --sidebar-accent: oklch(0.269 0 0); 110 --sidebar-accent-foreground: oklch(0.985 0 0); 111 --sidebar-border: oklch(1 0 0 / 10%); 112 --sidebar-ring: oklch(0.556 0 0); 113} 114 115@layer base { 116 * { 117 @apply border-border outline-ring/50; 118 } 119 body { 120 @apply bg-background text-foreground; 121 } 122}

Become an Astrae
Affiliate Today

Make referrals, and bring in clients. Keep 50% of your earnings paid out weekly.

Description