Pular para o conteúdo

Badges

components specs/components/badges.kmd

Small visual marker attached to another element, signalling presence of notifications, unread count, or status. Material parity (`/components/badges`). Covers small (dot) and large (numeric) variants, anchoring rules, and overflow formatting.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Badges

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/badges.

2 variants

VariantSizeContentUse
Small (dot)6 px circleNonePresence-only marker
Large (numeric)16 px height1-3 digits or textCount or short label

Pick variant by need: dot when count is irrelevant ("you have new messages"); numeric when count itself is information ("3 unread").

Anatomy (large badge on icon button)

        ┌───┐
   ┌─── │ 3 │   ← badge (top-right anchor)
   │    └───┘
   ▼
  ┌─────┐
  │  ✉  │      ← host (icon button)
  └─────┘
  • Dot diameter: 6 px
  • Large height: 16 px
  • Large min-width: 16 px (square at 1 digit); auto-expands
  • Large padding: 4 px horizontal
  • Text: label-small (11/16, weight 600)
  • Background: error color role by default
  • Foreground: on-error
  • Border: 1 px surface background (separation from host)

R1 — Anchor position

AnchorOffsetUse
Top-right (default)-2 px x, -2 px yIcon buttons, list items, avatar
Top-left+2 px x, -2 px yRTL mirror of top-right
InlineAfter text, 4 px gapInside list row, after label

Never anchor below or center — badge always sits at top corner OR inline after text.

R2 — Numeric formatting

