Skip to content

Communication

Badge

A dot or a short count on an icon, telling people something new is there.

Examples

Badges

A dot, a count (shown as 999+ past max) or up to four characters, on the top-end corner of its anchor. The badge text joins the accessible name of the item around it.

mail New notification notifications 8 inbox 999+ shopping_bag New
React HTML
import { Badge, BadgedBox, Icon } from '@kyralabs/m3exp-web/react';

export default function Badges() {
  return (
    <>
      <BadgedBox badge={<Badge />}>
        <Icon name="mail" label="Mail" />
      </BadgedBox>
      <BadgedBox badge={<Badge count={8} />}>
        <Icon name="notifications" label="Notifications" />
      </BadgedBox>
      <BadgedBox badge={<Badge count={1200} />}>
        <Icon name="inbox" label="Inbox" />
      </BadgedBox>
      <BadgedBox badge={<Badge label="New">New</Badge>}>
        <Icon name="shopping_bag" label="Shop" />
      </BadgedBox>
    </>
  );
}
<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Mail">mail</span>
  <span class="m3e-badge"><span class="m3e-badge__label">New notification</span></span>
</span>
<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Notifications">notifications</span>
  <span class="m3e-badge m3e-badge--large">
    <span class="m3e-badge__text" aria-hidden="true">8</span>
    <span class="m3e-badge__label">8</span>
  </span>
</span>
<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Inbox">inbox</span>
  <span class="m3e-badge m3e-badge--large">
    <span class="m3e-badge__text" aria-hidden="true">999+</span>
    <span class="m3e-badge__label">999+</span>
  </span>
</span>
<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Shop">shopping_bag</span>
  <span class="m3e-badge m3e-badge--large">
    <span class="m3e-badge__text" aria-hidden="true">New</span>
    <span class="m3e-badge__label">New</span>
  </span>
</span>

Run the HTML version (CSS classes only).

API

React

<Badge>

Material Design badge (Compose Badge, COMPONENT_SPEC §34): the dot, or a 16dp pill with a count or short text. Place it on an anchor with <BadgedBox>. Renders the m3e-css .m3e-badge contract: the visible text is aria-hidden and a visually hidden label is read with the item the badge is in, so name that item from its content (not aria-label, which replaces it).

