# Stat Card

> Scrollable stat cards with values, trends, and change indicators.

A shadcn/ui block from the [@manifest](https://ui.manifest.build) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/manifest/stat-card).

- Page, with a live preview: https://shadcn.exchange/r/manifest/stat-card
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/manifest/stat-card-light-63dea26a11b6270a.png
- Registry item: https://ui.manifest.build/r/stat-card.json
- Categories: miscellaneous
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.manifest.build/r/stat-card.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`

## Files

### `registry/miscellaneous/stat-card.tsx`

````tsx
"use client"

import { TrendingUp, TrendingDown, Minus } from "lucide-react"
import { cn } from "@/lib/utils"
import { demoStats } from './demo/miscellaneous'

/**
 * Represents a single statistic card with trend data.
 * @interface StatCard
 * @property {string} [label] - Label for the stat (e.g., "Sales", "Orders")
 * @property {string | number} [value] - The stat value to display
 * @property {number} [change] - Percentage change value
 * @property {string} [changeLabel] - Additional context for the change
 * @property {React.ReactNode} [icon] - Optional icon for the stat
 * @property {"up" | "down" | "neutral"} [trend] - Trend direction
 */
export interface StatCard {
  label?: string
  value?: string | number
  change?: number
  changeLabel?: string
  icon?: React.ReactNode
  trend?: "up" | "down" | "neutral"
}

/**
 * ═══════════════════════════════════════════════════════════════════════════
 * StatCardProps
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Props for the StatCard component, which displays a grid of statistic cards
 * with values, trend indicators, and optional icons.
 */
export interface StatCardProps {
  data?: {
    /** Array of stat cards to display in the grid. */
    stats?: StatCard[]
  }
}


/**
 * A statistics card grid displaying key metrics with trend indicators.
 * Shows stats in a responsive 2-3 column grid layout.
 *
 * Features:
 * - Trend indicators (up, down, neutral) with colors
 * - Percentage change display
 * - Optional icons per stat
 * - Responsive grid layout
 * - Optional change labels
 *
 * @component
 * @example
 * ```tsx
 * <StatCard
 *   data={{
 *     stats: [
 *       { label: "Revenue", value: "$12,543", change: 12.5, trend: "up" },
 *       { label: "Orders", value: "342", change: -3.2, trend: "down" },
 *       { label: "Customers", value: "1,205", change: 0, trend: "neutral" }
 *     ]
 *   }}
 * />
 * ```
 */
export function StatCard({ data }: StatCardProps) {
  const resolved: NonNullable<StatCardProps['data']> = data ?? { stats: demoStats }
  const stats = resolved.stats ?? []
  const getTrendIcon = (trend?: "up" | "down" | "neutral") => {
    switch (trend) {
      case "up":
        return <TrendingUp className="h-3.5 w-3.5" />
      case "down":
        return <TrendingDown className="h-3.5 w-3.5" />
      default:
        return <Minus className="h-3.5 w-3.5" />
    }
  }

  const getTrendColor = (trend?: "up" | "down" | "neutral") => {
    switch (trend) {
      case "up":
        return "text-green-600"
      case "down":
        return "text-red-600"
      default:
        return "text-muted-foreground"
    }
  }

  return (
    <div className="w-full">
      <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 sm:gap-4">
        {stats.map((stat, index) => {
          return (
            <div
              key={index}
              className="rounded-md sm:rounded-lg border bg-card p-2 sm:p-3 space-y-0.5 sm:space-y-1"
            >
              {(stat.label || stat.icon) && (
                <div className="flex items-center justify-between">
                  {stat.label && (
                    <span className="text-[10px] sm:text-xs text-muted-foreground">{stat.label}</span>
                  )}
                  {stat.icon}
                </div>
              )}
              {(stat.value !== undefined || stat.change !== undefined) && (
                <div className="flex flex-wrap items-baseline gap-1 sm:gap-2">
                  {stat.value !== undefined && (
                    <span className="text-base sm:text-xl font-bold">{stat.value}</span>
                  )}
                  {stat.change !== undefined && (
                    <span
                      className={cn(
                        "flex items-center gap-0.5 text-[10px] sm:text-xs font-medium shrink-0",
                        getTrendColor(stat.trend)
                      )}
                    >
                      {getTrendIcon(stat.trend)}
                      {Math.abs(stat.change)}%
                    </span>
                  )}
                </div>
              )}
              {stat.changeLabel && (
                <span className="text-[10px] sm:text-xs text-muted-foreground">
                  {stat.changeLabel}
                </span>
              )}
            </div>
          )
        })}
      </div>
    </div>
  )
}
````

### `registry/miscellaneous/demo/miscellaneous.ts`

```ts
// Demo data for Miscellaneous category components
// This file contains sample data used for component previews and documentation

export const demoStats = [
  { label: 'Revenue', value: '$12,345', change: 12.5 },
  { label: 'Orders', value: '1,234', change: -3.2 },
  { label: 'Customers', value: '567', change: 8.1 },
]

export const demoHeroDefault = {
  logo1: { text: 'Acme', alt: 'Acme' },
  title: 'Build beautiful chat experiences with Manifest UI',
  subtitle:
    'Create beautiful chat experiences with our comprehensive component library designed for agentic applications.',
  primaryButton: { label: 'Get Started' },
  secondaryButton: { label: 'GitHub' },
}

export const demoHeroTwoLogos = {
  logo1: { text: 'Acme' },
  logo2: {
    url: '/logo-manifest-ui.svg',
    urlLight: '/logo-manifest-ui-light.svg',
    alt: 'Manifest',
  },
  logoSeparator: 'x',
  title: 'Acme x Manifest UI',
  subtitle:
    'Combining the best of both worlds to deliver exceptional user experiences.',
  primaryButton: { label: 'Get Started' },
  secondaryButton: { label: 'GitHub' },
}

export const demoHeroWithTechLogos = {
  logo1: { text: 'Acme' },
  title: 'Build your next project with Acme',
  subtitle:
    'Create beautiful experiences with our comprehensive platform designed for modern applications.',
  primaryButton: { label: 'Get Started' },
  secondaryButton: { label: 'GitHub' },
  techLogosLabel: 'Built with open-source technologies',
  techLogos: [
    {
      url: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nextjs/nextjs-original.svg',
      alt: 'Next.js',
      name: 'Next.js',
    },
    {
      url: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/typescript/typescript-original.svg',
      alt: 'TypeScript',
      name: 'TypeScript',
    },
    {
      url: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg',
      alt: 'React',
      name: 'React',
    },
    {
      url: 'https://cdn.jsdelivr.net/gh/devicons/devicon/icons/tailwindcss/tailwindcss-original.svg',
      alt: 'Tailwind CSS',
      name: 'Tailwind CSS',
    },
    {
      url: 'https://ui.manifest.build/demo/os-tech-mnfst.svg',
      alt: 'Manifest',
      name: 'Manifest',
    },
  ],
}

export const demoHeroMinimal = {
  logo1: undefined,
  title: 'Welcome to the Future',
  subtitle: 'A simple, clean hero without logos or extra elements.',
  primaryButton: { label: 'Get Started' },
  secondaryButton: undefined,
}
```