CountDisplay
0Hide badge entirely (don't show "0")
1-9Single digit 3
10-99Two digits 42
100-999Three digits 512
1000+999+ (cap)

Custom cap allowed per surface: 99+ for notifications, 9+ for inbox folders. Document per surface; pick one.

R3 — Color roles

MeaningBackgroundForeground
Error / urgenterroron-error
Warningwarning (extended)on-warning
Info / neutral countprimaryon-primary
Success / newsuccess (extended)on-success
Status — onlinesuccess dot
Status — awaywarning dot
Status — offlineoutline dot

Default to error for unread/attention counts (matches Material default and user expectation).

R4 — Animation

  • Appear: fade-in + scale 0 → 1 (motion-fast, ~150 ms)
  • Count change: cross-fade old → new digit (motion-fast)
  • Disappear: fade-out (motion-fast)
  • No bouncing / pulsing — distracting and ignored by users after habituation
  • Reduced motion: instant appear/disappear, no animation

R5 — Accessibility

  • Badge content is part of host's accessible name:
    • Icon button: aria-label="Inbox, 3 unread" (concat, not separate)
    • Or use aria-describedby referencing a hidden span with badge text
  • Dot-only badge: include presence in host label ("Profile, online")
  • Color alone never carries meaning — always pair with text or icon-shape difference
  • Hit target: badge is decorative; host's hit target stays unchanged

R6 — Hit target

Badge is non-interactive. Tapping it triggers the host's action. Don't add a separate click handler on the badge — defeats user expectation and confuses screen readers.

R7 — Density

DensityDotLargePadding
Compact4 px14 px3 px
Default6 px16 px4 px
Comfortable8 px20 px6 px

Inherits surface density from customization.kmd.

R8 — Per-preset variation

PresetVisual
material3Rounded pill, no border, error-color default
material2Circle (numeric), filled background
ios_cupertinoRed filled circle, white text, slight gradient
gnomeRectangle with 4 px radius, accent color
windows_11Pill shape, system accent color, subtle outline
brutalistSharp square, thick 2 px border, no animation
terminal_classicASCII suffix (3) next to icon, no shape

R9 — Forbidden patterns

  • ❌ Showing 0 — hide badge entirely instead
  • ❌ Animating count change with bounce/pulse (distracting)
  • ❌ Putting interactive elements (buttons, links) inside a badge
  • ❌ Using badge to display arbitrary long text — use chip instead
  • ❌ Color-only differentiation (status dot WITHOUT label text)
  • ❌ Anchoring at bottom or center of host
  • ❌ Stacking multiple badges on the same host
  • themes/color-roles.kmderror / success / warning token sources
  • themes/typography.kmdlabel-small for numeric text
  • foundations/elements.kmd — Marker family
  • components/chips.kmd — sibling for longer textual labels with interaction

Requirements (testable)

Requirement: Numeric badge formatting and overflow cap {#req-badges-numeric-formatting}

A numeric badge SHALL render 1-3 digits verbatim for counts 1 through 999, SHALL cap counts of 1000 or greater to the surface's overflow token (default 999+), and SHALL NOT render at all when the count is 0.

Scenario: Single-digit count renders verbatim

  • GIVEN a large numeric badge bound to an unread count
  • WHEN the count is set to 3
  • THEN the badge is visible
  • AND its text content is exactly 3

Scenario: Count over the cap collapses to overflow token

  • GIVEN a large numeric badge using the default overflow cap
  • WHEN the count is set to 1000
  • THEN the badge text content is exactly 999+

Scenario: Zero count hides the badge

  • GIVEN a large numeric badge currently showing 5
  • WHEN the count changes to 0
  • THEN the badge is not rendered (no 0 is displayed)

Requirement: Anchor position {#req-badges-anchor}

A badge SHALL be anchored only at a top corner of its host (top-right by default, top-left for RTL mirror) or inline after text, and SHALL NOT be anchored at the bottom or center of the host.

Scenario: Default anchor is top-right

  • GIVEN a large badge attached to an icon button host
  • WHEN the badge is rendered with default anchoring
  • THEN the badge is positioned at the top-right of the host with a -2 px x and -2 px y offset

Scenario: Bottom and center anchors are rejected

  • GIVEN a badge attached to a host
  • WHEN an anchor of bottom or center is requested
  • THEN the badge does not render at that position (only top-corner or inline anchors are honored)

Requirement: Status meaning never conveyed by color alone {#req-badges-a11y}

A badge SHALL expose its content as part of the host's accessible name, and status information SHALL NOT be carried by color alone — a status dot SHALL always be paired with label text or an icon-shape difference.

Scenario: Numeric badge folds into host accessible name

  • GIVEN an icon button host labelled "Inbox" with a numeric badge showing 3
  • WHEN a screen reader computes the host's accessible name
  • THEN the accessible name includes the badge count (e.g. Inbox, 3 unread) as a single concatenated name

Scenario: Status dot requires accompanying text

  • GIVEN a dot-only badge indicating an online presence status
  • WHEN the badge is rendered
  • THEN the presence is also present in the host's accessible label (e.g. Profile, online)
  • AND the meaning is not conveyed by the dot color alone

Requirement: Badge is non-interactive {#req-badges-hit-target}

A badge SHALL be decorative and non-interactive: it SHALL NOT register its own click handler, SHALL NOT alter the host's hit target, and SHALL NOT contain interactive elements such as buttons or links.

Scenario: Tapping the badge triggers the host action

  • GIVEN an icon button host with a numeric badge anchored at its top-right
  • WHEN the pointer taps the badge region
  • THEN the host's action is invoked
  • AND no separate badge click handler fires

Requirement: Motion respects reduced-motion preference {#req-badges-animation}

A badge SHALL animate appearance, count change, and disappearance with the motion-fast fade/scale/cross-fade transitions and SHALL NOT bounce or pulse; when the user prefers reduced motion, the badge SHALL appear and disappear instantly with no animation.

Scenario: Reduced motion disables badge animation

  • GIVEN a badge on a host with the reduced-motion preference enabled
  • WHEN the badge appears in response to a new count
  • THEN the badge becomes visible instantly with no fade or scale animation

Scenario: Count change never bounces

  • GIVEN a large numeric badge showing 3 with default motion
  • WHEN the count changes to 4
  • THEN the digit updates via a cross-fade
  • BUT no bounce or pulse animation is played

Requirement: Variant selection between dot and numeric {#req-badges-variants}

A badge SHALL be rendered as either the small dot variant (6 px circle, no content) for presence-only markers or the large numeric variant (16 px height, 1-3 digits or short text) for counts, and the large variant SHALL NOT be used to display arbitrary long text.

Scenario: Dot variant carries no content

  • GIVEN a badge configured as the small dot variant
  • WHEN the badge is rendered at default density
  • THEN it is a 6 px circle
  • AND it contains no text content

Scenario: Long text is refused by the numeric variant

  • GIVEN a large badge variant
  • WHEN it is asked to display an arbitrary long text string
  • THEN the badge does not render the long text (a chip is used instead)

Referências