1 GitHub star = 1 new component.

Star the repo
Cardscardsstatsanalyticskpidashboard

Stats Trend Card

Modern analytics KPI metric card with values, trend badge, and mini sparkline.

Live Preview

Monthly Active Builders↑+18.4%
14,290
vs last 30 days

Usage

Copy and paste this code into your React component:

tsx
import { StatsTrendCard } from 'react-component-kit-ui/cards';

<StatsTrendCard title="Active Subscriptions" value="14,290" change="+12.8%" isPositive={true} subtitle="Compared to previous 30 days" />

Manual Installation

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

StatsTrendCard.tsx
'use client';

import type { CSSProperties } from 'react';
import './StatsTrendCard.css';

/** class:list β†’ className */
function cx(...args: unknown[]): string {
  const out: string[] = [];
  const walk = (v: unknown) => {
    if (!v) return;
    if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
    else if (Array.isArray(v)) v.forEach(walk);
    else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
  };
  args.forEach(walk);
  return out.join(' ');
}

/** "a: b; --c: d" β†’ style object */
function st(css: string | undefined): CSSProperties {
  const o: Record<string, string> = {};
  for (const decl of String(css ?? '').split(';')) {
    const i = decl.indexOf(':');
    if (i < 0) continue;
    const k = decl.slice(0, i).trim();
    if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
  }
  return o as CSSProperties;
}

/**
 * StatsTrendCard β€” Compact KPI metric card featuring delta percentages, trend directions, and mini activity bars.
 * 
 * @param {string} label - Metric name (e.g. 'Active Users').
 * @param {string} value - Formatted value figure (e.g. '42,850').
 * @param {string} change - Delta percentage string (e.g. '+18.4%').
 * @param {'up' | 'down' | 'neutral'} trend - Direction of the trend. Default is 'up'.
 * @param {string} timeframe - Timeframe subtitle (e.g. 'vs last month').
 */
export interface StatsTrendCardProps {
  label: string;
  value: string;
  change: string;
  trend?: 'up' | 'down' | 'neutral';
  timeframe?: string;
}

export function StatsTrendCard({
  label = 'Monthly Active Builders',
  value = '14,290',
  change = '+18.4%',
  trend = 'up',
  timeframe = 'vs last 30 days',
}: StatsTrendCardProps) {
  const bars = [35, 55, 40, 75, 60, 90, 80, 100];
  return (
    <>
      <div className="trend-card" data-rk-0bf6d9="">
        <div className="trend-card__top" data-rk-0bf6d9="">
          <span className="trend-card__label" data-rk-0bf6d9="">{label}</span>
          <span className={cx(['trend-card__badge', `trend-card__badge--${trend}`])} data-rk-0bf6d9="">
            {trend === 'up' && '↑'}
            {trend === 'down' && '↓'}
            {change}
          </span>
        </div>
      
        <div className="trend-card__value" data-rk-0bf6d9="">{value}</div>
      
        <div className="trend-card__bottom" data-rk-0bf6d9="">
          <span className="trend-card__timeframe" data-rk-0bf6d9="">{timeframe}</span>
          <div className="trend-card__chart" aria-hidden="true" data-rk-0bf6d9="">
            {bars.map((h, __i: number) => (
              <span key={__i} className="bar" style={st(`height: ${h}%;`)} data-rk-0bf6d9=""></span>
            ))}
          </div>
        </div>
      </div>
    </>
  );
}

export default StatsTrendCard;
StatsTrendCard.css
/* StatsTrendCard β€” ported from astro-component-kit (cards/StatsTrendCard.astro) */

.trend-card:where([data-rk-0bf6d9]) {
  display: flex;
  flex-direction: column;
  padding: var(--sp-6, 1.5rem);
  background: rgba(255, 255, 255, 0.02);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--r-xl, 20px);
  width: 100%;
  max-width: 280px;
  font-family: var(--font-sans, system-ui, sans-serif);
  transition: transform 0.25s ease, border-color 0.25s ease;
}

.trend-card:hover:where([data-rk-0bf6d9]) {
  transform: translateY(-2px);
  border-color: rgba(184, 134, 11, 0.3);
}

.trend-card__top:where([data-rk-0bf6d9]) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.trend-card__label:where([data-rk-0bf6d9]) {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--c-text-2, #a3a3a3);
}

.trend-card__badge:where([data-rk-0bf6d9]) {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--r-full, 9999px);
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.trend-card__badge--up:where([data-rk-0bf6d9]) {
  background: rgba(16, 185, 129, 0.15);
  color: #34d399;
  border: 1px solid rgba(16, 185, 129, 0.3);
}

.trend-card__badge--down:where([data-rk-0bf6d9]) {
  background: rgba(239, 68, 68, 0.15);
  color: #f87171;
  border: 1px solid rgba(239, 68, 68, 0.3);
}

.trend-card__badge--neutral:where([data-rk-0bf6d9]) {
  background: rgba(255, 255, 255, 0.08);
  color: var(--c-text-2, #a3a3a3);
}

.trend-card__value:where([data-rk-0bf6d9]) {
  font-size: 1.85rem;
  font-weight: 900;
  color: var(--c-text-1, #ffffff);
  letter-spacing: -0.02em;
  margin-bottom: 12px;
}

.trend-card__bottom:where([data-rk-0bf6d9]) {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
}

.trend-card__timeframe:where([data-rk-0bf6d9]) {
  font-size: 0.74rem;
  color: var(--c-text-3, #737373);
}

.trend-card__chart:where([data-rk-0bf6d9]) {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 24px;
  width: 60px;
}

.bar:where([data-rk-0bf6d9]) {
  flex: 1;
  background: rgba(184, 134, 11, 0.35);
  border-radius: 2px;
  transition: background 0.2s;
}

.trend-card:hover:where([data-rk-0bf6d9]) .bar:where([data-rk-0bf6d9]) {
  background: var(--c-primary-light, #d4a017);
}

Quick Info

Category
Cards
Filename
StatsTrendCard.tsx
Dependencies
None β€” pure React + CSS
Tags
cardsstatsanalyticskpidashboard