Web InnoventixFreeCode

Tabs Feature Section

Original · free

tabbed feature showcase

byWeb InnoventixReact + Tailwind
featxtabsfeatures
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-tabs.json
featx-tabs.tsx
"use client";

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

type IconProps = { className?: string };

function ChartIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M4 20V10" />
      <path d="M10 20V4" />
      <path d="M16 20v-6" />
      <path d="M22 20H2" />
    </svg>
  );
}

function BoltIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z" />
    </svg>
  );
}

function PeopleIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <circle cx="9" cy="8" r="3.2" />
      <path d="M3.5 20a5.5 5.5 0 0 1 11 0" />
      <path d="M16 5.2a3 3 0 0 1 0 5.6" />
      <path d="M17 14.4a5.5 5.5 0 0 1 3.5 5.1" />
    </svg>
  );
}

function ShieldIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M12 3 4 6v6c0 4.6 3.2 7.9 8 9 4.8-1.1 8-4.4 8-9V6l-8-3Z" />
      <path d="m9 12 2 2 4-4" />
    </svg>
  );
}

function CheckIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={2.2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="m5 12 5 5 9-11" />
    </svg>
  );
}

function ArrowIcon({ className }: IconProps): ReactNode {
  return (
    <svg
      viewBox="0 0 24 24"
      className={className}
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M5 12h14" />
      <path d="m13 6 6 6-6 6" />
    </svg>
  );
}

type Feature = {
  key: string;
  label: string;
  icon: (props: IconProps) => ReactNode;
  eyebrow: string;
  heading: string;
  description: string;
  bullets: string[];
};

const FEATURES: Feature[] = [
  {
    key: "insights",
    label: "Insights",
    icon: ChartIcon,
    eyebrow: "Analytics",
    heading: "See what's working, instantly",
    description:
      "Beacon reads your events as they land and turns them into dashboards your whole team can trust — no spreadsheets, no waiting on the data team.",
    bullets: [
      "Live dashboards that refresh the second your data does",
      "Cohort and funnel breakdowns without writing a line of SQL",
      "Anomaly alerts the moment a key metric drifts off course",
    ],
  },
  {
    key: "automation",
    label: "Automation",
    icon: BoltIcon,
    eyebrow: "Workflows",
    heading: "Let the busywork run itself",
    description:
      "Chain triggers, conditions, and actions across every tool you already use. Build a workflow once and Beacon keeps it running around the clock.",
    bullets: [
      "Drag-and-drop builder for multi-step workflows",
      "Fire actions from any event across your stack",
      "Retries, branching, and error handling built right in",
    ],
  },
  {
    key: "collaboration",
    label: "Collaboration",
    icon: PeopleIcon,
    eyebrow: "Teamwork",
    heading: "Keep the whole team in sync",
    description:
      "Discussion happens on the data itself, not buried in a thread somewhere. Everyone sees the same numbers and the same context in real time.",
    bullets: [
      "Comment, assign, and resolve right on any chart",
      "Shared views so everyone reads from one source",
      "Live presence and full change history on every board",
    ],
  },
  {
    key: "governance",
    label: "Governance",
    icon: ShieldIcon,
    eyebrow: "Security",
    heading: "Enterprise-grade, without the friction",
    description:
      "Controls that satisfy your security team and stay invisible to everyone else. Access, auditing, and encryption are on by default.",
    bullets: [
      "SSO and SAML with role-based access control",
      "A complete audit log of every action and export",
      "Encryption in transit and at rest, always on",
    ],
  },
];

