Metric Tile <mv-metric-tile>

Tarjeta de KPI independiente para dashboards de aplicaciones, configurada solo con atributos: etiqueta con un icono opcional, una cifra que se anima contando (number-ticker), su variación respecto a un valor anterior (trend-chip), un sparkline de histórico (chart) o un indicador hacia un máximo (progress en anillo), una nota y un pie, además de un tamaño compacto para cuadrículas densas, como ocho tarjetas de retrasos de pago en una misma pantalla de finanzas.

CategoríaVisualización de datos
TipoWeb Component (<mv-metric-tile>)
Estadoestable
También instalachart, number-ticker, progress, trend-chip
Keywordskpi, metric, stat card, stat tile, dashboard, sparkline, gauge, trend, delta, finance, compact, app

When to use

  • An app dashboard needs a row of KPI tiles, each with its figure, its change vs the previous period and a small history line
  • A finance or ops screen packs many indicators, such as payment delays by bucket, into a dense grid of small tiles
  • A figure must be read against a goal, like quota attainment or budget used, with a ring gauge beside it
  • Tiles refresh with new values and the figures should roll to them while screen readers stay uninterrupted

Avoid when

  • A landing page shows key figures in editorial columns or marketing cards → use Stats instead
  • The trend itself is the subject and needs axes, tooltips and several series → use Chart instead
  • Only a change badge is needed inside a table or a sentence → use Trend Chip instead

Instalación

node scripts/add.mjs metric-tile --out ./src/marvelous

Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["metric-tile"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/chart/chart.js, components/chart/chart.css, components/number-ticker/number-ticker.js, components/number-ticker/number-ticker.css, components/progress/progress.css, components/trend-chip/trend-chip.js, components/trend-chip/trend-chip.css, components/metric-tile/metric-tile.js, components/metric-tile/metric-tile.css.

Uso

Marcado de referencia: parte de él y personalízalo con atributos, data-* y variables CSS:

<div id="mv-mt-demo" style="display:grid;gap:1.75rem;width:min(100%,68rem);margin-inline:auto">
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,14.5rem),1fr));gap:1rem">
    <mv-metric-tile label="Revenue" value="48210" format="currency" previous="42900" period="vs last month"
      history="31200,34800,33100,39400,42800,41200,45900,42900,48210" history-labels="Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep">
      <svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M16.5 7.5c0-1.7-2-3-4.5-3s-4.5 1.3-4.5 3 2 2.6 4.5 3 4.5 1.4 4.5 3.2-2 3-4.5 3-4.5-1.3-4.5-3"/></svg>
    </mv-metric-tile>

    <mv-metric-tile label="Days sales outstanding" value="42" unit="days" previous="47" change-format="day" better="down" period="vs last month"
      chart-type="line" history="51,49,50,47,45,47,42" history-labels="Mar,Apr,May,Jun,Jul,Aug,Sep" note="Target: 40 days or fewer">
      <svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></svg>
    </mv-metric-tile>

    <mv-metric-tile id="mv-mt-quota" label="Quota attainment" value="72400" format="currency" compact max="100000" previous="61800" period="vs last quarter"
      note="Q3 target: $100K, 9 days left">
      <svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="4.5"/><circle cx="12" cy="12" r="0.5"/></svg>
    </mv-metric-tile>

    <mv-metric-tile label="Payments received" value="164" unit="invoices" previous="131" change-format="number" period="vs last week"
      chart-type="bar" history="96,118,104,142,121,131,164" history-labels="Week 33,Week 34,Week 35,Week 36,Week 37,Week 38,Week 39">
      <svg slot="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h9l4 4v14H6z"/><path d="M14 3v5h5M9.5 14l2 2 4-4"/></svg>
      <a slot="footer" href="#mv-mt-delays">Review late payments</a>
    </mv-metric-tile>
  </div>

  <section id="mv-mt-delays" aria-labelledby="mv-mt-delays-title" style="display:grid;gap:.85rem">
    <div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.75rem">
      <h3 id="mv-mt-delays-title" style="margin:0;font-size:var(--mv-text-base);font-weight:var(--mv-weight-semibold)">Payment delays <span style="color:var(--mv-fg-muted);font-weight:var(--mv-weight-medium)">September, compared with August</span></h3>
      <button class="mv-button" data-variant="outline" data-size="sm" id="mv-mt-next" type="button">Show October</button>
    </div>
    <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,13rem),1fr));gap:.75rem">
      <mv-metric-tile data-size="compact" id="mv-mt-ontime" label="Paid on time" value="81.4" format="percent" decimals="1" previous="78.9" change-format="points" period="vs Aug"
        history="74.2,76.8,75.1,78.9,81.4" history-labels="May,Jun,Jul,Aug,Sep"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-delay" label="Average delay" value="6.8" unit="days" previous="8.1" change-format="day" better="down" period="vs Aug"
        chart-type="line" history="9.4,8.8,9.1,8.1,6.8" history-labels="May,Jun,Jul,Aug,Sep"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-15" label="1 to 15 days late" value="412300" format="currency" compact previous="455000" better="down" period="vs Aug"
        history="398000,431000,447000,455000,412300" history-labels="May,Jun,Jul,Aug,Sep"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-30" label="16 to 30 days late" value="186400" format="currency" compact previous="171200" better="down" period="vs Aug"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-60" label="31 to 60 days late" value="74100" format="currency" compact previous="92300" better="down" period="vs Aug"
        history="88000,95400,101200,92300,74100" history-labels="May,Jun,Jul,Aug,Sep"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-90" label="Over 60 days late" value="38200" format="currency" compact previous="36500" better="down" period="vs Aug"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-disputed" label="Disputed invoices" value="23" previous="23" change-format="number" better="down" period="vs Aug"
        chart-type="bar" history="31,27,29,23,23" history-labels="May,Jun,Jul,Aug,Sep"></mv-metric-tile>
      <mv-metric-tile data-size="compact" id="mv-mt-collected" label="Collected toward target" value="1860000" format="currency" compact max="2400000"></mv-metric-tile>
    </div>
  </section>
