Notice
The shell's one persistent surface for a result with no visible home: after navigation, from a closed dialog form, for a confirm-only action such as a copied link. One notice at a time, fixed top-centre, staying until the user dismisses it or the app replaces it, announced without moving focus, and always linked back to its subject.
Where a result belongs
Feedback appears where the user's attention already is and stays until they have seen it (ADR 0008). Homes for a result, in order: the affected item, which appears, updates, or shows a failed state with a retry; the acting surface, meaning the form's result slot below its actions row; and the notice, last, only when neither is on screen. A notice about an item the user can see is a bug — the item state is the right home.
This is not a toast. Nothing auto-dismisses, nothing stacks, there is no action row, and every notice carries a link back to its subject. There is no size, no children, and no timer to configure, because those are the properties the ban is written against. An anchored mode, positioned against the trigger that produced the result, is deferred until a screen needs one (#145); today every notice is fixed top-centre.
Parts
Two exports. NoticeProvider is mounted once in the app shell: it renders the two live regions, then the notice outlet, then your children, and holds the single notice. useNotice() hands back show, which takes a variant, a title, an optional description, a required subject element, and an optional onDismiss, and a bare dismiss that closes whatever is showing with the app reason. It throws outside a provider.
The card is an Alert at role="presentation", so the variant icon, the title, the description, and the close button all come from alert and the announcement comes only from the provider's regions. The subject is yours: an anchor or a button — a router Link, or a button that reopens the dialog with the data the user submitted. The registry places it under the description and paints the link styling; you keep the focus ring, so put offsetFocusRingGeometry from the interaction lib on it.
Variants
The four alert variants, unchanged: info, success, warning, and error. The icon carries the variant on a neutral card, and error is the one that interrupts a screen reader. Each preview below is a frame of its own, so the card lands inside the panel instead of at the top of this page.
Replacement
One notice at a time. A second show swaps the content in place — no exit, no enter, no movement — and calls the previous notice's onDismiss with the replaced reason, so the app that owned it knows it is gone. The new text is announced again even when it reads the same as the old.
Dismissing from the handle
show returns a handle with one method, dismiss, so a page can take its own notice down once it renders the subject the notice points at. The handle goes quiet as soon as that notice has been replaced: a stale handle cannot kill a newer, unrelated notice. Dismissing from the handle reports the app reason; dismissing from the close button reports user.
A result after a closed dialog
A dialog form closes on submit and leaves its result slot empty, so a server error has nowhere inline to land. It becomes a notice whose subject reopens the dialog with the data the user typed, which is the case ADR 0008 was written around. Save the trip below and the planner fails on purpose.
Placement and motion
The card is fixed 16px from the top, centred, at most max-w-md wide, and full width minus 16px each side on a narrow viewport. It sits on z-50, the floating layer, and overlays the header: a reserved strip would displace content, which ADR 0008 bans. It enters and leaves on opacity and an 8px vertical travel, spring-settle both ways through the notice's own AnimatePresence, so nothing the app renders has to know about motion. The card is an Alert with animateOpen off, so alert's own height animation stays out of the way and the travel is the only thing moving. Replacement animates nothing, because a card that re-enters on every new result reads as a stack arriving.
Accessibility
The provider mounts two empty visually hidden live regions before your content: role="status" for info, success, and warning, and role="alert" for error. On every show the matching region is emptied and written on the next frame with the title, the description, and the subject's text, so the same words are read again when the same result happens twice. Dismissal announces nothing.
Focus never moves to the notice: the button the user pressed keeps it. Because the outlet sits before your children in the DOM, Tab reaches the subject and then the dismiss button before page content, which matches where the card visually sits. Escape does nothing — that key stays with dialogs — and a route change clears nothing, because the registry knows no router. Focus return after a dismissal is the app's job, as it is with alert.
Colour comes from alert, so the icon, the title, and the description carry the ratios measured on that page. The subject is underlined in --card-foreground, 18.3:1 light and 15.5:1 dark, which keeps it apart from the description without relying on colour, and takes --indicator on hover and press: 4.7:1 light and 5.1:1 dark on the card, the accordion trigger's hover colour rather than --primary, which would not clear AA for text.