Feedback
The family that interrupts, and the one that asks you to wait. Dialog and
Drawer are the first two, and they are the same component wearing different clothes: a
native <dialog> in the browser's top layer, opened modally, so everything behind it is
genuinely inert rather than merely covered up.
The components that label rather than interrupt — badge, chip, avatar and their two sets — are on Display.
One list, one drawer, one confirmation
The pattern every application with a table of records ends up needing. Open a row to see it beside the list it came from; delete it and answer for it. Both panels are real, and so is the row that disappears.
| Invoice | Client | Raised | Amount | Status |
|---|
Watch where focus goes. Opening a row moves it into the drawer; the confirmation takes it from the drawer and
gives it back on the way out; and when both close, it lands on the row you started from — or, if that row has
just been deleted, on the table itself rather than nowhere. None of that is written on this page: a modal
<dialog> makes everything outside it inert, so the trap is the platform's, and
returning focus is the component's.
Dialog
The component on its own. A form that does not deserve a page of its own, and a panel whose content is longer than the window — dismissed by Escape, by the backdrop or by the close button, each reported with its own reason, because an application that autosaves has to tell a cancelled form from a saved one.
Open the second one and try to scroll the page behind it. It does not move — and when the dialog closes you are
exactly where you were. That is not free: the page behind a modal <dialog> scrolls to the
wheel, so the lock is the component's, written the way that survives iOS and counted so that a confirmation over
a form releases it only once both have gone.
A dialog that must be answered
No close button, no Escape, no backdrop — for the question that has to have an answer.
Everything in it goes with it, and it cannot be undone.
dismissible="false" restrains the user, never your application: both buttons above close it by
calling closeDialog(). A modal that survived its own submit button would be a support call.
Drawer
The same machinery, arriving from an edge instead of the middle — filters beside a table, a record beside the list it came from, navigation on a narrow screen.
A navigation drawer sits on the leading edge, where a page's own navigation would be.
A sheet from the bottom edge is the shape a phone expects for a share or an action list.
The edges are logical. end opens from the right here and from the left in Arabic, with nothing to
change in between — try it below — while top and bottom do
not mirror, because the block direction runs the same way in every locale this library supports.
The same drawer, right to left
One attribute on a wrapper, and nothing else changes.
يفتح هذا الدرج من الحافة الأمامية.
Both drawers say edge="end". The one above opens from the right, this one from the left, and the
panel slides in from the edge it rests against rather than from the one it would have in English.
Tooltip
A few words about the control beside it. Hover one, then move the pointer into the tooltip — it stays. Press Escape and it goes without the pointer moving, and does not spring back until the pointer has been somewhere else. Watch the panel underneath: it reads where the focus is straight off the live document, and the popover section below prints the same line with the opposite answer. That difference is the whole reason these are two components.
WCAG 2.2 SC 1.4.13 is three rules and this component keeps all three. Dismissible —
Escape hides it where it stands. Hoverable — the pointer can travel into it, which is
what the close delay is for; it is the time the pointer takes to cross the gap, not a fade-out, and setting
it to zero is how the rule is usually broken. Persistent — it never disappears on a timer
of its own, because a tooltip that vanishes after four seconds is one a slow reader cannot finish. The
rules live in src/core/hover-intent.ts, where they are tested over every ordering a pointer
can produce rather than the two anybody thinks to try by hand.
Popover
The other half of the pair, and a different component on purpose. A tooltip describes; a popover contains. That is not a turn of phrase — it is a measurable fact, and the panel underneath reads it off the live document. Open one and compare it with the tooltip’s.
Amara Okonkwo
Account manager since 2023, handling the Northwind and Contoso accounts. Open the full profile
It is not a dialog. A dialog is modal: the page goes inert, the scroll locks, and the user must deal with it. This is non-modal — the page behind stays usable, nothing is locked, and it closes the moment attention moves on. That last part is what a non-modal panel has to get right: one that stays open behind the user's focus is a panel covering whatever they Tab to next. The event says why it closed, too, because a panel dismissed with Escape has been rejected and one closed by its Apply button has been accepted, and treating them the same is how a filter panel applies a filter somebody backed out of.
Toast
A short message about something that has already happened. Almost none of what makes one good is visible with a single toast on an empty screen — it is all in what happens when there are several, or when somebody is actually reading.
The last button is an alert, and the difference is not the colour. A status toast
waits for a gap in whatever a screen reader is saying; an alert cuts across it. That is right
exactly when not knowing is worse than being interrupted — and it is why an alert here stays until it
is dismissed, because interrupting somebody and then removing the message unread is the worst of both. It
is the same judgement that makes the tooltip and the popover two components rather than one, wearing
different clothes. If the user has to decide something, though, none of this is the right
component: that is a dialog.
Progress
A bar or a ring, with a value or without one. The drawing is one declaration; everything worth having is around it.
role="progressbar" announces nothing by
itself, so a component that only maintains aria-valuenow is one whose progress can be polled
and cannot be heard. Speak every value instead and it reads a hundred integers over the top of whatever
the user was doing. So it speaks at milestones: every twenty per cent, and the finish.
value, and that is a
state rather than a missing input: work is happening and nobody is pretending to know
how much is left. aria-valuenow is omitted entirely, which is how ARIA spells it — not
aria-valuenow="0", which claims a position and is read as "nothing has happened yet".
Turn on Reduce motion in your operating system and look again. The indeterminate two stop travelling and start breathing — a slow, small change in opacity, nothing crossing the screen. That is deliberate rather than a fallback: an indeterminate indicator is motion by definition, and one switched off entirely is a static grey shape that says the page has hung, which is worst for exactly the person who asked for less motion.
Skeleton
The grey shapes that hold a layout together while its content is on its way. A separate component from progress, and the reason is not the drawing.
Handles the Northwind and Contoso accounts. Joined in 2023, based in Manchester, and the person to ask about anything that touches the renewals pipeline.
label — one announcement per region, and none on the rest, or a card of twelve
placeholders reads out as twelve grey rectangles.
It is not a third shape on <bmx-progress>, and the reason is the same
one that makes the tooltip and the popover two components. A progress indicator reports a position: it has
a role, a range and a value, and it is worth announcing. A skeleton reports nothing — it is a picture
of a paragraph that has not arrived, and to a screen reader it should not exist. Folded in as a shape it
would be a progressbar with no value that must never be announced, which is a contradiction
you would have to paper over on every render.
The other half of the family
Badge, chip, avatar and the two sets that hold them are on their own page. They belong to the same tier and answer a different question: these components interrupt you, and those ones label what is already in front of you.