</div>
<script type="module">
  const root = document.getElementById("mv-mt-demo");
  const months = [
    { title: "September, compared with August", period: "vs Aug", data: {
      "mv-mt-ontime": [81.4, 78.9], "mv-mt-delay": [6.8, 8.1], "mv-mt-15": [412300, 455000], "mv-mt-30": [186400, 171200],
      "mv-mt-60": [74100, 92300], "mv-mt-90": [38200, 36500], "mv-mt-disputed": [23, 23], "mv-mt-collected": [1860000], "mv-mt-quota": [72400, 61800],
    } },
    { title: "October, compared with September", period: "vs Sep", data: {
      "mv-mt-ontime": [84.1, 81.4], "mv-mt-delay": [5.9, 6.8], "mv-mt-15": [389700, 412300], "mv-mt-30": [154900, 186400],
      "mv-mt-60": [79800, 74100], "mv-mt-90": [31600, 38200], "mv-mt-disputed": [19, 23], "mv-mt-collected": [2210000], "mv-mt-quota": [88900, 61800],
    } },
  ];
  let i = 0;
  const button = root.querySelector("#mv-mt-next");
  button.addEventListener("click", () => {
    i = (i + 1) % months.length;
    const month = months[i];
    for (const [id, [value, previous]] of Object.entries(month.data)) {
      const tile = root.querySelector(`#${id}`);
      if (previous !== undefined) tile.setAttribute("previous", previous);
      if (id !== "mv-mt-quota") tile.setAttribute("period", month.period);
      if (tile.hasAttribute("history")) {
        // October adds a point to the history (and drops the oldest one); September restores it.
        tile.sept ??= [tile.getAttribute("history"), tile.getAttribute("history-labels")];
        const [history, labels] = tile.sept;
        tile.setAttribute("history", i ? `${history.split(",").slice(1)},${value}` : history);
        tile.setAttribute("history-labels", i ? `${labels.split(",").slice(1)},Oct` : labels);
      }
      tile.setAttribute("value", value);
    }
    root.querySelector("#mv-mt-delays-title span").textContent = month.title;
    button.textContent = i === 0 ? "Show October" : "Show September";
  });
</script>

API

Attributes

