ATOM
Components (registry)Surfaces

StatsCard

A metric with label and optional delta in a compact card. Dashboards and KPI strips.

Preview

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

KPI with delta:

```tsx import { StatsCard } from '@/components/atoms/StatsCard';

<StatsCard value="12.4k" label="Active users" trend="up" trendValue="+8%" /> ```

## Accesibilidad

- `value` and `label` are required. Trend icons are `aria-hidden` — put meaning in `trendValue` text (e.g. `+8%`). - Do not rely on trend color alone for up/down.

## Cuándo no usar

- Full data tables → `Table`. - Marketing hero copy without a metric → Typography / layout blocks, not a stats card.

Uso

import { StatsCard } from '@/components/atoms/StatsCard';

<StatsCard value="12.4k" label="Active users" trend="up" trendValue="+8%" />

Props

PropTipoDefaultRango / opcionesWhatHow
trendselect`up`, `down`, `neutral`Direction of the trend chip (with trendValue).up for positive KPIs; down for regressions; neutral for flat. Omit both trend and trendValue when no delta.
compactboolean`true` / `false`Denser padding and type scale.true in dense dashboards/tables; false for hero KPI strips.
gradientboolean`true` / `false`Gradient surface treatment.true sparingly for featured metrics; false for data-dense grids.

Gotchas

  • react

    value and label are required strings. Trend UI only renders when both trend and trendValue are set.

  • a11y

    Trend icons are aria-hidden; put meaning in trendValue text (e.g. +12%).

Anatomía CSS

<div class="stats-card">
  <span class="stats-card__label"></span>
  <span class="stats-card__trend"></span>
  <span class="stats-card__trend--down"></span>
  <span class="stats-card__trend--neutral"></span>
  <span class="stats-card__trend--up"></span>
  <span class="stats-card__trend-icon"></span>
  <span class="stats-card__value"></span>
</div>
ClasePropósito
stats-cardroot
stats-card--compactmodifier
stats-card--gradientmodifier
stats-card__labelelement
stats-card__trendelement
stats-card__trend--downelement
stats-card__trend--neutralelement
stats-card__trend--upelement
stats-card__trend-iconelement
stats-card__valueelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allbg#ffffffcard
allborder1pxstroke.hairline
allfg#525252muted.foreground
gradientbgvar(--gradient-brand)--gradient-brand

Animaciones

PropiedadDuraciónEasing
border-colorvar(--duration-150)var(--easing-out)

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* ============================================================
   StatsCard — metric number + label + optional trend indicator
   ============================================================ */

.stats-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 32px;
  border-radius: 16px;
  background-color: #ffffff;
  border: 1px solid #e5e5e5;
  transition: border-color 150ms cubic-bezier(0.22, 1, 0.36, 1);
}

.stats-card--compact {
  padding: 16px;
  gap: 8px;
  border-radius: 12px;
}

/* ---- Value (big number) ---- */
.stats-card__value {
  font-size: 48.83px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: #0a0a0a;
  font-variant-numeric: tabular-nums;
}

.stats-card--compact .stats-card__value {
  font-size: 31.25px;
}

.stats-card--gradient .stats-card__value {
  /* fallback keeps standalone installs working without the utilities item */
  background: linear-gradient(135deg, var(--color-forest 0%, #ff6600 100%));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ---- Label ---- */
.stats-card__label {
  font-size: 12.8px;
  font-weight: 500;
  color: #525252;
  line-height: 1.4;
}

/* ---- Trend indicator ---- */
.stats-card__trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10.24px;
  font-weight: 500;
  line-height: 1;
  margin-top: 4px;
}

.stats-card__trend--up {
  color: #25d366;
}

.stats-card__trend--down {
  color: #f84131;
}

.stats-card__trend--neutral {
  color: #525252;
}

.stats-card__trend-icon {
  display: flex;
  width: 1em;
  height: 1em;
}

.stats-card__trend-icon svg {
  width: 100%;
  height: 100%;
}

Codigo fuente

components-react
components/atoms/StatsCard.tsx
import { forwardRef, type ReactNode } from 'react';

export type StatsTrend = 'up' | 'down' | 'neutral';

export type StatsCardProps = {
  value: string;
  label: string;
  trend?: StatsTrend;
  trendValue?: string;
  compact?: boolean;
  gradient?: boolean;
  className?: string;
};

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

function TrendIcon({ direction }: { direction: 'up' | 'down' }) {
  if (direction === 'up') {
    return (
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
        <polyline points="22 7 13.5 15.5 8.5 10.5 2 17" />
        <polyline points="16 7 22 7 22 13" />
      </svg>
    );
  }
  return (
    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
      <polyline points="22 17 13.5 8.5 8.5 13.5 2 7" />
      <polyline points="16 17 22 17 22 11" />
    </svg>
  );
}

export const StatsCard = forwardRef<HTMLDivElement, StatsCardProps>(
  ({ value, label, trend, trendValue, compact, gradient, className }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          'stats-card',
          compact && 'stats-card--compact',
          gradient && 'stats-card--gradient',
          className,
        )}
      >
        <span className="stats-card__value">{value}</span>
        <span className="stats-card__label">{label}</span>
        {trend && trendValue && (
          <span className={cn('stats-card__trend', `stats-card__trend--${trend}`)}>
            {trend !== 'neutral' && (
              <span className="stats-card__trend-icon" aria-hidden="true">
                <TrendIcon direction={trend} />
              </span>
            )}
            {trendValue}
          </span>
        )}
      </div>
    );
  },
);

StatsCard.displayName = 'StatsCard';

Webflow

Webflowstats-card

Pega en el Designer como application/json, luego convierte a Component (Atom / StatsCard) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • selector on .stats-card--compact .stats-card__valuecompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stats-card--gradient .stats-card__valuecompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stats-card__trend-icon svgcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • --gradient-brand on :roottoken not found in tokens-nested.json — resolve upstream or the declaration stays invalid on paste

Tras pegar: Create component → nombre Atom / StatsCard → Publish.

On this page

Detalles

Publicado14 de mayo de 2026
Categoriasurfaces
Lectura...
Visitas...
Ayuda?Slack

Componente

StatsCard

Source

components-react / cssDisponible via MCP: atom_uikit_source("stats-card")