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.

Amara Okonkwo amara@binarymission.co.uk
On this thread
To
Labels
Seven recipients, five shown. Three attachments.
Try it without the mouse. Tab reaches the recipient row once, not seven times; the arrows move inside it and Delete removes. Remove the one you are standing on and the focus lands on the recipient that took its place — the browser’s own answer is <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>…
Push it past ninety-nine and watch the two halves separate. Clear it and the badge disappears and takes its description with it — a button still announcing “8 unread” after the inbox is empty is worse than one that never announced anything.

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.

The first one’s photograph is deliberately unreadable — that is the point of it. There is no broken-image mark, because the source is a state the component resolves rather than an 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”.
Type, and watch what does not happen. The list narrows on every keystroke and nobody changes colour, because the tint is a function of the name rather than a random choice. A random one turns this into a disco on every keypress — and changes again between a server render and the browser’s, which is a hydration mismatch.

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.

Design Open issues Assigned to me Amara Okonkwo Documentation Archived
A static chip is a <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.

Limit 4 Walk the limit down from six and watch it skip: at five the set draws all six anyway, because a counter reading +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.

Add people one at a time and read the second line. Up to five hidden, the counter names them; past that it gives the number alone, because a list of forty read aloud stops being an answer and becomes an obstruction between the reader and the rest of the page. The cut-off is a judgement rather than a standard, and it is written down where it can be argued with.

+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.