Web InnoventixFreeCode

Grid Icons Feature Section

Original · free

feature grid with icons

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

import {
  useMemo,
  useRef,
  useState,
  type KeyboardEvent,
  type ReactElement,
  type SVGProps,
} from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

type Category = "build" | "ship" | "scale";
type Filter = "all" | Category;
type Accent = "indigo" | "violet" | "emerald" | "sky" | "amber" | "rose";

interface Feature {
  id: string;
  title: string;
  description: string;
  category: Category;
  accent: Accent;
  Icon: (props: SVGProps<SVGSVGElement>) => ReactElement;
}

const accentTile: Record<Accent, string> = {
  indigo:
    "bg-indigo-500/10 text-indigo-600 ring-1 ring-inset ring-indigo-500/20 dark:bg-indigo-400/10 dark:text-indigo-300 dark:ring-indigo-400/20",
  violet:
    "bg-violet-500/10 text-violet-600 ring-1 ring-inset ring-violet-500/20 dark:bg-violet-400/10 dark:text-violet-300 dark:ring-violet-400/20",
  emerald:
    "bg-emerald-500/10 text-emerald-600 ring-1 ring-inset ring-emerald-500/20 dark:bg-emerald-400/10 dark:text-emerald-300 dark:ring-emerald-400/20",
  sky: "bg-sky-500/10 text-sky-600 ring-1 ring-inset ring-sky-500/20 dark:bg-sky-400/10 dark:text-sky-300 dark:ring-sky-400/20",
  amber:
    "bg-amber-500/10 text-amber-600 ring-1 ring-inset ring-amber-500/20 dark:bg-amber-400/10 dark:text-amber-300 dark:ring-amber-400/20",
  rose: "bg-rose-500/10 text-rose-600 ring-1 ring-inset ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-300 dark:ring-rose-400/20",
};

const accentBar: Record<Accent, string> = {
  indigo: "from-indigo-500 to-violet-500",
  violet: "from-violet-500 to-fuchsia-500",
  emerald: "from-emerald-500 to-teal-500",
  sky: "from-sky-500 to-cyan-500",
  amber: "from-amber-500 to-orange-500",
  rose: "from-rose-500 to-pink-500",
};

function PreviewIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <rect x="3" y="4" width="18" height="14" rx="2" />
      <path d="M3 8h18" />
      <circle cx="5.75" cy="6" r="0.6" fill="currentColor" stroke="none" />
      <circle cx="8" cy="6" r="0.6" fill="currentColor" stroke="none" />
      <path d="M9 20h6" />
      <path d="M12 18v2" />
    </svg>
  );
}

function GitBranchIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <circle cx="6" cy="5" r="2.5" />
      <circle cx="6" cy="19" r="2.5" />
      <circle cx="18" cy="8" r="2.5" />
      <path d="M6 7.5v9" />
      <path d="M18 10.5c0 4-4 4.5-8 5" />
    </svg>
  );
}

function PlugIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M12 21v-3" />
      <path d="M7 8V4" />
      <path d="M17 8V4" />
      <rect x="5" y="8" width="14" height="6" rx="3" />
      <path d="M8 14a4 4 0 0 0 8 0" />
    </svg>
  );
}

function BoltIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z" />
    </svg>
  );
}

function RollbackIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M4 4v5h5" />
      <path d="M4.5 13a8 8 0 1 0 2-6.7L4 9" />
    </svg>
  );
}

function GlobeIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <circle cx="12" cy="12" r="9" />
      <path d="M3 12h18" />
      <path d="M12 3a14 14 0 0 1 0 18" />
      <path d="M12 3a14 14 0 0 0 0 18" />
    </svg>
  );
}

function ScaleIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M4 18V9" />
      <path d="M10 18v-6" />
      <path d="M16 18v-3" />
      <path d="M20 6l-4 4-3-2.5L7 11" />
      <path d="M20 6h-3" />
      <path d="M20 6v3" />
    </svg>
  );
}

function ChartIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M4 4v16h16" />
      <rect x="7.5" y="12" width="2.5" height="5" rx="0.6" />
      <rect x="12" y="9" width="2.5" height="8" rx="0.6" />
      <rect x="16.5" y="6" width="2.5" height="11" rx="0.6" />
    </svg>
  );
}

function PulseIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.6}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M3 12h4l2-6 4 12 2-6h6" />
    </svg>
  );
}

function ArrowIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M5 12h14" />
      <path d="M13 6l6 6-6 6" />
    </svg>
  );
}

