Skip to content
ssntl/ui

Metric card

One metric in an 8px frame with concentric corners, then its title and one sentence.

Repeat customers

41%

+6 points since July

People come back

Two in five buyers ordered again within 60 days.

Installation

$ pnpm dlx shadcn@latest add @ssntl/metric-card

Usage

tsx
import { MetricCard, MetricCardBody, MetricCardPanel, MetricCardText, MetricCardTitle } from "@/components/ui/metric-card"

<MetricCard>
  <MetricCardPanel>{/* a Stat or a chart */}</MetricCardPanel>
  <MetricCardText>
    <MetricCardTitle>People come back</MetricCardTitle>
    <MetricCardBody>Two in five buyers ordered again within 60 days.</MetricCardBody>
  </MetricCardText>
</MetricCard>
  • Breathing room from one rhythm: an 8px frame, 24px inside the panel, 16px around the text.
  • Concentric corners: the panel takes the card's 32px minus the 8px frame (needs the radius-concentric utility, installed with it).
  • One metric per card; the title states the finding, the body gives one sentence of context.

Code

components/ui/metric-card.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

// Essential Design MetricCard: a white shell with an 8px frame around a muted
// panel that holds one piece of evidence, then its title and one sentence.
// Breathing room comes from one rhythm: 8px frame, 24px inside the panel,
// 16px around the text. Concentric: the panel takes 32 − 8 = 24px.
function MetricCard({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="metric-card"
      className={cn(
        "grid content-start rounded-outer-lg bg-card p-concentric-2 shadow-float",
        className
      )}
      {...props}
    />
  )
}

function MetricCardPanel({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="metric-card-panel"
      className={cn("rounded-inner bg-muted p-6", className)}
      {...props}
    />
  )
}

function MetricCardText({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="metric-card-text"
      className={cn("grid gap-1 p-4", className)}
      {...props}
    />
  )
}

function MetricCardTitle({ className, ...props }: React.ComponentProps<"h3">) {
  return (
    <h3
      data-slot="metric-card-title"
      className={cn("text-heading", className)}
      {...props}
    />
  )
}

function MetricCardBody({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="metric-card-body"
      className={cn("text-body text-pretty text-muted-foreground", className)}
      {...props}
    />
  )
}

export {
  MetricCard,
  MetricCardBody,
  MetricCardPanel,
  MetricCardText,
  MetricCardTitle,
}