Esc

comark-metrics

Metrics

Write two numbers and get the arrow, the percent change, and whether it's better or worse. Wrap a table of befores and afters and get its change column, or a tile for each.

Playground

Moving the pigeon map to a worker cut the main bundle 412 kB → 388 kB ↓ 5.8% and the time to first sighting 2.4 s → 1.9 s ↓ 20.8% on a mid-range phone

A pull request that changes a size or a speed reads best with the numbers next to the claim. The plugin works out the change while parsing, so every renderer only draws it, and the output needs no script.

:delta{from=412 to=388 unit="kB" better="lower"}
md

Shows 412 kB → 388 kB ↓ 5.8%. The change is green when it's better and red when it's worse.

Prop What it does
from Required. The number before.
to Required. The number after.
unit Written after both numbers, such as kB, s, or %.
better lower for sizes and durations, higher for scores. Without it, the change is gray.

The change is a percent of from. When from is 0 there is no percent, so it shows the difference instead, such as ↑ 1.5 s.

:progress{value=0.85 label="Migrated"}
:progress{value=17 max=20}
md

A native <progress> with its percent after it.

Prop What it does
value Required. A fraction from 0 to 1, or a count up to max.
max What value counts up to. Default 1.
label Shown before the bar, and names it for screen readers.

A raw <progress> in the Markdown stays as written.

::stats{better="lower"}
| Metric | Before | After |
| --- | --- | --- |
| JavaScript | 412 kB | 388 kB |
| Requests | 38 | 41 |
::
md

The table needs three columns: a name, the value before, and the value after. The plugin adds a Change column with each row's arrow and percent, colored by better, and aligns the numbers. A cell is a number, with commas if you like, and an optional unit after it; both cells of a row need the same unit. Name the headers what you like.

Prop What it does
better lower or higher, for every row.
unit The unit of a difference from 0, for cells written without one.

With tiles, each row becomes a tile: the name is above the after value, and the change is shown below it in a pill. The before value is used for the change but is not displayed:

::stats{tiles better="lower"}
| Metric | Before | After |
| --- | --- | --- |
| JavaScript | 412 kB | 388 kB |
| Largest paint | 2.4 s | 1.9 s |
::
md

Tiles also accept a two-column table when there is no before value. The value can be any text and is shown without a change:

::stats{tiles}
| Metric | Value |
| --- | --- |
| Tests | 1,204 |
| Status | Passing |
::
md

The plugin adds every problem to meta.issues, as Validation describes: a missing or unreadable number, a better that isn't lower or higher, a value outside 0 to max, a ::stats without a table or with other than three columns (or two or three columns when tiles is set), a row whose cells aren't numbers or are in different units, and a prop a block doesn't read. Each issue names its block and says how to fix it, and each has its own rule in Doctor:

`:progress` 1: `value=85` is outside 0 to 1, so the bar was clamped. Write a fraction such as `value=0.85`, or set `max=100`.
`::stats` 1, row "Size": before is in MB and after in kB. Write both in the same unit.
txt

Put the numbers where the claim is. A sentence that says what changed carries a :delta, and a table of every measurement goes in ::stats under it:

Moving the map to a worker cut the main bundle :delta{from=412 to=388 unit="kB" better="lower"} on every page

::stats{better="lower"}
| Metric | Before | After |
| --- | --- | --- |
| JavaScript | 412 kB | 388 kB |
| Largest paint | 2.4 s | 1.9 s |
::
md

Copy the numbers from the tool's output as it prints them: 1,204 and 2.4 s both read.

better applies to the whole table. When some rows are better lower, such as sizes, and others better higher, such as scores, write two tables, one for each.

The plugin takes no options. metrics: false in comarkUi() turns it off.

comark-ui brings comark-metrics and registers it. To use it alone, install it with Comark and a renderer:

pnpm add comark @comark/html comark-metrics

Register the plugin and pass the renderer's components:

import { createHtmlRenderer } from "@comark/html";
import metrics, { components } from "comark-metrics/html";

const render = createHtmlRenderer({ plugins: [metrics()], components });
ts

The renderer inlines the styles once per document, and the output needs no script. For Vue, React, or Svelte, import from comark-metrics/vue, comark-metrics/react, or comark-metrics/svelte; the components add the styles when they mount. Import comark-metrics/style.css with your styles so a server-rendered page doesn't shift. Renderers covers using it with other plugins.

A delta's change is green when it's better, red when it's worse, and muted when there's no verdict. A progress bar follows shadcn/ui's Progress: the primary color on a track of itself. The rules sit in a comark CSS layer, so any plain rule of your own wins.

Deltas, bars, and tables read these shared tokens:

Token Used for
--comark-foreground Tile values and the progress bar, when the page has no --primary
--comark-muted The value before, a change with no verdict, a bar's label and percent
--comark-font Inherited text, with tabular numbers
--comark-border Tile borders
--comark-background Tile backgrounds
--comark-radius Tile corner radius
Variable Default Used for
--cmt-better #259f56, #59d38c in dark mode A change that's better
--cmt-worse shadcn/ui's --destructive, else #ee3533, #fd736d in dark mode A change that's worse
--cmt-bar shadcn/ui's --primary, else --comark-foreground The progress bar's fill, and its track at 20%

Set --cmt-better and --cmt-worse on .cmt-delta, and --cmt-bar on .cmt-progress.

Class Element
.cmt-delta A delta, with data-direction (up, down, same) and data-verdict (better, worse, neutral)
.cmt-from, .cmt-to, .cmt-change Its value before, after, and the arrow with the percent
.cmt-progress A progress bar, with .cmt-label before it and .cmt-percent after
.cmt-stats A ::stats table
.cmt-tiles The grid rendered by ::stats{tiles}
.cmt-tile One stat tile, containing its name, value, and optional change
.cmt-value The large value inside a tile