const FEATURES: Feature[] = [
  {
    id: "preview",
    category: "build",
    accent: "indigo",
    Icon: PreviewIcon,
    title: "Preview deployments",
    description:
      "Every pull request spins up an isolated preview URL with its own database branch, so reviewers test the real thing before it merges.",
  },
  {
    id: "git",
    category: "build",
    accent: "violet",
    Icon: GitBranchIcon,
    title: "Git-native workflow",
    description:
      "Push to a branch and it builds. No YAML to babysit, no runners to provision — your commit history is the deployment log.",
  },
  {
    id: "integrations",
    category: "build",
    accent: "sky",
    Icon: PlugIcon,
    title: "Typed integrations",
    description:
      "Wire in Postgres, Redis, or any REST service through connectors that validate credentials at build time, not at 2 a.m.",
  },
  {
    id: "deploys",
    category: "ship",
    accent: "amber",
    Icon: BoltIcon,
    title: "Instant global deploys",
    description:
      "Ship to 34 edge regions in under 40 seconds. Atomic swaps mean no half-deployed states and no cold visitors.",
  },
  {
    id: "rollback",
    category: "ship",
    accent: "rose",
    Icon: RollbackIcon,
    title: "One-click rollback",
    description:
      "Every deploy is immutable and addressable. If a release misbehaves, roll back to any previous build in a single click.",
  },
  {
    id: "edge",
    category: "ship",
    accent: "emerald",
    Icon: GlobeIcon,
    title: "Edge caching",
    description:
      "Static assets and cached responses serve from the region closest to each visitor, cutting time-to-first-byte to double digits.",
  },
  {
    id: "autoscale",
    category: "scale",
    accent: "violet",
    Icon: ScaleIcon,
    title: "Autoscaling compute",
    description:
      "Traffic spikes get absorbed automatically and capacity scales to zero when idle, so you pay for requests, not reserved boxes.",
  },
  {
    id: "analytics",
    category: "scale",
    accent: "indigo",
    Icon: ChartIcon,
    title: "Real-time analytics",
    description:
      "Track Core Web Vitals, traffic, and error rates per route without adding a single client-side script or cookie banner.",
  },
  {
    id: "uptime",
    category: "scale",
    accent: "emerald",
    Icon: PulseIcon,
    title: "Uptime monitoring",
    description:
      "Synthetic checks run every 30 seconds from six continents and page the right person before your users notice.",
  },
];

const TABS: { id: Filter; label: string }[] = [
  { id: "all", label: "All" },
  { id: "build", label: "Build" },
  { id: "ship", label: "Ship" },
  { id: "scale", label: "Scale" },
];

