MetricCard

GenUI

Displays a single KPI with optional trend data.

Dependencies

lucide-reactclsxtailwind-merge

Schema

LLM Schema (Intent)

{
  type: "MetricCard",
  props: {
  title: string  // The label for the metric (e.g., 'Monthly Recurring Revenue'),
  dataSource: string  // The exact name of the registered data source to fetch this metric from.,
  dataSourceParams: Record<string, aa> (optional)
}
}

Props Schema (Resolved)

{
  title: string,
  value: string | number,
  description: string (optional),
  trend: {
  value: number  // The percentage change value,
  direction: "up | down"  // Whether the trend is positive or negative
} (optional),
  isEmpty: boolean (optional),
  fallbackMessage: string (optional),
  className: string (optional)
}

Source

import { cn } from "@/lib/utils";
import { Card, CardContent } from "./card";
import { Text } from "./text";
import { Badge } from "./badge";
import { TrendingUp, TrendingDown } from "lucide-react";

export interface MetricCardProps {
  title: string;
  value: string | number;
  description?: string;
  trend?: { value: number; direction: "up" | "down" };
  isEmpty?: boolean;
  fallbackMessage?: string;
  className?: string;
}

export function MetricCard({
  title,
  value,
  description,
  trend,
  isEmpty,
  fallbackMessage,
  className,
}: MetricCardProps) {
  if (isEmpty) {
    return (
      <Card
        className={cn(
          "border-dashed border-gray-200 bg-gray-50",
          className,
        )}
      >
        <CardContent className="flex flex-col items-center justify-center p-6">
          <Text variant="muted" className="text-center">
            {fallbackMessage || "No data available."}
          </Text>
        </CardContent>
      </Card>
    );
  }

  return (
    <Card className={className}>
      <CardContent className="p-6">
        <Text variant="muted">{title}</Text>
        <div className="mt-2 flex items-baseline gap-2">
          <Text variant="h2" as="h3" className="text-3xl">
            {value}
          </Text>
          {trend && (
            <Badge
              variant={trend.direction === "up" ? "success" : "destructive"}
              className="flex items-center gap-1"
            >
              {trend.direction === "up" ? (
                <TrendingUp className="h-4 w-4" />
              ) : (
                <TrendingDown className="h-4 w-4" />
              )}
              {trend.value}%
            </Badge>
          )}
        </div>
        {description && (
          <Text variant="muted" className="mt-1">
            {description}
          </Text>
        )}
      </CardContent>
    </Card>
  );
}

CLI

npx genui add metric-card