Integration Hub Hero
A grid of integration icons surrounds a central product icon while beams flow along the connectors towards the centre in turn; hovering a service lights up its own connector.
A landing-page hero: a centred eyebrow, headline, supporting copy and two CTAs on top, and below them a grid of integration icons with your product icon in the centre cell (larger, with an accent outline and soft glow). Every service tile is linked to the centre by a smooth curve, beams flow along the connectors towards the centre in turn (or outwards), and the centre icon breathes gently to the beam rhythm. Hovering a service (or focusing it with the keyboard) turns its connector into a solid accent line and speeds up its beam while the other beams fade back. The grid size is picked from the number of services (3×3, 5×3, 7×3, 7×5…), so the centre cell is always in the middle.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add integration-hub-hero.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<IntegrationHubHero/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/integration-hub-hero.en.jsonOr, once registries are configured in components.json, install it as @webberui/integration-hub-hero.
Usage
import { Cloud, CreditCard, Database, Mail } from "lucide-react";
import { IntegrationHubHero } from "@/components/ui/integration-hub-hero";
<IntegrationHubHero
eyebrow="接點雲 · 40+ 種整合"
title="把每一套工具,接到同一個地方"
subtitle="資料、收款、通知與排程自動串在一起。"
primaryAction={{ label: "免費開始", href: "/signup" }}
secondaryAction={{ label: "瀏覽整合清單", href: "/integrations" }}
centerLabel="接點雲"
integrations={[
{ label: "雲庫資料", icon: Database },
{ label: "信鴿郵件", icon: Mail },
{ label: "收款站", icon: CreditCard },
{ label: "雲端倉", icon: Cloud },
// up to 8 fit a 3×3 grid, up to 14 fit 5×3
]}
/>The sample copy reads: eyebrow "Junction Cloud · 40+ integrations", headline "Plug every tool into one place", buttons "Start free" and "Browse integrations"; the services are "Vault DB", "Pigeon Mail", "PayPost" and "Cloudshed".
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Headline (required) |
eyebrow | ReactNode | — | Small label above the headline |
subtitle | ReactNode | — | Supporting copy below the headline |
primaryAction | IntegrationHubHeroAction | — | Primary button, filled with the accent colour |
secondaryAction | IntegrationHubHeroAction | — | Secondary button, outlined |
as | "h1" | "h2" | "h1" | Heading level |
minHeight | number | 560 | Minimum section height (px) |
integrations | IntegrationHubHeroItem[] | 14 built-in fictional services | Integration services; the grid size follows the count |
centerIcon | ComponentType | Layers | Central product icon |
centerLabel | string | "你的產品" (Your product) | Central product name (accessible name) |
beamDirection | "in" | "out" | "in" | Beams flow from the services to the centre (in) or out from the centre (out) |
showLabels | boolean | false | Always show names under the icons; when off, names only appear as a tooltip on hover / focus |
activeIndex | number | null | — | Controlled: index of the lit-up service |
defaultActiveIndex | number | null | null | Uncontrolled: initially lit-up service index |
onActiveIndexChange | (index: number | null) => void | — | Called when the lit-up service changes |
onIntegrationClick | (item, index) => void | — | Click on a service tile; when set, tiles render as button |
className | string | — | Classes merged into the root section |
IntegrationHubHeroItem
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Service name, also the tooltip and accessible name |
icon | ComponentType | — | Service icon (a lucide icon or any component that accepts className); when omitted, the first letter of the name is shown |
IntegrationHubHeroAction
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Button label |
href | string | — | Renders an a link when set, otherwise type="button" |
onClick | () => void | — | Click callback |
How it works
- The connectors are an SVG layer under the grid: a ResizeObserver measures each cell centre and draws an S-curve that leaves and enters sideways from each service tile to the centre cell, recomputed whenever the layout changes.
- Each beam is a
linearGradientswept along the connector's direction, about 20% of the connector long. Its period reads--wb-integration-hub-hero-beam(default2.4s, read when the component mounts), and phases are spread evenly by index so beams reach the centre in turn; the hovered one speeds up to half the period. - The centre icon breathes 1 → 1.05 → 1 on the same period.
- Entrance: eyebrow, headline, copy and CTAs rise 16px and fade in one after another, 80ms apart, over
--wb-integration-hub-hero-duration(default600ms, easingcubic-bezier(0.22, 1, 0.36, 1)); grid cells fade in ring by ring from the centre outwards, 40ms per ring, the connectors draw once the cells are in place, and only then do the beams start. - Accent:
--wb-integration-hub-hero-accent(default#0d9488in light mode,#2dd4bfin dark), used for the eyebrow, primary button, centre outline and glow, beams and the lit-up connector. - When there are more services than the grid holds, the grid grows automatically; spare cells stay empty.
Accessibility
- The root is a
sectionlabelled by the headline viaaria-labelledby; the heading level comes fromas. - The grid is a
ulwithrole="list"; each service tile and the centre cell is anliwith anaria-label. Empty cells and the connector SVG arearia-hidden. - With
onIntegrationClick, service tiles aretype="button", reachable with Tab; focus lights up the connector just like hover and shows a focus-visible ring. Without it, tiles are not focusable, so the hero does not fill up with Tab stops that do nothing. - CTAs are
aortype="button"with a clear focus-visible ring. - When the system's reduce motion setting is on, beams and the centre breathing stop and the connectors appear without drawing, leaving a static composition; the entrance becomes a 150ms fade, applied from the first paint by a CSS media query.
Ink Ridgeline Hero
Layered ink ridgelines are generated and drawn in one by one while the headline strokes in before filling, with the ridges separating in parallax as the page scrolls.
Blur-In Headline Hero
A minimal centred React hero where the headline rises character by character out of a blur and the single call-to-action breathes with a slow halo while everything else stays still; Chinese splits per character, English keeps words intact, and every timing is a CSS variable.