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.