export default function FeatxGridIcons() {
  const reduce = useReducedMotion();
  const [active, setActive] = useState<Filter>("all");
  const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);

  const visible = useMemo(
    () =>
      active === "all"
        ? FEATURES
        : FEATURES.filter((f) => f.category === active),
    [active],
  );

  function handleTabKey(event: KeyboardEvent<HTMLButtonElement>, index: number) {
    const count = TABS.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(TABS[next].id);
    tabRefs.current[next]?.focus();
  }

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-24 text-slate-900 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes featx-drift {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(0, -22px, 0) scale(1.06); }
        }
        @keyframes featx-rise {
          from { opacity: 0; transform: translateY(14px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .featx-anim-drift { animation: featx-drift 16s ease-in-out infinite; }
        .featx-anim-rise { animation: featx-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; }
        @media (prefers-reduced-motion: reduce) {
          .featx-anim-drift, .featx-anim-rise { animation: none !important; }
        }
      `}</style>

      {/* decorative background */}
      <div aria-hidden="true" className="pointer-events-none absolute inset-0">
        <div className="featx-anim-drift absolute -left-24 top-10 h-72 w-72 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/15" />
        <div
          className="featx-anim-drift absolute -right-20 bottom-0 h-80 w-80 rounded-full bg-violet-400/20 blur-3xl dark:bg-violet-500/15"
          style={{ animationDelay: "-6s" }}
        />
        <div
          className="absolute inset-0 opacity-[0.04] dark:opacity-[0.06]"
          style={{
            backgroundImage:
              "linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)",
            backgroundSize: "44px 44px",
          }}
        />
      </div>

      <div className="relative mx-auto max-w-6xl">
        <div className="featx-anim-rise mx-auto max-w-2xl text-center">
          <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-3 py-1 text-xs font-medium tracking-wide text-slate-600 backdrop-blur dark:border-slate-800 dark:bg-slate-900/60 dark:text-slate-400">
            <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
            Platform capabilities
          </span>
          <h2 className="mt-6 text-balance text-3xl font-semibold tracking-tight sm:text-4xl md:text-5xl">
            Everything between{" "}
            <span className="bg-gradient-to-r from-indigo-500 to-violet-500 bg-clip-text text-transparent">
              git push
            </span>{" "}
            and global traffic
          </h2>
          <p className="mt-4 text-pretty text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-400">
            Beacon runs the infrastructure so your team ships features, not
            pipelines. Nine primitives, one platform, zero servers to babysit.
          </p>
        </div>

        {/* filter tabs */}
        <div className="mt-10 flex justify-center">
          <div
            role="tablist"
            aria-label="Filter features by lifecycle stage"
            className="inline-flex flex-wrap items-center justify-center gap-1 rounded-full border border-slate-200 bg-white/70 p-1 backdrop-blur dark:border-slate-800 dark:bg-slate-900/60"
          >
            {TABS.map((tab, index) => {
              const selected = active === tab.id;
              return (
                <button
                  key={tab.id}
                  ref={(node) => {
                    tabRefs.current[index] = node;
                  }}
                  role="tab"
                  id={`featx-tab-${tab.id}`}
                  aria-selected={selected}
                  aria-controls="featx-panel"
                  tabIndex={selected ? 0 : -1}
                  onClick={() => setActive(tab.id)}
                  onKeyDown={(event) => handleTabKey(event, index)}
                  className={`relative rounded-full px-4 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950 ${
                    selected
                      ? "text-white"
                      : "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
                  }`}
                >
                  {selected && (
                    <motion.span
                      layoutId={reduce ? undefined : "featx-tab-pill"}
                      className="absolute inset-0 -z-10 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500"
                      transition={{ type: "spring", stiffness: 380, damping: 32 }}
                    />
                  )}
                  {tab.label}
                </button>
              );
            })}
          </div>
        </div>

        {/* grid */}
        <motion.ul
          role="tabpanel"
          id="featx-panel"
          aria-labelledby={`featx-tab-${active}`}
          layout={!reduce}
          className="mt-12 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"
        >
          <AnimatePresence mode="popLayout" initial={false}>
            {visible.map((feature) => {
              const Icon = feature.Icon;
              return (
                <motion.li
                  key={feature.id}
                  layout={!reduce}
                  initial={reduce ? false : { opacity: 0, y: 14, scale: 0.98 }}
                  animate={reduce ? {} : { opacity: 1, y: 0, scale: 1 }}
                  exit={reduce ? {} : { opacity: 0, y: -10, scale: 0.98 }}
                  transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
                  className="group relative flex flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition-shadow duration-300 hover:shadow-lg hover:shadow-indigo-500/5 dark:border-slate-800 dark:bg-slate-900/60 dark:hover:shadow-black/20"
                >
                  <span
                    aria-hidden="true"
                    className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r opacity-0 transition-opacity duration-300 group-hover:opacity-100 ${accentBar[feature.accent]}`}
                  />
                  <span
                    className={`inline-flex h-12 w-12 items-center justify-center rounded-xl transition-transform duration-300 group-hover:-translate-y-0.5 ${accentTile[feature.accent]}`}
                  >
                    <Icon className="h-6 w-6" aria-hidden="true" />
                  </span>
                  <h3 className="mt-5 text-lg font-semibold tracking-tight text-slate-900 dark:text-slate-50">
                    {feature.title}
                  </h3>
                  <p className="mt-2 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
                    {feature.description}
                  </p>
                </motion.li>
              );
            })}
          </AnimatePresence>
        </motion.ul>

        {/* footer strip */}
        <div className="featx-anim-rise mt-14 flex flex-col items-center justify-between gap-6 rounded-2xl border border-slate-200 bg-white/70 px-6 py-6 backdrop-blur sm:flex-row dark:border-slate-800 dark:bg-slate-900/60">
          <dl className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-center sm:justify-start">
            {[
              { value: "34", label: "edge regions" },
              { value: "99.99%", label: "uptime SLA" },
              { value: "SOC 2", label: "Type II certified" },
            ].map((stat) => (
              <div key={stat.label} className="flex flex-col">
                <dt className="sr-only">{stat.label}</dt>
                <dd className="text-2xl font-semibold tracking-tight text-slate-900 dark:text-slate-50">
                  {stat.value}
                </dd>
                <dd className="text-xs text-slate-500 dark:text-slate-400">
                  {stat.label}
                </dd>
              </div>
            ))}
          </dl>
          <a
            href="#get-started"
            className="group inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500 px-5 py-2.5 text-sm font-semibold text-white shadow-sm outline-none transition-transform hover:scale-[1.02] focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
          >
            Start building free
            <ArrowIcon className="h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" />
          </a>
        </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.