Web InnoventixFreeCode

Alternating Feature Section

Original · free

alternating image/text feature rows

byWeb InnoventixReact + Tailwind
featxalternatingfeatures
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/featx-alternating.json
featx-alternating.tsx
"use client";

import { useId, useRef, useState, type KeyboardEvent } from "react";
import { motion, useReducedMotion, type Variants } from "motion/react";

type VisualKey = "analytics" | "automation" | "collab";

type Feature = {
  id: string;
  eyebrow: string;
  dot: string;
  title: string;
  body: string;
  points: string[];
  cta: string;
  visual: VisualKey;
};

type Series = "revenue" | "users";

type SeriesData = { label: string; value: string; delta: string; bars: number[] };

const features: Feature[] = [
  {
    id: "analytics",
    eyebrow: "Real-time analytics",
    dot: "from-indigo-500 to-violet-500",
    title: "See what's working before the quarter ends",
    body: "Dashboards refresh the moment new data lands, so you catch a dip on Tuesday instead of in next month's review. Slice by channel, cohort, or region without waiting on the data team.",
    points: [
      "Live metrics with sub-second refresh",
      "Custom cohorts built in two clicks",
      "Alerts when a number moves more than you expect",
    ],
    cta: "Explore analytics",
    visual: "analytics",
  },
  {
    id: "automation",
    eyebrow: "Workflow automation",
    dot: "from-sky-500 to-indigo-500",
    title: "Put the busywork on autopilot",
    body: "Draw a workflow once and Cadence runs it forever — routing leads, syncing records, and nudging owners the second something stalls. No scripts, no brittle spreadsheets to babysit.",
    points: [
      "Drag-and-drop builder, zero code required",
      "300+ native integrations out of the box",
      "Retries and full audit logs on every run",
    ],
    cta: "See automations",
    visual: "automation",
  },
  {
    id: "collab",
    eyebrow: "Built for teams",
    dot: "from-emerald-500 to-teal-500",
    title: "Keep everyone on the same page, literally",
    body: "Comments, mentions, and shared views live right next to the data, so decisions happen in context instead of getting buried in a thread nobody can find a week later.",
    points: [
      "Real-time presence and inline comments",
      "Granular roles and per-view permissions",
      "Shareable views for clients and execs",
    ],
    cta: "Meet your team",
    visual: "collab",
  },
];

const seriesData: Record<Series, SeriesData> = {
  revenue: { label: "Revenue", value: "$482,900", delta: "+12.4%", bars: [42, 55, 48, 68, 60, 84, 96] },
  users: { label: "Active users", value: "18,204", delta: "+8.1%", bars: [50, 46, 62, 57, 73, 69, 90] },
};

const order: Series[] = ["revenue", "users"];
const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

function CheckIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 20 20" fill="none" className={className} aria-hidden="true">
      <path d="M4 10.5l3.5 3.5L16 5.5" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function ArrowIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 20 20" fill="none" className={className} aria-hidden="true">
      <path d="M4 10h11M11 5l5 5-5 5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function TrendIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" fill="none" className={className} aria-hidden="true">
      <path d="M3 11l3.5-3.5L9 10l4-5M13 5H9.5M13 5v3.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function PlayIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 20 20" fill="currentColor" className={className} aria-hidden="true">
      <path d="M6 4.5v11a1 1 0 001.53.85l8.5-5.5a1 1 0 000-1.7l-8.5-5.5A1 1 0 006 4.5z" />
    </svg>
  );
}

function PauseIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 20 20" fill="currentColor" className={className} aria-hidden="true">
      <path d="M6 4h3v12H6zM11 4h3v12h-3z" />
    </svg>
  );
}

const container: Variants = {
  hidden: {},
  show: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } },
};

const item: Variants = {
  hidden: { opacity: 0, y: 26 },
  show: { opacity: 1, y: 0, transition: { duration: 0.55, ease: "easeOut" } },
};

