1 GitHub star = 1 new component.

Star the repo
Heroes

Hero Dashboard Preview

SaaS hero with a live product dashboard mockup built from the kit’s KPI tiles and SVG line chart.

herosaasdashboardmockupcharts
Live Preview
Analytics, reimagined

See every metric that matters, in real time

Unified revenue, product and infrastructure dashboards. Set up in minutes, no SQL required.

Revenue

+9.2%

$128.4k

vs last month

Active users

+4.1%

24,812

vs last month

Churn

−0.6%

2.3%

vs last month
Revenue by channel

Last 12 weeks

  • Web
  • Mobile
02k4k6k8kW1W3W5W7W9W11
View data
PeriodWebMobile
W14.2k2.1k
W24.6k2.4k
W34.4k2.6k
W45k2.5k
W55.4k3k
W65.2k3.3k
W75.9k3.2k
W86.1k3.6k
W96k3.9k
W106.7k4.2k
W117k4.1k
W127.4k4.6k

Usage

Copy and paste this code into your React component:

tsx
import { HeroDashboardPreview } from 'react-component-kit-ui/sections';

<HeroDashboardPreview title="See every metric that matters" primary={{ label: 'Start free trial', href: '/signup' }} variant="floating" />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/sections/HeroDashboardPreview.tsx + HeroDashboardPreview.css:

HeroDashboardPreview.tsx
'use client';

import LineChart from '../charts/LineChart';
import KpiStatTile from '../charts/KpiStatTile';
import './HeroDashboardPreview.css';

/**
 * HeroDashboardPreview — SaaS hero with headline, CTAs and a live-rendered
 * product dashboard mockup built from the kit's own SVG charts.
 *
 * @param {string} badge - Eyebrow badge.
 * @param {string} title - Headline.
 * @param {string} subtitle - Supporting paragraph.
 * @param {{label: string, href: string}} primary - Primary CTA.
 * @param {{label: string, href: string}} secondary - Secondary CTA.
 * @param {string} variant - Mockup frame (browser|floating|flat).
 * @param {string} size - Vertical rhythm (sm|md|lg).
 * @param {string} className - Extra CSS class.
 */
export interface HeroDashboardPreviewProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  primary?: { label: string; href: string };
  secondary?: { label: string; href: string };
  variant?: 'browser' | 'floating' | 'flat';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function HeroDashboardPreview({
  badge = 'Analytics, reimagined',
  title = 'See every metric that matters, in real time',
  subtitle = 'Unified revenue, product and infrastructure dashboards. Set up in minutes, no SQL required.',
  primary = { label: 'Start free trial', href: '#' },
  secondary = { label: 'Book a demo', href: '#' },
  variant = 'browser',
  size = 'md',
  className = '',
}: HeroDashboardPreviewProps) {
  const id = `hdp-${Math.random().toString(36).slice(2, 8)}`;
  return (
    <>
      <div className="cq-hero-dashboard-preview-cq" data-rk-d7155c="">
      <section className={`hdp hdp--${variant} hdp--${size} ${className}`} aria-labelledby={id} data-rk-d7155c="">
        <div className="hdp__glow" aria-hidden="true" data-rk-d7155c=""></div>
        <div className="hdp__copy" data-rk-d7155c="">
          <span className="hdp__badge" data-rk-d7155c="">{badge}</span>
          <h1 id={id} className="hdp__title" data-rk-d7155c="">{title}</h1>
          <p className="hdp__subtitle" data-rk-d7155c="">{subtitle}</p>
          <div className="hdp__actions" data-rk-d7155c="">
            <a className="hdp__btn hdp__btn--primary" href={primary.href} data-rk-d7155c="">{primary.label}</a>
            <a className="hdp__btn hdp__btn--ghost" href={secondary.href} data-rk-d7155c="">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true" data-rk-d7155c=""><circle cx="12" cy="12" r="10" data-rk-d7155c="" /><path d="m10 8 6 4-6 4V8z" fill="currentColor" data-rk-d7155c="" /></svg>
              {secondary.label}
            </a>
          </div>
        </div>
      
        <div className="hdp__mock" role="group" aria-label="Product dashboard preview" data-rk-d7155c="">
          {variant === 'browser' && (
            <div className="hdp__chrome" aria-hidden="true" data-rk-d7155c="">
              <span data-rk-d7155c=""></span><span data-rk-d7155c=""></span><span data-rk-d7155c=""></span>
              <p data-rk-d7155c="">app.example.com/overview</p>
            </div>
          )}
          <div className="hdp__screen" data-rk-d7155c="">
            <div className="hdp__kpis" data-rk-d7155c="">
              <KpiStatTile label="Revenue" value="$128.4k" change={9.2} period="vs last month" size="sm" variant="solid" trend={[80, 84, 90, 88, 96, 102, 110, 118, 128]} />
              <KpiStatTile label="Active users" value="24,812" change={4.1} period="vs last month" size="sm" variant="solid" trend={[20, 21, 21.5, 22, 22.8, 23.1, 23.9, 24.2, 24.8]} />
              <KpiStatTile label="Churn" value="2.3%" change={-0.6} invert period="vs last month" size="sm" variant="solid" trend={[3.1, 3, 2.9, 2.8, 2.7, 2.6, 2.5, 2.4, 2.3]} />
            </div>
            <LineChart title="Revenue by channel" subtitle="Last 12 weeks" variant="solid" size="sm" height={220} />
          </div>
        </div>
      </section>
      </div>
    </>
  );
}

