Menu


Diff stat


How much a change touched: the lines added and removed, such as "+128 −12", and a row of five small squares that shows the balance.

Customize the preview


View
128 lines added, 12 lines removed

Options


The diff stat has no choices: it is drawn one way.

Examples


Usage

The lines added and removed, and five squares for the balance.

128 lines added, 12 lines removed

Balance

Mostly added, mixed, and mostly removed: the squares follow the counts.

128 lines added, 12 lines removed64 lines added, 48 lines removed6 lines added, 210 lines removed

One side only

Only added, only removed, and nothing changed, which leaves every square empty.

42 lines added, 0 lines removed0 lines added, 17 lines removed0 lines added, 0 lines removed

Beside a change

After a change's title in a list; thousands get a comma.

Add the pricing page1,240 lines added, 86 lines removedRemove the old sign-up form3 lines added, 412 lines removed

Guidelines


Purpose

Shows how much a change touched: the lines it added and the lines it removed, such as "+128 −12", then a row of five small squares that shows the balance at a glance, green for added and red for removed. It sits beside a change's title in a list of changes, a review or an agent's report of what it did.

When to use

  • Beside a change, a commit or a file in a list of changes, where a reader wants to see its size before opening it.
  • In a summary of what an agent or a person changed.

When not to use

  • For other counts that go up and down, such as visitors or sales. Use a stat with its trend.
  • For a share of a total. Use a meter.

Usage rules

Do

  • Keep it beside the change it describes.
  • Give it the real counts; the squares follow from them.

Don't

  • Use it for anything but lines added and removed.
  • Draw the squares without the counts.
Fix the date in receipts14 lines added, 9 lines removed
Do

Keep it beside the change it describes, so a reader sees its size before opening it.

Visitors this week320 lines added, 45 lines removed
Don't

Use it for other counts that go up and down; a stat with its trend says those.

Anatomy

  • The block: the two counts and the squares, side by side on one line.
  • The added part, the lines added with a plus sign, in the success text color.
  • The removed part, the lines removed with a minus sign, in the danger text color.
  • The blocks part, five small squares; each block part is green for added, red for removed or the empty track color.
  • The hidden part, the sentence a screen reader hears.

Both counts use the small code style with figures of one width, so counts line up down a list.

Behavior

The squares split five between the two counts by their share of the lines, rounded. A side with any lines at all keeps at least one square, so a small removal still shows; a change with no lines leaves all five empty. Counts are written with a comma between thousands.

Accessibility


What it does for you

  • A screen reader hears one sentence, such as "128 lines added, 12 lines removed". The signs, the counts as drawn and the squares are hidden from it, so it does not read "plus 128 minus 12".
  • The signs and the numbers carry the meaning; the colors and the squares only repeat it.
  • In high-contrast mode every square is outlined and the ones with lines are filled with the text color.

What you must do

  • Give label your own sentence when the page is in another language or counts something other than lines.

How to test

  • With a screen reader, it is read as one sentence with both counts.
  • With the colors removed, the signs and the numbers still say what was added and removed.
  • In a forced-color mode, every square is outlined and the ones with lines are filled.
  • With text at 200% and the page 320 pixels wide, the counts and squares stay whole.

Source


The files the diff stat is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
diff-stat.css
/* smbk-diff-stat — Diff stat. Figma "Diff stat" 7580:1825 (Balance: Adds, Mixed, Removes).
 * How much a change touched: the lines added in the success text color and the lines removed in the danger
 * text color, each with its sign, in the small code style, then five small squares that show the balance.
 * The signs and the numbers carry the meaning, so the colors are never the only sign; a hidden sentence says
 * it all to a screen reader. */

.smbk-diff-stat {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-6);
  vertical-align: middle;
  font-family: var(--smbk-typography-code-sm-font-family);
  font-size: var(--smbk-typography-code-sm-font-size);
  font-weight: var(--smbk-typography-code-sm-font-weight);
  line-height: var(--smbk-typography-code-sm-line-height);
  letter-spacing: var(--smbk-typography-code-sm-letter-spacing);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The block frames the words it hides for screen readers, so they stay inside it wherever it is placed: without a
   frame they hang from the page itself and stretch it, past the end of an area that scrolls. No weight, so a parent
   that places the block keeps its say. */
:where(.smbk-diff-stat) {
  position: relative;
}

.smbk-diff-stat__added {
  color: var(--smbk-color-feedback-success-text-on-subtle);
}

.smbk-diff-stat__removed {
  color: var(--smbk-color-feedback-danger-text-on-subtle);
}

.smbk-diff-stat__blocks {
  display: inline-flex;
  flex-shrink: 0;
  gap: var(--smbk-space-2);
}

.smbk-diff-stat__block {
  display: block;
  width: var(--smbk-size-8);
  height: var(--smbk-size-8);
  border-radius: var(--smbk-radius-2);
  background: var(--smbk-color-surface-track-subtle);
}

.smbk-diff-stat__block--added {
  background: var(--smbk-color-feedback-success-icon);
}

.smbk-diff-stat__block--removed {
  background: var(--smbk-color-feedback-danger-icon);
}

/* The sentence a screen reader hears; the signs and the squares are hidden from it. */
/* .smbk-diff-stat__hidden: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

/* ---- high contrast (forced colors) ---- */
/* The browser drops the fills: every square is outlined in the text color and the ones with lines are filled
   with it, so the balance still shows; the numbers keep their signs. */

@media (forced-colors: active) {
  .smbk-diff-stat__block {
    outline: var(--smbk-border-width-hairline) solid CanvasText;
    outline-offset: calc(-1 * var(--smbk-border-width-hairline));
  }

  .smbk-diff-stat__block--added,
  .smbk-diff-stat__block--removed {
    background: CanvasText;
  }
}
Tokens it reads
Diff stat tokens
Token and CSSValuePreviewFoundation
space.6--smbk-space-66pxSpacing
typography.code-sm--smbk-typography-code-sm-font-family--smbk-typography-code-sm-font-size--smbk-typography-code-sm-font-weight--smbk-typography-code-sm-line-height--smbk-typography-code-sm-letter-spacing--smbk-typography-code-sm-text-transformfontFamily: Geist Mono, Geist Mono Fallback, ui-monospace, monospacefontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.feedback.success.text-on-subtle--smbk-color-feedback-success-text-on-subtlelight: #016317dark: #8ddf90changes with themeColor
color.feedback.danger.text-on-subtle--smbk-color-feedback-danger-text-on-subtlelight: #a51d17dark: #febdb2changes with themeColor
space.2--smbk-space-22pxSpacing
size.8--smbk-size-88pxSize
radius.2--smbk-radius-22pxRadius
color.surface.track-subtle--smbk-color-surface-track-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
color.feedback.success.icon--smbk-color-feedback-success-iconlight: #037a1fdark: #54b85bchanges with themeColor
color.feedback.danger.icon--smbk-color-feedback-danger-iconlight: #cc0d0ddark: #fe7d6dchanges with themeColor
border-width.hairline--smbk-border-width-hairline1pxBorder width
Manifest
diff-stat.manifest.json
{
  "name": "diff-stat",
  "block": "smbk-diff-stat",
  "summary": "How much a change touched: the lines added and removed, such as \"+128 −12\", and a row of five small squares that shows the balance.",
  "adapt": "css",
  "axes": {},
  "flags": {},
  "parts": [
    "hidden",
    "added",
    "removed",
    "blocks",
    "block"
  ],
  "figmaNodeId": "7580:1825"
}