# Faqs 7

> Expandable FAQ cards in a 2×3 grid with hover lift — scannable topics above pricing.

A shadcn/ui block from the [@nusaiba](https://nusaiba.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nusaiba/faqs-7).

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/faqs-7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/faqs-7-light-7ca6392ab3697d95.png
- Registry item: https://nusaiba.dev/r/base-nova/faqs-7.json
- Categories: faqs
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/faqs-7.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `@nusaiba/foundation`

## Files

### `registry/blocks/faqs/faqs-data.ts`

```ts
export const faqSite = {
	name: "Nusaiba",
	namespace: "@nusaiba",
	domain: "nusaiba.dev",
} as const;

export type FaqCategoryId = "install" | "plans" | "customize";

export type FaqItem = {
	id: string;
	category: FaqCategoryId;
	title: string;
	content: string;
};

export const faqCategories: { id: FaqCategoryId; label: string }[] = [
	{ id: "install", label: "Install" },
	{ id: "plans", label: "Plans" },
	{ id: "customize", label: "Customize" },
];

export const faqItems: FaqItem[] = [
	{
		id: "what-is",
		category: "install",
		title: `What is ${faqSite.name}?`,
		content: `${faqSite.name} is an open shadcn registry of motion-first landing sections — heroes, pricing, FAQs, footers, and more. Preview live, install into your repo with the CLI, and keep every file.`,
	},
	{
		id: "landing-pages",
		category: "plans",
		title: "Can I use blocks on a client landing page?",
		content:
			"Yes. Free blocks ship for personal and commercial projects. Pro blocks include a commercial license for client work — install once, edit the copy and tokens, and ship the page you own.",
	},
	{
		id: "install",
		category: "install",
		title: "How do I add a block to my project?",
		content: `Add the ${faqSite.namespace} registry to components.json, then run npx shadcn@latest add ${faqSite.namespace}/block-name. Files land in your codebase — no runtime dependency on ${faqSite.name}.`,
	},
	{
		id: "free-vs-pro",
		category: "plans",
		title: "What's included in Free vs Pro?",
		content:
			"Free blocks cover core landing patterns — hero, features, FAQ, CTA, footer. Pro unlocks advanced motion blocks, registry token install, lifetime updates, and commercial licensing for agency work.",
	},
	{
		id: "customize",
		category: "customize",
		title: "Can I change copy and layout after install?",
		content:
			"Every block is yours to edit. Swap headlines for your product, trim sections, restyle with your tokens, or remove motion — the source lives in your project, not behind a package import.",
	},
	{
		id: "frameworks",
		category: "install",
		title: "Which stacks are supported?",
		content:
			"Any React setup that runs shadcn/ui — Next.js, Vite, Remix, and others. Blocks use your existing components.json aliases and design tokens.",
	},
	{
		id: "motion",
		category: "customize",
		title: "Do animations respect reduced motion?",
		content:
			"Yes. Blocks check prefers-reduced-motion and fall back to static layouts. Motion is packaging — your landing page stays readable when users request less animation.",
	},
	{
		id: "copy-prompt",
		category: "install",
		title: "What is Copy Prompt?",
		content: `Each block page includes a Copy Prompt — a ready-made instruction for Cursor or other AI tools to install and adapt the section. Use it when you want a guided handoff instead of the CLI alone.`,
	},
	{
		id: "tokens",
		category: "customize",
		title: "Will blocks match my brand colors?",
		content:
			"Blocks use semantic tokens — background, foreground, muted, primary — from your globals.css. After install, they inherit your theme without hardcoded palette lock-in.",
	},
	{
		id: "preview",
		category: "install",
		title: "Can I preview before installing?",
		content: `Browse ${faqSite.domain}/blocks, toggle light and dark preview, and read the source explorer. When a section fits your landing page, copy the CLI command or prompt and add it to your repo.`,
	},
];

export const faqSplitHeader = {
	eyebrow: "FAQ",
	title: "Questions before you ship",
	description:
		"Quick answers about installing blocks, Pro licensing, and tailoring sections for your landing page.",
	supportLabel: "Browse the library",
	supportHref: "/blocks",
} as const;

export const faqLandingHeader = {
	eyebrow: "FAQ",
	title: `Questions about ${faqSite.name}`,
	description:
		"Quick answers about the registry, install flow, and what lands in your project after you run the CLI.",
	supportLabel: "Browse the library",
	supportHref: "/blocks",
} as const;

export const faqCenteredHeader = {
	eyebrow: "Support",
	title: "Common questions",
	description:
		"Everything you need to pick blocks, install with shadcn, and ship a polished landing page.",
} as const;

export function faqsByCategory(category: FaqCategoryId) {
	return faqItems.filter((item) => item.category === category);
}
```

### `registry/blocks/faqs/7/faqs-section.tsx`

```tsx
"use client";

import { useRef, useState } from "react";
import { ChevronDownIcon } from "lucide-react";
import { AnimatePresence, motion, useInView, useReducedMotion } from "motion/react";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { faqItems, faqSplitHeader } from "../faqs-data";
import { SectionShell } from "@/components/layout-contract";

const spring = { type: "spring" as const, duration: 0.3, bounce: 0 };

const featuredItems = faqItems.slice(0, 6);

export function FaqsCardGridSection() {
	const reduceMotion = useReducedMotion();
	const ref = useRef<HTMLElement>(null);
	const inView = useInView(ref, motionViewport);
	const [openId, setOpenId] = useState<string | null>(featuredItems[0]?.id ?? null);

	const itemVariants = reduceMotion
		? { hidden: { opacity: 1 }, visible: { opacity: 1 } }
		: {
				hidden: { opacity: 0, filter: "blur(4px)", y: 8 },
				visible: { opacity: 1, filter: "blur(0px)", y: 0, transition: spring },
			};

	return (
		<SectionShell spacingMode="section" ref={ref}>
			<motion.div
				animate={inView ? "visible" : "hidden"}
				className="mx-auto w-full max-w-5xl"
				initial="hidden"
				variants={{
					hidden: {},
					visible: { transition: { staggerChildren: motionStagger.item, delayChildren: 0.04 } },
				}}
			>
				<motion.div className="max-w-2xl" variants={itemVariants}>
					<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">
						{faqSplitHeader.eyebrow}
					</p>
					<h2 className="mt-3 text-balance font-medium text-2xl tracking-tight md:text-4xl">
						Pick a topic
					</h2>
					<p className="mt-4 text-pretty text-muted-foreground text-sm md:text-base">
						Card grid for scanners — expand one answer at a time without leaving the
						page rhythm.
					</p>
				</motion.div>

				<div className="mt-10 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
					{featuredItems.map((item, index) => {
						const isOpen = openId === item.id;

						return (
							<motion.div key={item.id} variants={itemVariants}>
								<button
									aria-expanded={isOpen}
									className={cn(
										"group flex h-full w-full flex-col rounded-xl border border-border bg-card/40 p-4 text-left shadow-[0_1px_0_rgba(0,0,0,0.03)] transition-[transform,box-shadow,border-color]",
										"hover:-translate-y-0.5 hover:shadow-md active:scale-[0.98]",
										isOpen && "border-foreground/15 bg-card",
										"dark:shadow-[0_1px_0_rgba(255,255,255,0.04)]",
									)}
									onClick={() => setOpenId(isOpen ? null : item.id)}
									type="button"
								>
									<div className="flex items-start justify-between gap-3">
										<span className="font-mono text-muted-foreground text-xs tabular-nums">
											{String(index + 1).padStart(2, "0")}
										</span>
										<ChevronDownIcon
											className={cn(
												"size-4 shrink-0 text-muted-foreground transition-transform",
												isOpen && "rotate-180",
											)}
										/>
									</div>
									<p className="mt-3 font-medium text-sm leading-snug">{item.title}</p>
									<AnimatePresence initial={false}>
										{isOpen ? (
											<motion.p
												animate={{ opacity: 1, height: "auto" }}
												className="mt-3 text-pretty text-muted-foreground text-xs leading-relaxed"
												exit={{ opacity: 0, height: 0 }}
												initial={{ opacity: 0, height: 0 }}
												transition={spring}
											>
												{item.content}
											</motion.p>
										) : null}
									</AnimatePresence>
								</button>
							</motion.div>
						);
					})}
				</div>
			</motion.div>
		</SectionShell>
	);
}
```