export default HeroDashboardPreview;
HeroDashboardPreview.css
/* HeroDashboardPreview — ported from astro-component-kit (sections/HeroDashboardPreview.astro) */

.cq-hero-dashboard-preview-cq:where([data-rk-d7155c]) { container-type: inline-size; }
.hdp:where([data-rk-d7155c]) {
  --hdp-py: 5.5rem;
  position: relative;
  overflow: hidden;
  padding: var(--hdp-py) var(--sp-6, 1.5rem) 0;
  background: var(--c-bg, #050505);
}
.hdp--sm:where([data-rk-d7155c]) { --hdp-py: 3.5rem; }
.hdp--lg:where([data-rk-d7155c]) { --hdp-py: 7.5rem; }
.hdp__glow:where([data-rk-d7155c]) {
  position: absolute;
  top: 30%;
  left: 50%;
  width: 900px;
  height: 600px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse, rgba(57, 135, 229, 0.16), transparent 65%);
  filter: blur(40px);
  pointer-events: none;
}
.hdp__copy:where([data-rk-d7155c]) { position: relative; max-width: 760px; margin: 0 auto 3.5rem; text-align: center; }
.hdp__badge:where([data-rk-d7155c]) {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  border-radius: var(--r-full, 999px);
  background: rgba(57, 135, 229, 0.12);
  border: 1px solid rgba(57, 135, 229, 0.3);
  color: #a9cdf7;
  font-size: 0.8125rem;
  font-weight: 600;
}
.hdp__title:where([data-rk-d7155c]) {
  margin: 1.4rem 0 1rem;
  font-size: clamp(2.25rem, 5.5vw, 3.75rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.06;
  color: var(--c-text-1, #fff);
  text-wrap: balance;
}
.hdp__subtitle:where([data-rk-d7155c]) { margin: 0 auto 2rem; max-width: 560px; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
.hdp__actions:where([data-rk-d7155c]) { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; }
.hdp__btn:where([data-rk-d7155c]) {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 48px;
  padding: 0 1.5rem;
  border-radius: var(--r-md, 12px);
  font-weight: 650;
  text-decoration: none;
  transition: transform 0.2s ease, background 0.2s ease;
}
.hdp__btn:hover:where([data-rk-d7155c]) { transform: translateY(-1px); }
.hdp__btn:focus-visible:where([data-rk-d7155c]) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
.hdp__btn--primary:where([data-rk-d7155c]) { background: var(--chart-1, #3987e5); color: #fff; }
.hdp__btn--primary:hover:where([data-rk-d7155c]) { background: #5598e7; color: #fff; }
.hdp__btn--ghost:where([data-rk-d7155c]) { color: var(--c-text-1, #fff); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14); }
.hdp__btn--ghost:hover:where([data-rk-d7155c]) { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.3); color: var(--c-text-1, #fff); }

.hdp__mock:where([data-rk-d7155c]) {
  position: relative;
  max-width: 1080px;
  margin: 0 auto;
  border-radius: var(--r-xl, 24px) var(--r-xl, 24px) 0 0;
  border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
  border-bottom: 0;
  background: #0a0a0c;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, #000 70%, transparent);
}
.hdp--floating:where([data-rk-d7155c]) .hdp__mock:where([data-rk-d7155c]) {
  border-radius: var(--r-xl, 24px);
  border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
  margin-bottom: var(--hdp-py);
  mask-image: none;
  transform: perspective(1600px) rotateX(8deg);
  box-shadow: 0 60px 120px -40px rgba(57, 135, 229, 0.35);
}
.hdp--flat:where([data-rk-d7155c]) .hdp__mock:where([data-rk-d7155c]) { background: transparent; border-color: transparent; }
.hdp__chrome:where([data-rk-d7155c]) {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
}
.hdp__chrome:where([data-rk-d7155c]) span:where([data-rk-d7155c]) { width: 10px; height: 10px; border-radius: 50%; background: rgb(var(--ink-rgb, 255 255 255) / 0.12); }
.hdp__chrome:where([data-rk-d7155c]) p:where([data-rk-d7155c]) {
  margin: 0 auto;
  padding: 0.25rem 1rem;
  border-radius: 6px;
  background: rgb(var(--ink-rgb, 255 255 255) / 0.04);
  font-size: 0.75rem;
  color: var(--c-text-3, #737373);
}
.hdp__screen:where([data-rk-d7155c]) { display: grid; gap: 1rem; padding: 1.25rem; }
.hdp__kpis:where([data-rk-d7155c]) { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; }
@container (max-width: 760px) {
  .hdp__kpis:where([data-rk-d7155c]) { grid-template-columns: 1fr; }
  .hdp__kpis:where([data-rk-d7155c]) > *:nth-child(n + 2) { display: none; }
  .hdp--floating:where([data-rk-d7155c]) .hdp__mock:where([data-rk-d7155c]) { transform: none; }
}