Building a Pricing Page with Ninna UI: Step-by-Step
A practical, copy-paste-ready guide to building a responsive, accessible pricing page with toggle, cards, and FAQ — using Ninna UI components and Tailwind v4.
By Ninna UI Team
A pricing page is one of the highest-stakes pages in a SaaS app — it needs to be clear, responsive, accessible, and fast. Let's build one with Ninna UI and Tailwind CSS v4.
1. Page structure
import { Container } from "@ninna-ui/layout";
import { Heading, Text } from "@ninna-ui/primitives";
export default function PricingPage() {
return (
<Container className="py-16 max-w-5xl">
<div className="text-center mb-12">
<Heading as="h1" size="4xl">Simple, transparent pricing</Heading>
<Text size="lg" className="text-base-content/60 mt-3">Choose the plan that fits your team.</Text>
</div>
{/* Toggle + Cards + FAQ */}
</Container>
);
}2. Monthly/yearly toggle
import { Switch } from "@ninna-ui/forms";
import { useState } from "react";
const [yearly, setYearly] = useState(false);
<div className="flex items-center justify-center gap-3 mb-8">
<Text>Monthly</Text>
<Switch checked={yearly} onCheckedChange={setYearly} />
<Text>Yearly <Badge variant="soft" color="success" size="sm">Save 20%</Badge></Text>
</div>3. Pricing cards
import { Card } from "@ninna-ui/data-display";
import { Button, Heading, Text, Badge } from "@ninna-ui/primitives";
import { VStack } from "@ninna-ui/layout";
const plans = [
{ name: "Starter", monthly: 0, yearly: 0, features: ["1 project", "Community support"] },
{ name: "Pro", monthly: 29, yearly: 23, features: ["10 projects", "Priority support"], popular: true },
{ name: "Team", monthly: 99, yearly: 79, features: ["Unlimited projects", "SSO + SAML"] },
];
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{plans.map((plan) => (
<Card key={plan.name} className={`p-8 ${plan.popular ? "border-primary ring-2 ring-primary/20" : ""}`}>
<VStack gap="4">
{plan.popular && <Badge variant="solid" color="primary">Most Popular</Badge>}
<Heading as="h3" size="xl">{plan.name}</Heading>
<Text size="3xl" weight="bold">
${yearly ? plan.yearly : plan.monthly}
<span className="text-base font-normal text-base-content/60">/mo</span>
</Text>
<Button color={plan.popular ? "primary" : "outline"} fullWidth>Get Started</Button>
<ul className="space-y-2 text-sm">
{plan.features.map((f) => <li key={f}>✓ {f}</li>)}
</ul>
</VStack>
</Card>
))}
</div>4. FAQ section with structured data
Add an FAQ section with FAQPage schema for rich results:
import { Accordion } from "@ninna-ui/navigation";
<Accordion type="single" collapsible>
<Accordion.Item value="q1">
<Accordion.Trigger>Can I change plans anytime?</Accordion.Trigger>
<Accordion.Content>Yes, upgrade or downgrade at any time. Changes are prorated.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="q2">
<Accordion.Trigger>Is there a free trial?</Accordion.Trigger>
<Accordion.Content>The Starter plan is free forever. Pro and Team have a 14-day trial.</Accordion.Content>
</Accordion.Item>
</Accordion>Don't forget to inject FAQPage JSON-LD schema for Google rich results and AI citation. Use the buildFAQPageJsonLd helper from app/utils/structuredData.ts.
Wrapping up
You now have a complete, responsive, accessible pricing page with a billing toggle, three-tier cards, and an FAQ section — all with Ninna UI components and Tailwind v4. The page is accessible by default (keyboard navigation, ARIA, focus management) and themeable through CSS.