The SaaS Pricing Page: Wireframes, Copy, and a Laravel + Tailwind Build

By BuildVoyage Team September 2, 2025 5 min read Updated 1 day ago

Why pricing pages fail (and how to fix yours)

When I shipped the first pricing page for a developer tool in 2021, I did what most founders do: I copied a design I liked, swapped the colors, and wrote the plan names five minutes before launch. Conversions were… fine. Not great. It took three iterations to realise two uncomfortable truths:

  • People don’t buy plans; they buy outcomes.
  • Your pricing page is a narrative, not a grid.

Below is the process I now use to plan the story, draft honest copy, and ship the page with Laravel + Tailwind in a single afternoon.

Step 1 — Write the headline like a promise

If your H1 reads “Simple, transparent pricing”, it says nothing. Try this instead:

“Ship release notes your customers actually read — from $12/mo.”

It’s specific, it names the outcome, and it anchors price early. If you need help writing SEO‑friendly titles, skim our checklist: The Ultimate SEO Checklist for SaaS Websites.

Step 2 — Wireframe the narrative

Sketch this order (pen + paper works):

  1. Headline + subhead stating outcome and time‑to‑value.
  2. Toggle: Monthly vs Annual (show annual savings in real dollars).
  3. Plan cards with one sentence summaries (“For solopreneurs shipping weekly updates”).
  4. Social proof: logos, a short quote, or a simple metric (“3.2M emails sent last year”).
  5. FAQ block to kill obvious objections.

Keep the first screen decision‑friendly. Table comparisons are for scrollers, not deciders.

Step 3 — Draft copy that sounds like a person

You don’t need flowery language. You need clarity. Use phrases customers actually say on calls. If they say “I don’t have time to design emails,” your copy should read “Save 3–5 hours a week on email design.”

A simple formula per plan:

  • Who it’s for
  • What it unlocks (1–2 outcomes)
  • One specific guardrail (“Up to 3 team members”)

Step 4 — Build it in Laravel + Tailwind

Here’s a trimmed Blade snippet I reuse. It’s intentionally boring — boring converts.

// resources/views/pricing.blade.php
@php($annual = request('annual') === '1')
@php($price = fn($m, $y) => $annual ? $y : $m)

<div class="flex items-center gap-3 mb-6">
  <span class="text-sm">Monthly</span>
  <label class="inline-flex items-center cursor-pointer">
    <input type="checkbox" class="sr-only" onchange="location='?annual='+(this.checked?1:0)" {{ $annual ? 'checked' : '' }}>
    <span class="w-12 h-6 bg-sand-300 rounded-full p-1 transition">
      <span class="block w-4 h-4 bg-white rounded-full shadow translate-x-0 {{ $annual ? 'translate-x-6' : '' }}"></span>
    </span>
  </label>
  <span class="text-sm">Annual <span class="chip-sm bg-saffron-100 text-saffron-700">save 20%</span></span>
  <span class="text-xs text-ink-500">(real $ savings, not vague %)</span>
  </div>

<div class="grid md:grid-cols-3 gap-4">
  @foreach ([
    ['Starter','For solo builders','12','120'],
    ['Growth','For small teams','29','290'],
    ['Business','For steady teams','79','790'],
  ] as [$name,$blurb,$m,$y])
    <div class="card p-6 border border-sand-300/60 {{ $name==='Growth' ? 'ring-2 ring-saffron-400' : '' }}">
      <div class="flex items-baseline justify-between">
        <h3 class="font-heading text-xl">{{ $name }}</h3>
        @if($name==='Growth')<span class="chip-sm">Most popular</span>@endif
      </div>
      <p class="mt-1 text-ink-600">{{ $blurb }}</p>
      <div class="mt-3 text-3xl font-semibold">
        ${{ $price($m,$y) }}<span class="text-base text-ink-500">/{{ $annual?'yr':'mo' }}</span>
      </div>
      <ul class="mt-3 space-y-2 text-sm">
        <li>• Unlimited projects</li>
        <li>• Changelog widget</li>
        <li>• Email sending included</li>
      </ul>
      <a href="{{ route('checkout.start', strtolower($name)) }}" class="btn btn-primary mt-4">Start {{ $name }}</a>
    </div>
  @endforeach
</div>

For styling, lean on Tailwind utility classes you already use in the app. Avoid bespoke CSS if you can; it’s one more surface area to maintain.

Step 5 — Measure like an adult

Track three things first:

  • Click‑through rate on primary CTA per plan
  • Annual vs monthly toggle usage
  • Drop‑offs between pricing → checkout

If you want to get nerdy, instrument “compare table opened” and “FAQ opened” events. Those correlate with higher intent.

Common mistakes I keep seeing

  • Four+ plans with indistinguishable names
  • Pricing hidden behind a demo call (for SMB… don’t)
  • Feature tables that list your database schema instead of outcomes

If you’re earlier in the journey, you might prefer a pre‑launch approach. We’ve covered that too: SaaS Pre‑Launch Checklist and a Go‑to‑Market Strategy Checklist.

A quick checklist before you ship

  • One clear promise above the fold
  • Annual savings shown in dollars
  • Social proof visible without scrolling
  • 3 FAQs that address real objections
  • Obvious CTA copy (“Start Growth” beats “Get Started”)

Ship the boring version first. You can always redesign. You can’t recover momentum you never built.

Related articles

Frequently asked questions

What should I A/B test first on a pricing page?
Start with the headline and CTA copy, then price anchoring (monthly vs annual toggle) and social proof placement. These are easy to change and have outsized impact.
How many plans are ideal?
Three plans with a clear ‘Most Popular’ anchor usually work best for early‑stage SaaS. Add enterprise only when you’ve done at least 3 bespoke deals.
Do I need a free plan?
Not always. If your onboarding is smooth and the product delivers value quickly, a free trial (7–14 days) is usually enough, especially for a B2B tool.
About the author

BuildVoyage Team writes about calm, steady growth for indie products. BuildVoyage highlights real products, their stacks, and milestones to help makers learn from each other.