Workspace / block / pricing

Minimal Pricing

One price, stated large, with what it includes in a single measured list. For products confident enough to have one answer.

Workspaceblockpricing

paxfx add blocks/block-pricing-minimal-05local only

Tier: low. Reduced motion: Fully static.

"use client";

import { cn, useInView, useReducedMotion, type BlockCopy } from "@pax-sh/blocks";

export interface PricingMinimalProps {
  copy?: BlockCopy;
  price?: string;
  cadence?: string;
  includes?: string[];
  className?: string;
}

const DEFAULT_COPY: BlockCopy = {
  eyebrow: "Pricing",
  title: "One plan. Everything in it.",
  description: "No tiers to compare, no feature matrix to squint at. This is the whole offer.",
  primaryCta: { label: "Start today", href: "/docs/installation" },
};

const DEFAULT_INCLUDES = [
  "Every block, component, and template",
  "Unlimited projects, commercial included",
  "MCP registry for coding agents",
  "All future releases while subscribed",
  "Support answered by the people who built it",
];

/**
 * A single-answer pricing section: one price stated very large, one measured
 * list of what it includes, one CTA. The entrance is a quiet settle, skipped
 * entirely under reduced motion.
 */
export function PricingMinimal({
  copy = DEFAULT_COPY,
  price = "$29",
  cadence = "per editor, per month",
  includes = DEFAULT_INCLUDES,
  className,
}: PricingMinimalProps) {
  const reduced = useReducedMotion();
  const [ref, inView] = useInView<HTMLDivElement>({ threshold: 0.25 });
  const settled = reduced || inView;

  const enter = (step: number) =>
    reduced
      ? undefined
      : ({
          opacity: settled ? 1 : 0,
          transform: settled ? "translateY(0)" : "translateY(12px)",
          transition: `opacity 0.55s ease ${step * 0.09}s, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1) ${step * 0.09}s`,
        } as const);

  return (
    <section
      className={cn(
        "bg-[var(--pax-paper,#f6f2ea)] px-6 py-20 text-[var(--pax-ink,#12110f)] md:px-10 md:py-28",
        className,
      )}
      data-pax-asset="block-pricing-minimal-05"
    >
      <div ref={ref} className="mx-auto flex max-w-6xl flex-col items-center text-center">
        {copy.eyebrow ? (
          <p
            className="m-0 text-xs font-medium uppercase tracking-[0.22em] text-[var(--pax-mute,#6b645c)]"
            style={enter(0)}
          >
            {copy.eyebrow}
          </p>
        ) : null}
        <h2
          className="mt-3 text-3xl font-semibold tracking-tight [text-wrap:balance] md:text-4xl"
          style={enter(1)}
        >
          {copy.title}
        </h2>
        {copy.description ? (
          <p
            className="mt-4 max-w-md text-base leading-relaxed text-[var(--pax-mute,#6b645c)]"
            style={enter(2)}
          >
            {copy.description}
          </p>
        ) : null}

        <p className="mt-12 flex items-baseline gap-3" style={enter(3)}>
          <span className="text-7xl font-semibold tracking-tight [font-variant-numeric:tabular-nums] md:text-8xl">
            {price}
          </span>
          <span className="text-sm text-[var(--pax-mute,#6b645c)]">{cadence}</span>
        </p>

        <ul className="m-0 mt-10 list-none space-y-3 p-0 text-left" style={enter(4)}>
          {includes.map((item) => (
            <li key={item} className="flex items-start gap-3 text-sm md:text-base">
              <span className="sr-only">Included:</span>
              <svg
                viewBox="0 0 16 16"
                width="16"
                height="16"
                aria-hidden="true"
                className="mt-1 shrink-0"
              >
                <path
                  d="m3 8.5 3.2 3L13 4.5"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth="1.75"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
              {item}
            </li>
          ))}
        </ul>

        {copy.primaryCta ? (
          <a
            href={copy.primaryCta.href}
            className="mt-12 rounded-full bg-[var(--pax-ink,#12110f)] px-8 py-3.5 text-sm font-semibold text-[var(--pax-paper,#f6f2ea)] outline-offset-4 transition-colors hover:bg-[var(--pax-night,#0b0d10)] focus-visible:outline focus-visible:outline-2"
            style={enter(5)}
            {...(copy.primaryCta.goal ? { "data-fast-goal": copy.primaryCta.goal } : {})}
          >
            {copy.primaryCta.label}
          </a>
        ) : null}
      </div>
    </section>
  );
}