Prop Type Default Description
count number | undefined A number to show: written as is up to max, then <max>+ (999+). Without count and children the badge is the 6dp dot.
max number | undefined The largest count shown as is (m3.material.io: "+" for quantities over 999).
children ReactNode Other short content instead of a count (up to four characters).
label string | undefined The visually hidden text that joins the accessible name of the item around the badge (owner decision M9 #7). Defaults to "New notification" for the dot, the shown count, or the children when they are text. '' leaves the badge out of the name.

Also accepts HTML attributes not listed here.

<BadgedBox>

Compose BadgedBox: the anchor's size, with the badge on its top-end corner (the dot 6dp inside it; a badge with content 12dp before the end and 2dp above the top). Renders the m3e-css .m3e-badged-box contract: the anchor, then the badge, so the badge is read after it.

Prop Type Default Description
badge required ReactNode The badge, typically a <Badge>.
children ReactNode The anchor the badge is placed on, typically an <Icon>.

Also accepts HTML attributes not listed here.

CSS

badge.css

@import '@kyralabs/m3e-css/badge.css';

Badge (COMPONENT_SPEC §34): a 6dp dot, or a 16dp pill with up to four characters ("999+"), placed on the top-end corner of an anchor by a badged box. No JavaScript needed; m3e-react's <Badge count max> writes the "999+" text (owner decision M9 #6).

<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Inbox">inbox</span>
  <span class="m3e-badge m3e-badge--large">
    <span class="m3e-badge__text" aria-hidden="true">999+</span>
    <span class="m3e-badge__label">999+</span>
  </span>
</span>

<span class="m3e-badged-box">
  <span class="m3e-icon" role="img" aria-label="Mail">mail</span>
  <span class="m3e-badge"><span class="m3e-badge__label">New notification</span></span>
</span>
  • .m3e-badge alone is the dot (Compose Badge without content); --large is the badge with
content: the visible text goes in __text (aria-hidden), and __label holds the visually hidden
text that joins the accessible name of the item around it (owner decision M9 #7: "New
notification" for the dot, the count otherwise, or the app's own words). Name the item from
its content (an Icon with a label, visible text): an aria-label on the item replaces its
content, the badge included.
  • .m3e-badged-box (Compose BadgedBox) is the anchor's size, content centered; its direct
.m3e-badge child is placed relative to its top-end corner, mirrored in RTL (placeRelative).
  • Bounds (Compose Modifier.badgeBounds(): BadgeEndRuler / BadgeTopRuler): a component that
keeps badges inside itself (navigation items, tabs) sets --_m3e-badged-box-bounds-end and
--_m3e-badged-box-bounds-top on the box or an ancestor: how far its end and top edges lie
beyond the box's end and top edges (lengths ≥ 0, both set). The badge then moves back inside
(x = min(desired, end − badge width), y = max(desired, top)). Unset, nothing clamps (a
BadgedBox outside a badgeBounds() layout: the rulers default to ±infinity).
15 custom properties it reads
Custom property Default
--m3e-comp-badge-container-color var(--m3e-sys-color-error)
--m3e-comp-badge-large-label-text-color var(--m3e-sys-color-on-error)
--m3e-comp-badge-large-label-text-font var(--m3e-sys-typescale-label-small-font)
--m3e-comp-badge-large-label-text-line-height var(--m3e-sys-typescale-label-small-line-height)
--m3e-comp-badge-large-label-text-size var(--m3e-sys-typescale-label-small-size)
--m3e-comp-badge-large-label-text-tracking var(--m3e-sys-typescale-label-small-tracking)
--m3e-comp-badge-large-label-text-weight var(--m3e-sys-typescale-label-small-weight)
--m3e-comp-badge-large-offset-horizontal 12px
--m3e-comp-badge-large-offset-vertical 14px
--m3e-comp-badge-large-padding-horizontal 4px
--m3e-comp-badge-large-shape var(--m3e-sys-shape-corner-full)
--m3e-comp-badge-large-size 16px
--m3e-comp-badge-offset 6px
--m3e-comp-badge-shape var(--m3e-sys-shape-corner-full)
--m3e-comp-badge-size 6px

Accessibility

the badge holds visually hidden text (__label) that joins the accessible name of the item it is in: "New notification" for the dot (BadgeSamples.kt), the count otherwise, or an overriding label; the visible text (__text) is aria-hidden (owner decision M9 #7). Name the item from its content (or with aria-labelledby that includes the badge), not aria-label.

Every interactive part has a target of at least 48 × 48 px, a visible focus ring for keyboard focus only, and works under forced-colors and prefers-reduced-motion.

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §34 and the m3e-css sources.

BadgeTokens.kt@19991f2c plus the Badge.kt values Compose renders (components/badge-defaults.css). Small (no content): a 6dp dot, full corners, BadgeDefaults.containerColor (error). Large (with content): at least 16 × 16dp, full corners, 4dp horizontal padding, label small in on-error (contentColorFor(error)), the same error container (Compose never reads LargeColor).

badge.css sources

(compose/material3 @ 19991f2c):

  • BadgeTokens.kt: Size 6dp, LargeSize 16dp, Shape / LargeShape CornerFull, LargeLabelTextFont LabelSmall, LargeLabelTextColor OnError.
  • Badge.kt: Badge (L151-189: defaultMinSize(size, size), background(containerColor, shape), 4dp horizontal padding with content, centered; contentColorFor(BadgeDefaults.containerColor = BadgeTokens.Color) = onError), BadgedBox (L64-130: anchor size, badge measured with a loose height; hasContent = badge width > BadgeTokens.Size; x = anchor width − 12 or 6dp, y = −badge height + 14 or 6dp; clamped by the rulers). No motion: badges appear and disappear at once.
  • Literals: components/badge-defaults.css (PARITY.md).