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.

Invoices. Choose a row to open it.
InvoiceClientRaisedAmountStatus
The drawer keeps the list visible along one edge, which is the whole reason to reach for a drawer rather than a dialog: you are looking at a record in the context of the list. Delete one and the confirmation arrives on top of the drawer — two modals stacked, one scroll lock, released only when both have gone.
Close Delete invoice

Keep it Delete

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.

Rename project Long content
Cancel Save
I agree

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.

Delete workspace

Everything in it goes with it, and it cannot be undone.

Keep it Delete

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.

Filters Sections Share
Clear Apply

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.

Publish Makes this visible to everyone in the workspace. Archive Hides it from the list without deleting anything. You can undo this for thirty days, after which it is removed for good. Merge Only available once both records have an owner. A longer delay For a control whose meaning is already fairly obvious.
Tab along the row and each tooltip opens at once — a keyboard user has arrived deliberately, and a four-hundred-millisecond wait after a Tab press is lag with no purpose. Click a button and none of them opens: a tooltip appearing over what the click just did is the commonest way a good tooltip becomes an annoying one.

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.

Filters
Apply
Who is this?

Amara Okonkwo

Account manager since 2023, handling the Northwind and Contoso accounts. Open the full profile

The link in the second one is exactly why it is a popover. Put it in a tooltip and it becomes content a keyboard user can see and cannot reach — which is the commonest accessibility defect in commercial component suites, and the reason this library ships two components rather than one with a flag.

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.

Save Delete message Fail to save Upload five files
Send five at once and watch where the new ones go: beyond the ones already on screen, never at the corner. The obvious arrangement puts the newest nearest the edge, and it moves every toast already there — so the sentence you are halfway through slides away and the Undo you were reaching for is somewhere else by the time you press. Three are shown at a time; the rest wait their turn. Then rest the pointer anywhere in the stack, or Tab into an Undo. Every clock in that corner stops, not just the one under the pointer, and what was left of the time is kept rather than restarted. Switching to another browser tab does the same thing.
Draft saved. Message moved to Deleted. Undo We could not reach the server. Your changes are still here. accounts-q3.xlsx uploaded. payroll-august.csv uploaded. contract-northwind.pdf uploaded. site-photos.zip uploaded. handover-notes.docx uploaded.

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.

Run it Fail halfway
The readout is not decoration — it is the component's live region, printed on screen so you can see what a screen reader would hear. 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.
Two rings, and only one of them knows anything. A ring is the same component and the same arithmetic as a bar — an arc is a dash pattern round a circle — so everything below is true of both. The second ring and the bar under the amber one have no 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.

Load the card
Watch the card rather than the skeleton: nothing below it moves when the content lands, which is the whole reason to draw placeholders instead of nothing at all. Only the avatar carries a 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.