export default function FeatxAlternating() {
  const prefersReduced = useReducedMotion();
  const reduce = !!prefersReduced;

  const [playing, setPlaying] = useState(true);
  const [series, setSeries] = useState<Series>("revenue");

  const uid = useId();
  const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);

  const onTabKey = (e: KeyboardEvent<HTMLButtonElement>, idx: number) => {
    if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
    e.preventDefault();
    const dir = e.key === "ArrowRight" ? 1 : -1;
    const next = (idx + dir + order.length) % order.length;
    setSeries(order[next]);
    tabRefs.current[next]?.focus();
  };

  const sparkPoints = (bars: number[]) => {
    const max = Math.max(...bars);
    return bars
      .map((b, i) => {
        const x = (i / (bars.length - 1)) * 100;
        const y = 38 - (b / max) * 32;
        return `${x.toFixed(1)},${y.toFixed(1)}`;
      })
      .join(" ");
  };

  const active = seriesData[series];

  const cardShell =
    "relative rounded-3xl border border-slate-200 bg-white/80 p-6 shadow-xl shadow-slate-900/5 backdrop-blur dark:border-slate-800 dark:bg-slate-900/70 dark:shadow-black/30";

  function AnalyticsVisual() {
    return (
      <div className="relative">
        <div className="featxalt-anim featxalt-float pointer-events-none absolute -inset-6 -z-10 rounded-[2rem] bg-gradient-to-tr from-indigo-400/25 via-violet-400/10 to-sky-400/25 blur-2xl dark:from-indigo-500/20 dark:to-sky-500/10" />
        <div className={cardShell}>
          <div role="tablist" aria-label="Choose a metric" className="mb-5 inline-flex rounded-full bg-slate-100 p-1 dark:bg-slate-800">
            {order.map((key, idx) => {
              const selected = series === key;
              return (
                <button
                  key={key}
                  ref={(el) => {
                    tabRefs.current[idx] = el;
                  }}
                  id={`${uid}-tab-${key}`}
                  type="button"
                  role="tab"
                  aria-selected={selected}
                  aria-controls={`${uid}-panel`}
                  tabIndex={selected ? 0 : -1}
                  onKeyDown={(e) => onTabKey(e, idx)}
                  onClick={() => setSeries(key)}
                  className={`rounded-full px-4 py-1.5 text-sm font-medium transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
                    selected
                      ? "bg-white text-slate-900 shadow-sm dark:bg-slate-950 dark:text-white"
                      : "text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200"
                  }`}
                >
                  {seriesData[key].label}
                </button>
              );
            })}
          </div>

          <div id={`${uid}-panel`} role="tabpanel" aria-labelledby={`${uid}-tab-${series}`} tabIndex={0} className="focus-visible:outline-none">
            <div className="flex items-end justify-between gap-4">
              <div>
                <div className="text-3xl font-semibold tracking-tight text-slate-900 dark:text-white">{active.value}</div>
                <div className="mt-1.5 flex items-center gap-2 text-sm">
                  <span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300">
                    <TrendIcon className="h-3.5 w-3.5" />
                    {active.delta}
                  </span>
                  <span className="text-slate-500 dark:text-slate-400">vs last week</span>
                </div>
              </div>
              <svg viewBox="0 0 100 40" className="h-12 w-28 flex-none overflow-visible" aria-hidden="true">
                <polyline
                  points={sparkPoints(active.bars)}
                  fill="none"
                  strokeWidth={2.2}
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  className="stroke-indigo-500 dark:stroke-indigo-400"
                />
              </svg>
            </div>

            <div className="mt-6 flex h-32 items-end gap-2" aria-hidden="true">
              {active.bars.map((b, i) => (
                <div
                  key={`${series}-${i}`}
                  style={{ height: `${b}%` }}
                  className="featxalt-motion flex-1 rounded-t-md bg-gradient-to-t from-indigo-500 to-violet-400 transition-[height] duration-700 ease-out"
                />
              ))}
            </div>
            <div className="mt-3 flex justify-between text-[11px] font-medium text-slate-400 dark:text-slate-500">
              {days.map((d) => (
                <span key={d}>{d}</span>
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }

  function AutomationVisual() {
    const nodes = [
      { x: 62, y: 54, label: "New lead" },
      { x: 258, y: 46, label: "Enrich" },
      { x: 268, y: 150, label: "Score" },
      { x: 96, y: 196, label: "Notify" },
    ];
    const edges: Array<[number, number]> = [
      [0, 1],
      [1, 2],
      [2, 3],
      [0, 3],
    ];

    return (
      <div className="relative">
        <div className="featxalt-anim featxalt-float pointer-events-none absolute -inset-6 -z-10 rounded-[2rem] bg-gradient-to-tr from-sky-400/25 via-indigo-400/10 to-violet-400/25 blur-2xl dark:from-sky-500/15 dark:to-violet-500/15" />
        <div className={`${cardShell} overflow-hidden`}>
          <div className="mb-4 flex items-center justify-between">
            <span className="text-sm font-medium text-slate-700 dark:text-slate-200">Lead routing</span>
            <span className="inline-flex items-center gap-1.5 rounded-full bg-sky-50 px-2.5 py-1 text-xs font-semibold text-sky-700 dark:bg-sky-500/10 dark:text-sky-300">
              <span className="featxalt-anim featxalt-pulse h-2 w-2 rounded-full bg-sky-500" />
              Running
            </span>
          </div>
          <svg viewBox="0 0 340 240" className="w-full" role="img" aria-label="Automated workflow: new lead to enrich to score to notify">
            {edges.map(([a, b], i) => (
              <path
                key={`edge-${i}`}
                d={`M ${nodes[a].x} ${nodes[a].y} L ${nodes[b].x} ${nodes[b].y}`}
                fill="none"
                strokeWidth={2}
                strokeLinecap="round"
                strokeDasharray="5 7"
                className="featxalt-anim featxalt-dash stroke-indigo-400/70 dark:stroke-indigo-400/60"
              />
            ))}
            {nodes.map((n, i) => (
              <g key={`node-${i}`}>
                <rect
                  x={n.x - 46}
                  y={n.y - 19}
                  width={92}
                  height={38}
                  rx={12}
                  strokeWidth={1.5}
                  className="fill-white stroke-slate-200 dark:fill-slate-800 dark:stroke-slate-700"
                />
                <text x={n.x} y={n.y} textAnchor="middle" dominantBaseline="central" className="fill-slate-700 text-[13px] font-medium dark:fill-slate-100">
                  {n.label}
                </text>
              </g>
            ))}
          </svg>
        </div>
      </div>
    );
  }

  function CollabVisual() {
    const people = [
      { initials: "AR", cls: "from-indigo-500 to-violet-500" },
      { initials: "MK", cls: "from-emerald-500 to-teal-500" },
      { initials: "SL", cls: "from-rose-500 to-orange-500" },
      { initials: "JD", cls: "from-sky-500 to-indigo-500" },
      { initials: "PN", cls: "from-amber-500 to-rose-500" },
    ];

    return (
      <div className="relative">
        <div className="featxalt-anim featxalt-float pointer-events-none absolute -inset-6 -z-10 rounded-[2rem] bg-gradient-to-tr from-emerald-400/25 via-sky-400/10 to-indigo-400/25 blur-2xl dark:from-emerald-500/15 dark:to-indigo-500/15" />
        <div className={cardShell}>
          <div className="flex items-center justify-between">
            <div className="flex items-center">
              {people.map((p, i) => (
                <span
                  key={p.initials}
                  className={`inline-flex h-11 w-11 items-center justify-center rounded-full bg-gradient-to-br text-xs font-semibold text-white ring-2 ring-white dark:ring-slate-900 ${p.cls} ${i > 0 ? "-ml-3" : ""}`}
                >
                  {p.initials}
                </span>
              ))}
              <span className="-ml-3 inline-flex h-11 w-11 items-center justify-center rounded-full bg-slate-100 text-xs font-semibold text-slate-600 ring-2 ring-white dark:bg-slate-800 dark:text-slate-300 dark:ring-slate-900">
                +7
              </span>
            </div>
            <span className="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300">
              <span className="featxalt-anim featxalt-pulse h-2 w-2 rounded-full bg-emerald-500" />
              12 online
            </span>
          </div>

          <div className="mt-6 rounded-2xl border border-slate-200 bg-slate-50/80 p-4 dark:border-slate-800 dark:bg-slate-800/40">
            <div className="flex items-center gap-2">
              <span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-br from-indigo-500 to-violet-500 text-[10px] font-semibold text-white">
                NA
              </span>
              <span className="text-sm font-medium text-slate-800 dark:text-slate-100">Nadia A.</span>
              <span className="text-xs text-slate-400 dark:text-slate-500">just now</span>
            </div>
            <p className="mt-2 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
              Can we get the Q3 revenue view shared with the board before Friday? Left a couple of notes on the cohort tab.
            </p>
          </div>

          <div className="featxalt-anim featxalt-drift pointer-events-none absolute right-7 top-24 flex items-center gap-1">
            <svg viewBox="0 0 24 24" className="h-5 w-5 drop-shadow-md" aria-hidden="true">
              <path d="M5 3l14 8-6 1.6L9.6 19 5 3z" className="fill-indigo-500" />
            </svg>
            <span className="rounded-md bg-indigo-500 px-1.5 py-0.5 text-[10px] font-semibold text-white shadow">Marco</span>
          </div>
        </div>
      </div>
    );
  }

  function renderVisual(f: Feature) {
    if (f.visual === "analytics") return <AnalyticsVisual />;
    if (f.visual === "automation") return <AutomationVisual />;
    return <CollabVisual />;
  }

  return (
    <section
      className="featxalt-root relative isolate w-full overflow-hidden bg-white py-20 text-slate-900 sm:py-28 lg:py-32 dark:bg-slate-950 dark:text-slate-100"
      data-play={playing ? "true" : "false"}
      aria-labelledby={`${uid}-heading`}
    >
      <style>{`
        @keyframes featxalt-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
        @keyframes featxalt-pulse { 0%,100% { opacity: .45; transform: scale(1); } 50% { opacity: 1; transform: scale(1.25); } }
        @keyframes featxalt-dash { to { stroke-dashoffset: -32; } }
        @keyframes featxalt-drift { 0%,100% { transform: translate(0,0); } 50% { transform: translate(7px,-7px); } }
        .featxalt-float { animation: featxalt-float 6s ease-in-out infinite; }
        .featxalt-pulse { animation: featxalt-pulse 2.6s ease-in-out infinite; }
        .featxalt-dash { animation: featxalt-dash 1.1s linear infinite; }
        .featxalt-drift { animation: featxalt-drift 5.5s ease-in-out infinite; }
        .featxalt-root[data-play="false"] .featxalt-anim { animation-play-state: paused !important; }
        @media (prefers-reduced-motion: reduce) {
          .featxalt-anim { animation: none !important; }
          .featxalt-motion { transition: none !important; }
        }
      `}</style>

      <div
        className="pointer-events-none absolute inset-0 -z-10 opacity-[0.5] dark:opacity-[0.28]"
        style={{
          backgroundImage: "radial-gradient(circle at 1px 1px, rgb(148 163 184 / 0.28) 1px, transparent 0)",
          backgroundSize: "28px 28px",
          maskImage: "radial-gradient(ellipse at 50% 0%, black, transparent 72%)",
          WebkitMaskImage: "radial-gradient(ellipse at 50% 0%, black, transparent 72%)",
        }}
        aria-hidden="true"
      />
      <div
        className="pointer-events-none absolute -top-40 left-1/2 -z-10 h-80 w-[42rem] max-w-full -translate-x-1/2 rounded-full bg-gradient-to-b from-indigo-400/25 to-transparent blur-3xl dark:from-indigo-500/20"
        aria-hidden="true"
      />

      <div className="mx-auto w-full max-w-6xl px-6 lg:px-8">
        <div className="mx-auto max-w-2xl text-center">
          <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/60 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-slate-600 dark:border-slate-700 dark:bg-slate-900/60 dark:text-slate-300">
            <span className="h-1.5 w-1.5 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500" />
            Why teams choose Cadence
          </span>
          <h2 id={`${uid}-heading`} className="mt-5 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
            Everything your team needs, in one connected workspace
          </h2>
          <p className="mt-4 text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-300">
            Stop stitching together five different tools. Cadence brings analytics, automation, and collaboration into a single source of
            truth your whole team can actually trust.
          </p>

          {!reduce && (
            <div className="mt-7 flex justify-center">
              <button
                type="button"
                onClick={() => setPlaying((p) => !p)}
                aria-pressed={playing}
                className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-200 dark:hover:bg-slate-900"
              >
                {playing ? <PauseIcon className="h-4 w-4" /> : <PlayIcon className="h-4 w-4" />}
                {playing ? "Pause motion" : "Play motion"}
              </button>
            </div>
          )}
        </div>

        <div className="mt-16 space-y-24 lg:mt-24 lg:space-y-32">
          {features.map((f, i) => {
            const flip = i % 2 === 1;
            return (
              <motion.div
                key={f.id}
                className="grid items-center gap-10 lg:grid-cols-2 lg:gap-16"
                variants={container}
                initial={reduce ? false : "hidden"}
                whileInView="show"
                viewport={{ once: true, amount: 0.3 }}
              >
                <motion.div variants={item} className={flip ? "lg:order-2" : "lg:order-1"}>
                  <div className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/60 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-slate-600 dark:border-slate-700 dark:bg-slate-900/60 dark:text-slate-300">
                    <span className={`h-1.5 w-1.5 rounded-full bg-gradient-to-r ${f.dot}`} />
                    {f.eyebrow}
                  </div>
                  <h3 className="mt-5 text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl dark:text-white">{f.title}</h3>
                  <p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-300">{f.body}</p>
                  <ul className="mt-6 space-y-3">
                    {f.points.map((p) => (
                      <li key={p} className="flex items-start gap-3">
                        <span className="mt-0.5 inline-flex h-5 w-5 flex-none items-center justify-center rounded-full bg-emerald-100 text-emerald-600 dark:bg-emerald-500/15 dark:text-emerald-400">
                          <CheckIcon className="h-3 w-3" />
                        </span>
                        <span className="text-sm text-slate-600 dark:text-slate-300">{p}</span>
                      </li>
                    ))}
                  </ul>
                  <a
                    href="#"
                    className="group mt-8 inline-flex items-center gap-2 text-sm font-semibold text-indigo-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-indigo-400 dark:focus-visible:ring-offset-slate-950"
                  >
                    {f.cta}
                    <ArrowIcon className="featxalt-motion h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
                  </a>
                </motion.div>

                <motion.div variants={item} className={flip ? "lg:order-1" : "lg:order-2"}>
                  {renderVisual(f)}
                </motion.div>
              </motion.div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →
Three Column Icon Grid

Three Column Icon Grid

Original

A responsive icon grid that presents six product capabilities as scannable cards, each with an inline SVG icon, title and short description.

Alternating Media Rows

Alternating Media Rows

Original

Three feature rows that alternate a text column with a CSS-built visual panel, walking the reader through a plan, measure and ship story.

Bento Feature Grid

Bento Feature Grid

Original

A bento-style grid mixing one large hero cell, a tall stat cell and several compact cards to give features a clear visual hierarchy.

Tabbed Feature Switcher

Tabbed Feature Switcher

Original

A tabbed feature preview that switches panels using only native radio inputs and Tailwind peer variants, so it works with no JavaScript.

Stat Backed Features

Stat Backed Features

Original

A feature layout that pairs each capability with a headline metric, so every claim is anchored to a measurable result.

Scroll-reveal bento grid

Scroll-reveal bento grid

Original

An asymmetric bento feature grid whose cards stagger into view on scroll, lift on hover and feature an animated conic gradient border, shimmering headline, growing bar chart and a masked marquee tag strip.

Sticky scroll steps

Sticky scroll steps

Original

A scroll-linked how-it-works section where a sticky panel cross-fades between steps and a progress ring plus filling timeline track advance as you scroll through the process.

Spotlight hover feature cards

Spotlight hover feature cards

Original

A responsive feature card grid where each card follows the cursor with a radial spotlight glow, lifts and rotates its icon on hover, reveals a call to action, and sits above a scrolling logo marquee.

Animated marquee highlights band

Animated marquee highlights band

Original

A bold feature band with an animated gradient background, floating blurred orbs, blur-in staggered headline, two opposing marquee pill rows and shimmering stat cards.

Bordered Feature Grid (2x2)

Bordered Feature Grid (2x2)

MIT

A two-column grid of bordered feature cards, each pairing an outlined icon with a title and supporting copy. Clean, enterprise-leaning layout with full dark-mode support.

Outlined Feature Cards with CTA

Outlined Feature Cards with CTA

MIT

A three-column feature section with heading and intro, plus blue-outlined cards that each carry an icon, description, and a circular arrow call-to-action. Fully theme-aware.

Icon-Left Feature List

Icon-Left Feature List

MIT

A centered heading over a three-column feature list, each row leading with a rounded icon badge and a 'Learn More' link. Classic marketing feature block, ported with added dark variants.