function InsightsVisual(): ReactNode {
  const bars = [42, 61, 38, 74, 55, 88, 96];
  return (
    <div className="flex h-full flex-col justify-between gap-6 rounded-2xl bg-white/70 p-6 ring-1 ring-slate-200 dark:bg-slate-900/60 dark:ring-white/10">
      <div className="flex items-start justify-between">
        <div>
          <p className="text-xs font-medium text-slate-500 dark:text-slate-400">
            Weekly active users
          </p>
          <p className="mt-1 text-2xl font-semibold tracking-tight text-slate-900 dark:text-white">
            12,480
          </p>
        </div>
        <span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300">
          <ArrowIcon className="h-3 w-3 -rotate-90" />
          18%
        </span>
      </div>
      <div className="flex h-40 items-end gap-2.5">
        {bars.map((h, i) => (
          <div
            key={i}
            className="featx-anim flex-1 origin-bottom rounded-t-md bg-gradient-to-t from-indigo-500 to-violet-400 dark:from-indigo-500 dark:to-violet-400"
            style={
              {
                height: `${h}%`,
                animation: "featx-grow 0.7s cubic-bezier(0.22,1,0.36,1) both",
                animationDelay: `${i * 70}ms`,
              } as CSSProperties
            }
          />
        ))}
      </div>
      <div className="flex items-center justify-between text-[11px] font-medium text-slate-400 dark:text-slate-500">
        <span>Mon</span>
        <span>Tue</span>
        <span>Wed</span>
        <span>Thu</span>
        <span>Fri</span>
        <span>Sat</span>
        <span>Sun</span>
      </div>
    </div>
  );
}

function AutomationVisual(): ReactNode {
  const steps = [
    { t: "New signup received", s: "Trigger", tone: "sky" },
    { t: "Enrich profile & score", s: "Action", tone: "indigo" },
    { t: "Route to the right rep", s: "Action", tone: "violet" },
  ];
  return (
    <div className="relative flex h-full flex-col justify-center gap-3 rounded-2xl bg-white/70 p-6 ring-1 ring-slate-200 dark:bg-slate-900/60 dark:ring-white/10">
      <div className="pointer-events-none absolute bottom-10 left-[38px] top-14 w-px bg-gradient-to-b from-sky-400 via-indigo-400 to-violet-400" />
      <div
        className="featx-anim pointer-events-none absolute left-[35px] top-14 h-2.5 w-2.5 rounded-full bg-white shadow-[0_0_0_3px_rgba(99,102,241,0.4)]"
        style={{ animation: "featx-flow 2.6s ease-in-out infinite" }}
      />
      {steps.map((step, i) => (
        <div
          key={i}
          className="relative flex items-center gap-3.5 rounded-xl bg-slate-50 p-3 ring-1 ring-slate-200 dark:bg-slate-800/60 dark:ring-white/10"
        >
          <span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-gradient-to-br from-indigo-500 to-violet-500 text-xs font-bold text-white">
            {i + 1}
          </span>
          <div className="min-w-0">
            <p className="truncate text-sm font-semibold text-slate-900 dark:text-white">
              {step.t}
            </p>
            <p className="text-xs text-slate-500 dark:text-slate-400">{step.s}</p>
          </div>
          <span className="ml-auto h-2 w-2 shrink-0 rounded-full bg-emerald-400" />
        </div>
      ))}
    </div>
  );
}

function CollaborationVisual(): ReactNode {
  const avatars = [
    "from-rose-400 to-amber-400",
    "from-sky-400 to-indigo-400",
    "from-emerald-400 to-sky-400",
    "from-violet-400 to-rose-400",
  ];
  return (
    <div className="flex h-full flex-col justify-center gap-5 rounded-2xl bg-white/70 p-6 ring-1 ring-slate-200 dark:bg-slate-900/60 dark:ring-white/10">
      <div className="flex items-center justify-between">
        <div className="flex -space-x-2">
          {avatars.map((g, i) => (
            <span
              key={i}
              className={`h-8 w-8 rounded-full bg-gradient-to-br ${g} ring-2 ring-white dark:ring-slate-900`}
            />
          ))}
        </div>
        <span className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-500 dark:text-slate-400">
          <span
            className="featx-anim h-2 w-2 rounded-full bg-emerald-400"
            style={{ animation: "featx-pulse 2s ease-in-out infinite" }}
          />
          4 online
        </span>
      </div>

      <div className="flex gap-3">
        <span className="h-7 w-7 shrink-0 rounded-full bg-gradient-to-br from-sky-400 to-indigo-400" />
        <div className="rounded-2xl rounded-tl-sm bg-slate-100 px-3.5 py-2.5 text-sm text-slate-700 dark:bg-slate-800/70 dark:text-slate-200">
          Signups jumped after the new onboarding shipped — nice work.
        </div>
      </div>
      <div className="flex flex-row-reverse gap-3">
        <span className="h-7 w-7 shrink-0 rounded-full bg-gradient-to-br from-rose-400 to-amber-400" />
        <div className="rounded-2xl rounded-tr-sm bg-indigo-500 px-3.5 py-2.5 text-sm text-white">
          Assigning the retention board to you 👀
        </div>
      </div>
    </div>
  );
}

