Skip to main content
All articles
Tutorial May 5, 2026 8 min read

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.


Build it with Ninna UI

Accessible React components, CSS-only theming, Tailwind v4 native.

Get Started

Keep reading