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:
npx create-next-app@latest my-app
2. Install Framer Motion
npm install framer-motion
3. Initialize Shadcn UI
npx shadcn@latest init
4. Install Shadcn Components
npx shadcn@latest add accordion button
Add FAQ Section
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
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
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
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
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}