function GovernanceVisual(): ReactNode {
  const items = ["SSO / SAML", "Role-based access", "Audit log", "Encrypted at rest"];
  return (
    <div className="flex h-full flex-col items-center justify-center gap-6 rounded-2xl bg-white/70 p-6 ring-1 ring-slate-200 dark:bg-slate-900/60 dark:ring-white/10">
      <div className="relative">
        <span
          className="featx-anim absolute inset-0 rounded-full bg-emerald-400/25 blur-xl"
          style={{ animation: "featx-pulse 2.4s ease-in-out infinite" }}
        />
        <span className="relative grid h-16 w-16 place-items-center rounded-2xl bg-gradient-to-br from-emerald-500 to-sky-500 text-white shadow-lg shadow-emerald-500/30">
          <ShieldIcon className="h-8 w-8" />
        </span>
      </div>
      <ul className="grid w-full grid-cols-2 gap-2.5">
        {items.map((item) => (
          <li
            key={item}
            className="flex items-center gap-2 rounded-lg bg-slate-50 px-3 py-2.5 text-xs font-medium text-slate-700 ring-1 ring-slate-200 dark:bg-slate-800/60 dark:text-slate-200 dark:ring-white/10"
          >
            <CheckIcon className="h-4 w-4 shrink-0 text-emerald-500" />
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

function FeatureVisual({ index }: { index: number }): ReactNode {
  if (index === 0) return <InsightsVisual />;
  if (index === 1) return <AutomationVisual />;
  if (index === 2) return <CollaborationVisual />;
  return <GovernanceVisual />;
}

export default function FeatxTabs(): ReactNode {
  const [active, setActive] = useState(0);
  const reduce = useReducedMotion();
  const uid = useId();
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);

  const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
    const count = FEATURES.length;
    let next = index;
    if (event.key === "ArrowRight" || event.key === "ArrowDown") {
      next = (index + 1) % count;
    } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
      next = (index - 1 + count) % count;
    } else if (event.key === "Home") {
      next = 0;
    } else if (event.key === "End") {
      next = count - 1;
    } else {
      return;
    }
    event.preventDefault();
    setActive(next);
    tabRefs.current[next]?.focus();
  };

  const current = FEATURES[active];

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 py-24 sm:py-32 dark:bg-slate-950">
      <style>{`
        @keyframes featx-grow {
          from { transform: scaleY(0); opacity: 0; }
          to { transform: scaleY(1); opacity: 1; }
        }
        @keyframes featx-flow {
          0% { transform: translateY(0); opacity: 0; }
          12% { opacity: 1; }
          88% { opacity: 1; }
          100% { transform: translateY(150px); opacity: 0; }
        }
        @keyframes featx-pulse {
          0%, 100% { transform: scale(1); opacity: 0.9; }
          50% { transform: scale(1.35); opacity: 0.5; }
        }
        @media (prefers-reduced-motion: reduce) {
          .featx-anim { animation: none !important; transform: none !important; opacity: 1 !important; }
        }
      `}</style>

      {/* decorative background */}
      <div
        aria-hidden="true"
        className="pointer-events-none absolute -top-40 left-1/2 h-96 w-[46rem] -translate-x-1/2 rounded-full bg-gradient-to-br from-indigo-300/40 via-violet-300/30 to-transparent blur-3xl dark:from-indigo-600/20 dark:via-violet-600/15"
      />

      <div className="relative mx-auto max-w-6xl px-6">
        <div className="mx-auto max-w-2xl text-center">
          <span className="inline-flex items-center gap-2 rounded-full bg-white/70 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-indigo-600 ring-1 ring-slate-200 dark:bg-white/5 dark:text-indigo-300 dark:ring-white/10">
            <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
            Platform overview
          </span>
          <h2 className="mt-5 text-balance text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl md:text-5xl dark:text-white">
            Everything your team needs, in one workspace
          </h2>
          <p className="mt-4 text-pretty text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-300">
            Beacon brings analytics, automation, and collaboration under one roof —
            so you spend less time stitching tools together and more time shipping.
          </p>
        </div>

        {/* Tablist */}
        <div
          role="tablist"
          aria-label="Product features"
          aria-orientation="horizontal"
          className="mx-auto mt-12 flex max-w-3xl flex-wrap justify-center gap-2"
        >
          {FEATURES.map((feature, index) => {
            const selected = index === active;
            const Icon = feature.icon;
            return (
              <button
                key={feature.key}
                ref={(node) => {
                  tabRefs.current[index] = node;
                }}
                id={`${uid}-tab-${index}`}
                role="tab"
                type="button"
                aria-selected={selected}
                aria-controls={`${uid}-panel-${index}`}
                tabIndex={selected ? 0 : -1}
                onClick={() => setActive(index)}
                onKeyDown={(event) => onKeyDown(event, index)}
                className={`group inline-flex items-center gap-2 rounded-full px-4 py-2.5 text-sm font-semibold outline-none transition focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-950 ${
                  selected
                    ? "bg-gradient-to-r from-indigo-600 to-violet-600 text-white shadow-lg shadow-indigo-500/25"
                    : "bg-white/70 text-slate-600 ring-1 ring-slate-200 hover:bg-white hover:text-slate-900 dark:bg-white/5 dark:text-slate-300 dark:ring-white/10 dark:hover:bg-white/10 dark:hover:text-white"
                }`}
              >
                <Icon className="h-4 w-4" />
                {feature.label}
              </button>
            );
          })}
        </div>

        {/* Panel */}
        <div className="relative mt-10 min-h-[26rem] rounded-3xl bg-white/60 p-6 ring-1 ring-slate-200 backdrop-blur-sm sm:p-8 dark:bg-white/[0.03] dark:ring-white/10">
          <AnimatePresence mode="wait">
            <motion.div
              key={current.key}
              role="tabpanel"
              id={`${uid}-panel-${active}`}
              aria-labelledby={`${uid}-tab-${active}`}
              tabIndex={0}
              initial={reduce ? false : { opacity: 0, y: 14 }}
              animate={{ opacity: 1, y: 0 }}
              exit={reduce ? { opacity: 0 } : { opacity: 0, y: -14 }}
              transition={{ duration: 0.32, ease: [0.22, 1, 0.36, 1] }}
              className="grid grid-cols-1 items-center gap-8 outline-none lg:grid-cols-2 lg:gap-12"
            >
              <div>
                <p className="text-xs font-semibold uppercase tracking-wider text-indigo-600 dark:text-indigo-300">
                  {current.eyebrow}
                </p>
                <h3 className="mt-2 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
                  {current.heading}
                </h3>
                <p className="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-300">
                  {current.description}
                </p>
                <ul className="mt-6 space-y-3">
                  {current.bullets.map((bullet) => (
                    <li key={bullet} className="flex items-start gap-3">
                      <span className="mt-0.5 grid h-5 w-5 shrink-0 place-items-center rounded-full bg-emerald-100 text-emerald-600 dark:bg-emerald-500/15 dark:text-emerald-300">
                        <CheckIcon className="h-3.5 w-3.5" />
                      </span>
                      <span className="text-sm leading-relaxed text-slate-700 dark:text-slate-200">
                        {bullet}
                      </span>
                    </li>
                  ))}
                </ul>
                <a
                  href="#"
                  className="mt-7 inline-flex items-center gap-1.5 text-sm font-semibold text-indigo-600 outline-none transition hover:gap-2.5 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white rounded dark:text-indigo-300 dark:focus-visible:ring-offset-slate-950"
                >
                  Explore {current.label.toLowerCase()}
                  <ArrowIcon className="h-4 w-4" />
                </a>
              </div>

              <div className="h-72 sm:h-80">
                <FeatureVisual index={active} />
              </div>
            </motion.div>
          </AnimatePresence>
        </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.