Communication
Badge
A dot or a short count on an icon, telling people something new is there.
- COMPONENT_SPEC §34
- CSS · React
- Open in the playground
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.
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).