Display
The family that labels what is already there. Nothing on this page interrupts you, and nothing here is the thing you came for — a badge, a chip and an avatar exist to say something about a control, a filter or a person that the page is showing anyway.
Which is exactly what makes them the hardest small components in a library to get right. A red bubble on the corner of a bell works because it is on the bell, and proximity is the first thing to disappear when somebody cannot see the page. Every decision below is about that.
One composer, all five
A message being written, which is where these components actually live. Add a recipient, remove one, attach a file, change who is on the thread — and watch what the panel underneath reports, because it is read off the live document rather than out of the events.
<body>, which would throw you back to the top of the page once per removal.
What a screen reader is given here is not what you can see. The attachment count is announced as part of
the Attachments button rather than as a loose number beside it; the +2 on the thread
is announced as “2 more: Priya Raman, Tom Baird”; the sender’s initials are
never spelled out. Three different components, one rule — and the panel above reports both halves so
you can compare them.
Badge
A count or a state, attached to something else. Two bells below, drawn identically. One of them tells a screen reader how many notifications there are and the other does not, and you cannot see which.
<bmx-badge for="bell-right">
<bmx-badge><button>…
The right-hand bell is the same red circle in the same place, and to anybody using the page without sight
it is a button called “Notifications” beside an unexplained number. Proximity is the whole
message a badge carries and it is the first thing that does not survive into an accessibility tree. So
for writes aria-describedby on the control, appending rather than replacing
whatever hint it already had, and removes it again when the badge leaves. Wrapping cannot do this: a
description has to attach to the element that takes focus, not to a box drawn round it —
which is why the component warns in the console when you put a focusable control in its slot, as the
second bell does.
Avatar
An image that is usually not there. In most applications most people have never uploaded one, so the initials are how this component normally looks and the photograph is the exception.
src it hands to the browser and hopes for. Then initials; then, with no name at all, the
anonymous glyph, which is silent rather than announced as “image”.
AL is drawn and never read out: it is an abbreviation the layout asked for, exactly
as 99+ is on a badge, so the letters are aria-hidden and the accessible name is
the whole name. Two initials from two words and one from a single word — Prince as
PR reads as two names, and a name written in a script without spaces is one word by
definition. Graphemes rather than characters, so an astral letter is not cut in half; and
toLocaleUpperCase, because Turkish upper-cases i to İ and a reader
whose own initial comes back wrong notices immediately.
Chip
Three things that look almost identical and are not the same component wearing three hats. Hover or focus any of them and the panel below says what element it actually rendered, read out of its shadow root rather than asserted here in prose.
<span> with no tab stop, because a focus stop that does
nothing when pressed reads as a broken control and a row of twelve tags would be twelve of them. A
selectable one is a real <button> carrying aria-pressed, so it announces
its own state and answers to Space without a line of key handling in this library.
A cross that is not a button is the commonest defect in this pattern. Drawn as an icon inside the chip’s own text it cannot be reached, cannot be pressed, and says nothing about what it would do. Here it is a button named “Remove Amara Okonkwo” — named for what it removes, because a row of eight buttons all called “Remove” tells a screen-reader user which action they are on and nothing about which of the eight. And it is not a second tab stop: Delete or Backspace removes, which is what a keyboard user already does in every recipient field they have used.
Chip set
A row of chips that behaves as one thing. Everything it adds is keyboard behaviour, and every bit of it is invisible to anybody testing with a mouse — which is why the composer above is the real demonstration and this section is about the arithmetic underneath.
+1 takes the same room as the chip it replaced and
tells the reader less. That rule is one line of arithmetic and almost no design system starts with it.
The whole set is one element with a JSON items attribute and no script — which is what
a Razor page, a Blazor component, a Django template or an htmx swap can actually produce, and the reason
every list property in this library is read through a coercion layer instead of being documented as
“set it from JavaScript”. Slot your own bmx-chip children in when you want to
control each one; the two are alternatives, and the set works the same either way.
Avatar group
The overlapping stack that says who is on something — and what it says is not what it draws.
+2 is drawn and never read: the third truncation on this page, following the badge’s
99+ and the avatar’s initials, and given the same treatment because it is the same
thing — a compromise the layout asked for, made by a constraint that does not apply to anybody
listening. The faces themselves are decorative and the group carries the name;
eight avatars each announcing themselves is eight announcements to sit through before reaching the rest
of the row. The overlap is a logical margin, so the stack leans the way the reader reads and mirrors in
Arabic and Hebrew with no second rule.