Pricing table
The pricing section done as a real feature comparison, not four identical bullet lists: each plan has its own checklist of included/excluded features (real Check/X icons, not text alone), a live Bill monthly/Bill annually switch that recomputes every price and reveals a "Save $N" badge on the recommended tier, and that tier promoted with a fixed dark card that stays legible in both themes.
Built with
How it’s built
- 01The Bill monthly/Bill annually switch is a real
FormFieldSet type="switch", controlled (checked/onChange) — both flanking labels and the "Save $N" badge all derive from the sameisAnnualstate, so they can never disagree. - 02Every feature row uses a real
CheckIcon/XIconcolored via a wrapping<span>— the icons draw withstroke="currentColor"and take no color prop themselves, so the color has to come from an ancestor's text color. - 03The featured plan's dark card uses a fixed hex (
bg-[#241206]), not an oks-ui semantic token like--oks-color-primary-950— that token is *designed* to flip to a near-white shade in dark mode, which would wash the card's white text out to near-invisible instead of staying a deliberately dark, promoted card in both themes. - 04The grid needs its own
@container—@2xl:grid-cols-4has nothing to evaluate against without an ancestor establishing container-type, so it silently never applies and the plans stay stacked in one column regardless of how wide the page is.
Source
Two files · copy & pasteStyling: this source uses Tailwind utility classes and this site’s colour tokens (for example text-ink, bg-paper and border-line). The oks-ui components render without them; swap those classes for your own.
import { useState } from "react";
import { Button } from "oks-ui/button";
import { Card, CardBody } from "oks-ui/card";
import { Chip } from "oks-ui/chip";
import { FormFieldSet } from "oks-ui/form-field-set";
import { PageTitle } from "oks-ui/page-title";
import { CheckIcon, XIcon } from "./icons";
const plans = [
{
name: "Intro",
monthly: 9,
annual: 90,
featured: false,
features: [
{ label: "Upload video up to 720p", included: true },
{ label: "Attachment & post scheduling", included: false },
{ label: "Set your own rates", included: false },
{ label: "Exclusive deals", included: false },
{ label: "Advanced statistics", included: false },
],
},
{
name: "Base",
monthly: 15,
annual: 150,
featured: false,
features: [
{ label: "Upload video in HD", included: true },
{ label: "Attachment & post scheduling", included: true },
{ label: "Set your own rates", included: false },
{ label: "Exclusive deals", included: false },
{ label: "Advanced statistics", included: false },
],
},
{
name: "Pro",
monthly: 18,
annual: 176,
featured: true,
features: [
{ label: "Upload video in HD", included: true },
{ label: "Attachment & post scheduling", included: true },
{ label: "Set your own rates", included: true },
{ label: "Exclusive deals", included: true },
{ label: "Advanced statistics", included: false },
],
},
{
name: "Enterprise",
monthly: 39,
annual: 390,
featured: false,
features: [
{ label: "Upload video in 4K", included: true },
{ label: "Attachment & post scheduling", included: true },
{ label: "Set your own rates", included: true },
{ label: "Exclusive deals", included: true },
{ label: "Advanced statistics", included: true },
],
},
];
const PricingTable = () => {
const [isAnnual, setIsAnnual] = useState(false);
return (
// @container so the @2xl:grid-cols-4 below has a container to query —
// without an ancestor establishing container-type, a container-query
// utility has nothing to evaluate against and silently never applies.
<div className="@container flex flex-col items-center gap-8 px-10 pt-20 pb-40 text-center @3xl:px-12">
<PageTitle
as="h2"
title="The right plan for your business"
subtitle="Straightforward pricing that scales with your team — switch between monthly and annual billing anytime."
classNames={{
base: "flex-col items-center gap-2",
title: "text-3xl font-bold tracking-tight text-ink",
subtitle: "max-w-lg text-[0.9rem] text-ink-muted",
}}
/>
<div className="flex items-center gap-3">
<span
className={`whitespace-nowrap text-[0.9rem] font-medium ${!isAnnual ? "text-ink" : "text-ink-muted"}`}
>
Bill monthly
</span>
<FormFieldSet
type="switch"
name="billingPeriod"
label={
<span className="sr-only">Bill annually instead of monthly</span>
}
checked={isAnnual}
onChange={(v: unknown) => setIsAnnual(Boolean(v))}
/>
<span
className={`whitespace-nowrap text-[0.9rem] font-medium ${isAnnual ? "text-ink" : "text-ink-muted"}`}
>
Bill annually
</span>
</div>
<div className="grid w-full gap-5 @2xl:grid-cols-4">
{plans.map((plan) => (
<Card
key={plan.name}
shadow={plan.featured ? "lg" : "sm"}
// A fixed hex, not an oks-ui semantic token — the featured
// card's white text needs the background to stay dark in
// BOTH themes. A token like --oks-color-primary-950 flips to a
// near-white shade in dark mode by design, which would wash
// the white text out to near-invisible.
className={plan.featured ? "bg-[#241206]" : ""}
>
<CardBody className="flex flex-col gap-5 p-6 text-left">
<div className="flex items-center justify-between gap-2">
<h3
className={`text-[0.95rem] font-semibold ${plan.featured ? "text-white" : "text-ink"}`}
>
{plan.name}
</h3>
{plan.featured && isAnnual ? (
<Chip size="sm" variant="solid" color="primary">
Save ${plan.monthly * 12 - plan.annual}
</Chip>
) : null}
</div>
<div className="flex flex-col gap-3">
{plan.features.map((f) => (
<div key={f.label} className="flex items-start gap-2">
<span
className={`shrink-0 ${
f.included
? plan.featured
? "text-[color:var(--oks-color-success-400)]"
: "text-[color:var(--oks-color-success-700)]"
: plan.featured
? "text-white/30"
: "text-ink-faint"
}`}
>
{f.included ? (
<CheckIcon size={16} />
) : (
<XIcon size={16} />
)}
</span>
<span
className={`text-[0.85rem] ${
plan.featured
? f.included
? "text-white"
: "text-white/60"
: f.included
? "text-ink-soft"
: "text-ink-faint"
}`}
>
{f.label}
</span>
</div>
))}
</div>
<p className={plan.featured ? "text-white" : "text-ink"}>
<span className="text-[1.9rem] font-extrabold tracking-tight">
${isAnnual ? plan.annual : plan.monthly}
</span>
<span
className={`text-[0.85rem] ${plan.featured ? "text-white/70" : "text-ink-muted"}`}
>
/{isAnnual ? "year" : "month"}
</span>
</p>
<Button
fullWidth
radius="full"
color="primary"
variant={plan.featured ? "solid" : "soft"}
>
{plan.featured ? "Try 1 month" : "Choose plan"}
</Button>
</CardBody>
</Card>
))}
</div>
</div>
);
};
export default PricingTable;It imports ./icons, so save this next to it:
export const CheckIcon = ({ size = 18 }: { size?: number }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M5 13l4 4L19 7" />
</svg>
);
};
export const XIcon = ({ size = 18 }: { size?: number }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
);
};More patterns

Analytics dashboardApp screens
A full fintech app screen — collapsible sidebar, header search, wallet cards, an earnings chart, savings goals, and a transactions table.

Sign-in screenAuth
A split-screen sign-in with a feature panel, Google SSO, and remember-me.

Settings panelApp screens
A real settings screen — top tabs, two-column sections, and a sortable, selectable billing history table.