Innovations

Before / After Comparison

Toggle-based before/after comparison block showing client transformation across multiple dimensions.

Preview

Source

tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { ArrowRight, CheckCircle, XCircle } from "lucide-react";

const comparisons = [
  {
    id: 1,
    label: "Website Performance",
    before: {
      headline: "Slow, outdated site",
      description: "3.8s load time, no mobile optimization, built on an aging CMS that required a developer for every change.",
      stats: "12% conversion rate · 72% bounce rate",
    },
    after: {
      headline: "Fast, modern, self-serve",
      description: "0.9s load time, fully responsive, headless CMS with visual editor — team makes changes in minutes, not weeks.",
      stats: "34% conversion rate · 38% bounce rate",
    },
  },
  {
    id: 2,
    label: "Lead Generation",
    before: {
      headline: "Random, inconsistent leads",
      description: "No defined funnel, relying on word-of-mouth, no email capture, no nurture sequences, sporadic follow-up.",
      stats: "~8 leads/month · $420 avg cost per lead",
    },
    after: {
      headline: "Predictable pipeline",
      description: "Multi-channel funnel with content-gated offers, automated email sequences, and a CRM synced to every touchpoint.",
      stats: "~47 leads/month · $68 avg cost per lead",
    },
  },
  {
    id: 3,
    label: "Brand Positioning",
    before: {
      headline: "Generic and forgettable",
      description: "Logo from a stock site, inconsistent colors across pages, messaging that sounded like every competitor.",
      stats: "2% brand recall · 0 unsolicited referrals",
    },
    after: {
      headline: "Distinct and memorable",
      description: "Custom identity system with a clear visual language, sharp messaging framework, and a brand guide the team actually uses.",
      stats: "61% brand recall · 14 referrals in first quarter",
    },
  },
];

export default function BeforeAfter() {
  const [view, setView] = useState<"before" | "after">("before");

  return (
    <section className="bg-background py-16 px-4 sm:px-6 lg:px-8">
      <div className="mx-auto max-w-4xl">
        {/* Header */}
        <div className="text-center mb-10">
          <p className="text-sm font-semibold uppercase tracking-widest text-primary mb-2">
            The Transformation
          </p>
          <h2 className="text-3xl font-bold text-foreground sm:text-4xl mb-4">
            Before vs. After
          </h2>
          <p className="text-muted-foreground max-w-xl mx-auto text-base">
            See exactly what changed — and why it matters.
          </p>
        </div>

        {/* Toggle */}
        <div className="flex justify-center mb-10">
          <div className="inline-flex rounded-full border border-border bg-muted p-1 gap-1">
            <button
              onClick={() => setView("before")}
              className={`rounded-full px-6 py-2 text-sm font-semibold transition-all duration-200 ${
                view === "before"
                  ? "bg-red-500 text-white shadow"
                  : "text-muted-foreground hover:text-foreground"
              }`}
            >
              Before
            </button>
            <button
              onClick={() => setView("after")}
              className={`rounded-full px-6 py-2 text-sm font-semibold transition-all duration-200 ${
                view === "after"
                  ? "bg-green-500 text-white shadow"
                  : "text-muted-foreground hover:text-foreground"
              }`}
            >
              After
            </button>
          </div>
        </div>

        {/* Cards */}
        <div className="space-y-4">
          {comparisons.map((item) => {
            const data = view === "before" ? item.before : item.after;
            const isBefore = view === "before";

            return (
              <div
                key={item.id}
                className={`rounded-2xl border p-6 transition-all duration-300 ${
                  isBefore
                    ? "border-red-200 bg-red-500/5"
                    : "border-green-200 bg-green-500/5"
                }`}
              >
                <div className="flex items-start gap-4">
                  <div
                    className={`mt-0.5 shrink-0 rounded-full p-1 ${
                      isBefore ? "text-red-500" : "text-green-500"
                    }`}
                  >
                    {isBefore ? (
                      <XCircle className="h-5 w-5" />
                    ) : (
                      <CheckCircle className="h-5 w-5" />
                    )}
                  </div>
                  <div className="flex-1 min-w-0">
                    <div className="flex flex-wrap items-center gap-2 mb-1">
                      <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                        {item.label}
                      </span>
                      <span
                        className={`text-xs rounded-full px-2 py-0.5 font-semibold ${
                          isBefore
                            ? "bg-red-100 text-red-600"
                            : "bg-green-100 text-green-700"
                        }`}
                      >
                        {isBefore ? "Before" : "After"}
                      </span>
                    </div>
                    <h3
                      className={`text-lg font-bold mb-1 ${
                        isBefore ? "text-red-700" : "text-green-800"
                      }`}
                    >
                      {data.headline}
                    </h3>
                    <p className="text-sm text-muted-foreground leading-relaxed mb-3">
                      {data.description}
                    </p>
                    <div
                      className={`inline-block rounded-full px-3 py-1 text-xs font-mono font-medium ${
                        isBefore
                          ? "bg-red-100 text-red-700"
                          : "bg-green-100 text-green-700"
                      }`}
                    >
                      {data.stats}
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>

        {/* CTA */}
        <div className="mt-10 flex justify-center">
          <Button size="lg" className="gap-2">
            Get these results for your business
            <ArrowRight className="h-4 w-4" />
          </Button>
        </div>
      </div>
    </section>
  );
}
Claude Code Instructions

CLI Install

npx innovations add before-after

Where to use it

Use this on case study pages or your services page to show the contrast between client's situation before and after working with you. In Astro: import BeforeAfter from '../components/innovations/case-studies/before-after'; <BeforeAfter client:visible /> In Next.js: import BeforeAfter from '@/components/innovations/case-studies/before-after'; Replace the comparisons array with your actual before/after data. Each row needs: label, before { headline, description, stats }, after { headline, description, stats }.