# Faqs 8

> Sticky topic rail with registry stats + synced accordion — editorial FAQ for long landings.

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

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/faqs-8
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/faqs-8-light-42e98bbb2a5b6796.png
- Registry item: https://nusaiba.dev/r/base-nova/faqs-8.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-8.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`, `accordion`

## 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/8/faqs-section.tsx`

```tsx
"use client";

import { useRef, useState } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
import {
	Accordion,
	AccordionContent,
	AccordionItem,
	AccordionTrigger,
} from "@/components/ui/accordion";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { faqItems, faqSite, faqSplitHeader } from "../faqs-data";
import { SectionShell, ContentRail } from "@/components/layout-contract";

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

const tocStats = [
	{ label: "Free blocks", value: "40+" },
	{ label: "Motion patterns", value: "50+" },
	{ label: "Install path", value: "CLI" },
] as const;

export function FaqsTocRailSection() {
	const reduceMotion = useReducedMotion();
	const ref = useRef<HTMLElement>(null);
	const inView = useInView(ref, motionViewport);
	const [activeId, setActiveId] = useState(faqItems[0]?.id ?? "");

	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}>
			<ContentRail maxWidth="max-w-5xl" className="grid gap-10 lg:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] lg:gap-14">
				<motion.aside
					animate={inView ? "visible" : "hidden"}
					className="lg:sticky lg:top-24 lg:self-start"
					initial="hidden"
					variants={{
						hidden: {},
						visible: { transition: { staggerChildren: motionStagger.item, delayChildren: 0.04 } },
					}}
				>
					<motion.p
						className="font-medium text-primary text-xs uppercase tracking-[0.22em]"
						variants={itemVariants}
					>
						{faqSplitHeader.eyebrow}
					</motion.p>
					<motion.h2
						className="mt-3 text-balance font-medium text-2xl tracking-tight md:text-3xl"
						variants={itemVariants}
					>
						{faqSplitHeader.title}
					</motion.h2>
					<motion.p
						className="mt-4 text-pretty text-muted-foreground text-sm leading-relaxed"
						variants={itemVariants}
					>
						Jump between topics — useful when this section sits above pricing on a
						landing page.
					</motion.p>

					<motion.ul className="mt-6 space-y-1 border-border border-t pt-6" variants={itemVariants}>
						{faqItems.map((item) => (
							<li key={item.id}>
								<button
									className={cn(
										"w-full rounded-md px-2 py-2 text-left text-sm transition-colors",
										"active:scale-[0.99]",
										activeId === item.id
											? "bg-muted font-medium text-foreground"
											: "text-muted-foreground hover:text-foreground",
									)}
									onClick={() => setActiveId(item.id)}
									type="button"
								>
									{item.title}
								</button>
							</li>
						))}
					</motion.ul>

					<motion.dl
						className="mt-8 grid grid-cols-3 gap-3 border-border border-t pt-6"
						variants={itemVariants}
					>
						{tocStats.map((stat) => (
							<div key={stat.label}>
								<dt className="text-muted-foreground text-xs">{stat.label}</dt>
								<dd className="mt-1 font-medium text-sm tabular-nums">{stat.value}</dd>
							</div>
						))}
					</motion.dl>
					<motion.p className="mt-4 text-muted-foreground text-xs" variants={itemVariants}>
						Registry · {faqSite.namespace}
					</motion.p>
				</motion.aside>

				<motion.div
					animate={inView ? "visible" : "hidden"}
					initial="hidden"
					variants={{
						hidden: {},
						visible: { transition: { staggerChildren: motionStagger.item, delayChildren: 0.08 } },
					}}
				>
					<Accordion
						onValueChange={(value) => setActiveId(value[0] ?? faqItems[0].id)}
						value={[activeId]}
					>
						{faqItems.map((item) => (
							<motion.div id={item.id} key={item.id} variants={itemVariants}>
								<AccordionItem value={item.id}>
									<AccordionTrigger className="py-4 text-left hover:no-underline">
										{item.title}
									</AccordionTrigger>
									<AccordionContent className="pb-4 text-muted-foreground text-sm leading-relaxed">
										{item.content}
									</AccordionContent>
								</AccordionItem>
							</motion.div>
						))}
					</Accordion>
				</motion.div>
			</ContentRail>
		</SectionShell>
	);
}
```
