<bmx-badge>
A count or a state, attached to something else.
8 properties · 0 events · 0 methods · 2 parts
Example
99+ is a truncation the layout asked for, and nobody listening to the page has
that constraint — so the drawn text and the announced text are two different strings.
show-zero is there for the scoreboard case.
for rather than wrapping it. A description has to attach to
the element that takes focus, not to a box drawn round it — so a badge that encloses a button cannot describe
it. The slot is for a picture: an avatar, an icon, anything the user cannot focus. Put a focusable control in it and
the component says so once in the console.
Show markup
<div class="row" style="gap: 2.5rem; align-items: center">
<span style="position: relative; display: inline-flex">
<button id="ex-inbox" type="button" style="padding: 0.5rem 0.9rem">Inbox</button>
<bmx-badge for="ex-inbox" value="127" label="unread messages" tone="danger"
style="position: absolute; inset-block-start: 0; inset-inline-end: 0; transform: translate(35%, -35%)"></bmx-badge>
</span>
<bmx-badge value="Beta" tone="info"></bmx-badge>
<bmx-badge value="Renewed" tone="success"></bmx-badge>
<bmx-badge value="Action needed" tone="warning"></bmx-badge>
<bmx-badge dot tone="success" label="Online"></bmx-badge>
</div>
<div class="row" style="margin-block-start: 1.5rem">
<span class="note">
The bubble on the left draws <strong>99+</strong>; a screen reader is told <em>“Inbox, 127 unread
messages”</em>. <code>99+</code> is a truncation the layout asked for, and nobody listening to the page has
that constraint — so the drawn text and the announced text are two different strings.
</span>
</div>
<div class="row" style="margin-block-start: 1rem; gap: 1.5rem; align-items: center">
<bmx-badge value="3">
<bmx-avatar name="Amara Okonkwo" decorative></bmx-avatar>
</bmx-badge>
<bmx-badge value="0" label="unread">
<bmx-avatar name="Grace Hopper" decorative></bmx-avatar>
</bmx-badge>
<span class="note" style="flex: 1 1 18rem">
A count of zero draws nothing, because a badge that appears when nothing needs attention teaches the reader to stop
looking at it. <code>show-zero</code> is there for the scoreboard case.
</span>
</div>
<div class="row" style="margin-block-start: 1rem">
<span class="note">
<strong>To badge a control, use <code>for</code> rather than wrapping it.</strong> A description has to attach to
the element that takes focus, not to a box drawn round it — so a badge that encloses a button cannot describe
it. The slot is for a picture: an avatar, an icon, anything the user cannot focus. Put a focusable control in it and
the component says so once in the console.
</span>
</div>
<!-- a status, on its own -->
<bmx-badge value="Beta" tone="info"></bmx-badge>
<!-- a count, on a control the user can focus -->
<button id="inbox">Inbox</button>
<bmx-badge for="inbox" value="127" label="unread messages"></bmx-badge>
<!-- a count, over a picture -->
<bmx-badge value="3" label="new photographs">
<bmx-avatar name="Ada Lovelace" decorative></bmx-avatar>
</bmx-badge>
THE TWO THINGS THIS COMPONENT HAS TO GET RIGHT
One: the count has to reach the control, not float beside it. A red
bubble on the corner of a bell tells a sighted user there are three
notifications because it is on the bell. Proximity is the whole message,
and proximity is exactly what does not survive into an accessibility tree: a
screen reader meets a button called "Inbox" and then, separately, the number
127, with nothing to say the two are related. So a badge on a focusable
control is attached with for, which writes aria-describedby on it, and
the control is then announced as "Inbox, 127 unread messages".
Two: 99+ is a truncation and is never spoken. It exists because the
bubble has room for two digits. Nobody listening to the page has that
constraint. The drawn text and the announced text are two different strings
for this reason; see src/core/badge.ts.
WHY for RATHER THAN WRAPPING THE BUTTON
Wrapping is what the visual design looks like, so it is the first thing
anybody reaches for - and a description has to be attached to the element
that takes focus, which is the button inside, not the box drawn round it.
A badge that wraps a button therefore cannot describe it without reaching
into markup it does not own. for puts the reference on the right element
and leaves the positioning to CSS, where it belongs.
The slot is still there and is right for a picture: an avatar with a
presence dot, an icon with a count, anything the user cannot focus. If a
focusable element is slotted in without a for, the component says so once
in the console - it is the same class of mistake as an X on a chip that is
not a button, and it is invisible until somebody tries the page without a
mouse.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
dot |
dot |
boolean |
false |
Draw a mark with no number in it. Then label is not optional: a dot with nothing to say is a decoration, and this component makes it one - aria-hidden, out of the tree entirely. |
for |
for |
string |
— | The id of the control this badge belongs to. The badge writes aria-describedby on it, so the count is announced as part of that control rather than as a loose number beside it. The control must be in the same document or shadow root as the badge, which is what an ID reference means everywhere in the platform. |
label |
label |
string |
— | What is being counted: unread messages, items in your basket. It reaches the announcement and never the bubble. Without it a screen reader is given a bare number, which is the accessible equivalent of a red dot with no explanation. |
max |
max |
number |
99 |
The largest count drawn in full. Past it the bubble shows 99+, and the exact number is still announced. |
placement |
placement |
BmxBadgePlacement |
'top-end' |
Where the bubble sits over slotted content. |
showZero |
show-zero |
boolean |
false |
Draw a count of zero instead of nothing. |
tone |
tone |
BmxBadgeTone |
'neutral' |
What the badge is reporting. Meaning, not colour: the colours are custom properties. |
value |
value |
number | string |
— | The count, or a short word. A string of digits is treated as the count it is, because value="3" from a Razor page or an htmx swap is the commonest way this is ever set. |
Slots
| Slot | Description |
|---|---|
(default) |
Something for the bubble to sit on. Not a focusable control - see above. |
CSS shadow parts
| Part | Description |
|---|---|
anchor |
The box the slotted content sits in. |
badge |
The bubble. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-badge-background |
The bubble's fill, overriding the tone. |
--bmx-badge-border |
The ring that separates the bubble from what it sits on. |
--bmx-badge-color |
The text on it. |
--bmx-badge-dot-size |
The diameter of the dot form. |
--bmx-badge-font-size |
The digits. |
--bmx-badge-font-weight |
How heavy they are. |
--bmx-badge-height |
The bubble's height, and its diameter when it holds one digit. |
--bmx-badge-offset |
How far the bubble hangs off the corner of slotted content. |
--bmx-badge-padding-inline |
The room either side of a longer count or a word. |
--bmx-badge-radius |
The corner radius. A pill by default. |
--bmx-badge-tone-danger |
The fill for tone="danger". |
--bmx-badge-tone-info |
The fill for tone="info". |
--bmx-badge-tone-neutral |
The fill for tone="neutral". |
--bmx-badge-tone-success |
The fill for tone="success". |
--bmx-badge-tone-warning |
The fill for tone="warning". |
--bmx-badge-tone-warning-text |
The text on the warning fill, which is dark rather than white. |