NameTipoDefaultDescription
labelstringName of the metric. Shown above the figure and used as the tile's accessible name (role=group).
valuenumberThe figure. Changing it rolls the number to the new value and recomputes the change and the gauge. Without it the tile shows “-” and says “No data”.
unitstringText after the figure, smaller and muted (days, ms, invoices).
formatdecimal | currency | percent | compactdecimalIntl style of the figure, as in number-ticker. percent expects percentage points (42 = 42%).
currencyISO 4217 codeUSDCurrency for format=currency.
decimalsnumber(inferred from value)Fraction digits of the figure.
compactbooleanCompact notation, combinable with currency ($72.4K).
localeBCP 47nearest lang, else en-USFormatting locale for the figure, the gauge and the sparkline. The change follows the nearest lang, like trend-chip.
previousnumberValue of the previous period. With value, it drives the change chip (relative change by default).
changenumberA change computed elsewhere, used instead of previous (a fraction for percent and points: 0.124 = +12.4%).
change-formatpercent | points | number | currency | ISO 4217 code | Intl unitpercentHow the change is computed and printed (trend-chip's format). points with format=percent compares the two percentages in points; an Intl unit such as day or millisecond prints the difference in that unit.
betterup | down | neutralupWhich direction is good: colors and words of the change. Use down for delays, costs, churn.
periodstringWhat the change is measured against (vs last month).
historycomma-separated numbersPast values, oldest first, drawn as a sparkline (two values at least). An empty item is a gap.
history-labelscomma-separated stringsPoint 1, Point 2…Labels of the history values, read in the sparkline's data table.
chart-typearea | line | barareaSparkline style.
maxnumberShows a ring gauge of value between min and max, with the percentage in its center.
minnumber0Lower bound of the gauge.
notestringA muted line under the figures (target, context, last update).
animateview | noneviewview: the figure counts up and the sparkline draws in when the tile scrolls into view. none: everything shows at once.
data-sizeregular | compactregularcompact: smaller text and padding, the figure and the change share a line, and the sparkline or the gauge sits on the right, for dense grids of tiles.

Properties

NameTipoDefaultDescription
stringsobjectMvMetricTile.stringsTranslatable text merged over the defaults: empty (“-”), noValue (“No data”), gauge (“{percent} of {max}”, also {value}), chart (“{label}, history”) and point (“Point {n}”). Nested change and chart objects are handed to the trend chip's and the sparkline's own strings. Can be set before the element is defined.
ticker / chip / chartElement | nullRead-only: the inner mv-number-ticker, mv-trend-chip and mv-chart, for fine tuning (tile.chip.tolerance = 0.005). null before the first connection.
percentnumber | nullRead-only: where value stands between min and max (0 to 100), or null without a gauge.
label / value / previous / change / history / max …string | numberEvery attribute is mirrored as a property (camelCase: changeFormat, historyLabels, chartType); setting one updates the tile at once, even before the element is defined.

Methods

NameDescription
render()Syncs every part with the attributes now (rarely needed: changes already do it).

Events

NameDescription
mv-endBubbles from the inner number-ticker when the figure finishes counting (detail.value).

Content structure

NameDescription
iconA child with slot="icon" (an svg, an img, an icon element) moves to the left of the label. It is decorative: the tile hides it from screen readers.
footerChildren with slot="footer" (a link, a button, a timestamp) go to a footer row under a hairline.

CSS classes

NameDescription
mv-metric-tile-body / -mainGenerated: the figures column and, beside it, the gauge.
mv-metric-tile-head / -icon / -labelGenerated: the label row.
mv-metric-tile-figure / -unit / -emptyGenerated: the figure (an mv-number-ticker), its unit, and the “-” shown without a value.
mv-metric-tile-changeGenerated: the mv-trend-chip.
mv-metric-tile-gaugeGenerated: the ring gauge (a .mv-progress-ring with role=meter).
mv-metric-tile-chartGenerated: the mv-chart sparkline.
mv-metric-tile-note / -footerGenerated: the note line and the footer row.

CSS variables

NameDefaultDescription
--mv-metric-tile-bgvar(--mv-surface)Tile background.
--mv-metric-tile-bordervar(--mv-border)Tile border color.
--mv-metric-tile-radiusvar(--mv-radius-xl) (lg in compact)Corner radius.
--mv-metric-tile-shadowvar(--mv-shadow-xs)Tile shadow.
--mv-metric-tile-paddingvar(--mv-space-5) (space-3 in compact)Inner padding; the sparkline bleeds over it.
--mv-metric-tile-value-sizevar(--mv-text-3xl) (text-xl in compact)Font size of the figure.
--mv-metric-tile-icon-bg / -icon-color / -icon-boxvar(--mv-bg-muted) / var(--mv-fg) / 1.75remIcon square (compact shows the bare icon).
--mv-metric-tile-chart-colorvar(--mv-accent)Sparkline color. Tip: mv-metric-tile[data-tone="bad"] { --mv-metric-tile-chart-color: var(--mv-danger) }.
--mv-metric-tile-chart-width4.5remSparkline width in the compact size.
--mv-metric-tile-gauge-colorvar(--mv-accent)Gauge fill color.

Accessibility

The tile is a role=group named by its label, so a screen reader announces “Revenue, group” and then reads, in order: the figure (number-ticker's hidden final value: the counting digits are aria-hidden and never read in bursts) with its unit, the change as one whole sentence from trend-chip (“Up 12.4% vs last month, an improvement”, “Down 5 days vs last month, an improvement” with better="down"), the gauge as role=meter named by the label with a spoken value (“72% of $100K”), the sparkline as chart's labelled group with its visually hidden data table, then the note and the footer. The icon is aria-hidden. Without a value the figure shows “-” and says “No data”. Updates are silent (no live region): a refreshing dashboard never interrupts the reader. Nothing is focusable except what you put in the footer; the compact sparkline is not interactive either. Meaning never depends on color: the change carries a glyph and words, and data-tone / data-dir (good, bad, neutral; up, down, flat) are exposed on the tile for styling only. Reduced motion: the figure, the gauge and the sparkline show their final state at once. Forced colors: the tile keeps a CanvasText frame, the chip and the chart switch to their own system-color styles. Known limits: the gauge animates on first display, not on scroll into view; the change chip reads its locale from the nearest lang rather than the locale attribute; tolerance and digits of the change are set on tile.chip.

Esta página se tradujo con IA. Informar de un problema de traducción