# 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.

Block: `smbk-diff-stat`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|

Parts: `smbk-diff-stat__hidden`, `smbk-diff-stat__added`, `smbk-diff-stat__removed`, `smbk-diff-stat__blocks`, `smbk-diff-stat__block`

## Markup

```html
<span class="smbk-diff-stat">
  <span class="smbk-diff-stat__hidden">128 lines added, 12 lines removed</span>
  <span class="smbk-diff-stat__added" aria-hidden="true">+128</span>
  <span class="smbk-diff-stat__removed" aria-hidden="true">−12</span>
  <span class="smbk-diff-stat__blocks" aria-hidden="true">
    <span class="smbk-diff-stat__block smbk-diff-stat__block--added"></span>
    <span class="smbk-diff-stat__block smbk-diff-stat__block--added"></span>
    <span class="smbk-diff-stat__block smbk-diff-stat__block--added"></span>
    <span class="smbk-diff-stat__block smbk-diff-stat__block--added"></span>
    <span class="smbk-diff-stat__block smbk-diff-stat__block--removed"></span>
  </span>
</span>
```

## React

```tsx
import { DiffStat } from './design-system/components/diff-stat/diff-stat.js';

<DiffStat added={128} removed={12} />
```

## Stylesheet

```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

```json
{
  "space": {
    "2": {
      "$type": "dimension",
      "$value": "2px",
      "$description": "Raw space step, 2 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "6": {
      "$type": "dimension",
      "$value": "6px",
      "$description": "Raw space step, 6 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    }
  },
  "typography": {
    "code-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist Mono, Geist Mono Fallback, ui-monospace, monospace",
        "fontSize": "12px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      },
      "$description": "Code SM: keys (Kbd) and short code in a line of text. Geist Mono."
    }
  },
  "color": {
    "feedback": {
      "success": {
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.green.700}}",
          "$description": "Success messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.green.600}}",
          "$description": "The success tone’s own icon (a check), so the tone is never shown by colour alone."
        }
      },
      "danger": {
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.red.700}}",
          "$description": "Danger messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.red.600}}",
          "$description": "The danger tone’s own icon (an alert sign), so the tone is never shown by colour alone."
        }
      }
    },
    "surface": {
      "track-subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "The empty part of a progress track: light enough that the fill stands out by lightness, not only by hue."
      }
    }
  },
  "size": {
    "8": {
      "$type": "dimension",
      "$value": "8px",
      "$description": "Raw size step, 8 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    }
  },
  "radius": {
    "2": {
      "$type": "dimension",
      "$value": "2px",
      "$description": "Raw corner step, 2 px. Semantic corners (radius/control, container) point here."
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  }
}
```

## 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.

## 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.

## 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](stat.md) with its trend.
- For a share of a total. Use a [meter](meter.md).

## 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.

## 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.
