Accordion expand per tier with price in trigger and features inside — grid-reveal stagger on open panels.
@nusaiba
registry/blocks/pricing/6/pricing-section.tsx
"use client";
import { useState } from "react";
import { CheckIcon } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { defaultSpring, motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";
const tiers = [
{
id: "starter",
name: "Starter",
price: "$0",
period: "forever",
summary: "Free registry blocks and CLI install path.",
features: ["All free blocks", "Shadcn CLI install", "Copy prompt", "Live preview"],
},
{
id: "pro",
name: "Pro",
price: "$147",
period: "once",
summary: "Pro catalog, token, and commercial license.",
features: [
"Every Pro block",
"Private registry token",
"Commercial license",
"Motion pattern docs",
],
popular: true,
},
{
id: "enterprise",
name: "Enterprise",
price: "Custom",
period: "annual",
summary: "Team workspace, SSO, and dedicated support.",
features: ["Everything in Pro", "Team seats", "Role-based access", "Priority support"],
},
];
export function PricingAccordionSection() {
const reduceMotion = useReducedMotion();
const [open, setOpen] = useState<string[]>(["pro"]);
return (
<SectionShell spacingMode="section" className="bg-background">
<ContentRail maxWidth="max-w-3xl" className="space-y-8 md:space-y-10">
<div className="max-w-xl space-y-2">
<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">
Expand
</p>
<h2 className="text-balance font-medium text-2xl tracking-tight md:text-4xl">
One tier at a time
</h2>
<p className="text-pretty text-muted-foreground text-sm md:text-base">
Open a plan to see features — accordion clarity without a full comparison
table.
</p>
</div>
<motion.div
className="overflow-hidden border border-border bg-background shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:shadow-[0_1px_2px_rgba(0,0,0,0.2)]"
initial={reduceMotion ? false : { opacity: 0, y: 12, filter: "blur(4px)" }}
transition={defaultSpring}
viewport={motionViewport}
whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
>
<Accordion
className="divide-y divide-border"
onValueChange={setOpen}
value={open}
>
{tiers.map((tier, index) => (
<motion.div
initial={reduceMotion ? false : { opacity: 0, y: 8 }}
key={tier.id}
transition={{
...defaultSpring,
delay: index * motionStagger.item,
}}
viewport={motionViewport}
whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
>
<AccordionItem className="border-0 px-4 md:px-6" value={tier.id}>
<AccordionTrigger
className={cn(
"py-5 hover:no-underline md:py-6",
"after:absolute after:top-1/2 after:left-1/2 after:size-10 after:-translate-1/2",
)}
>
<div className="flex flex-1 flex-wrap items-center justify-between gap-3 pr-4 text-left">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<span className="font-medium">{tier.name}</span>
{tier.popular ? (
<span className="font-medium text-[10px] text-primary uppercase tracking-[0.18em]">
Popular
</span>
) : null}
</div>
<p className="text-pretty text-muted-foreground text-sm">
{tier.summary}
</p>
</div>
<div className="text-right">
<p className="font-medium text-xl tabular-nums tracking-tight md:text-2xl">
{tier.price}
</p>
<p className="text-muted-foreground text-xs">{tier.period}</p>
</div>
</div>
</AccordionTrigger>
<AccordionContent className="pb-5 md:pb-6">
<ul className="mb-5 space-y-2.5">
{tier.features.map((feature) => (
<li className="flex items-start gap-2 text-sm" key={feature}>
<CheckIcon className="mt-0.5 size-4 shrink-0 text-primary" />
{feature}
</li>
))}
</ul>
<Button
className="active:scale-[0.96]"
variant={tier.popular ? "default" : "outline"}
>
{tier.price === "Custom" ? "Contact sales" : "Get started"}
</Button>
</AccordionContent>
</AccordionItem>
</motion.div>
))}
</Accordion>
</motion.div>
</ContentRail>
</SectionShell>
);
}





























