Design System

Design System

Non-modal Dialog

Surfaces

Open in Storybook: Non-modal Dialog (opens in a new tab)

On this page

Overview

A native dialog for a box in the page. Nothing has to stop: the person can ignore it and keep working. It has no ::backdrop, no inert page, no focus trap, no Esc and no top layer, unless it is a popover. The element gives the role, the name, form method="dialog" and returnValue; focus, stacking and closing are the component’s.

The rule. Never showModal() and never aria-modal. Markup open at load: focus stays put. Raised at runtime: focus goes into it, to the heading; focus entering is what assistive tech speaks. No trap, no wrap. A visible, named dismiss button inside, and every exit leaves focus on a real control.

Opened by the open attribute, show() or the Popover API (<dialog popover>). showModal() is the Modal Dialog.

Anatomy

Open each part to see what makes it. The one you are on lights up in the figure.

  1. Page

    Nothing inert · “Cookie settings” link

    The person keeps working. When the bar closes, focus lands on the link.

    <main>…</main>  <!-- not inert, no aria-modal -->
    <footer><a href="#" id="cookie-settings">Cookie settings</a></footer>
    bar.addEventListener('close', () => {
      const on = document.activeElement
      if (bar.contains(on) || on === document.body) cookieLink.focus()
    })
  2. Dialog

    <dialog open> first in <body> · position: fixed + scroll-padding

    Role, name, met first in reading order. No script. Pinned to the bottom, it never hides the focused link.

    <dialog open class="bar" aria-labelledby="ck-h">  <!-- first in body: met first -->
      <h2 id="ck-h">Cookies on this site</h2>
      <p>Essential ones keep the bag working. Others help us improve.</p>
      <form method="dialog">
        <button value="essential">Essential only</button>
        <button value="all">Accept all</button>
      </form>
    </dialog>
    .bar { position: fixed; inset: auto 0 0 0; }
    html { scroll-padding-block-end: var(--bar-h); }
    body { padding-block-end: var(--bar-h); }

Usage

Use cases

Six that stay in the page and four that are popovers, each the real component and each working. Each stands in a page in small, so what is pinned to the window is pinned to it.

  • Maintenance notice

    One acknowledgement, one button

    Ordering pauses Saturday 02:00–04:00 UTC

    Your bag stays saved. Checkout opens again at 04:00.

    Halden Coffee

    New coffees

    Browse coffees

    <dialog open> in the flow (position: static) · focus: none · one acknowledgement · after: next control

    The title says what and the consequence. Got it is the only answer.

  • Locale banner

    Buttons named by the result, never Yes and No

    View this site in Italiano?

    Halden Coffee

    New coffees

    Language: English

    Browse coffees

    <dialog open> first in body · buttons named by the result · closedby none · after: next control

    Every answer is one click, the same size and the same step. Refusing never costs an extra screen.

  • Toast

    Raised at runtime, with a way out and no timer

    Halden Coffee

    New coffees

    Last action: none yet

    Draft saved

    show() after Save draft · focus: the heading (autofocus) · closerequest · no timer · after: back to Save draft

    Focus entering the heading is what is spoken. Esc closes the newest toast, wherever focus is.

  • Toast stack

    One dialog for each toast

    Halden Coffee

    New coffees

    Last action: none yet

    • Photo uploaded
    • Price updated
    • Draft saved

    One dialog per toast, own dismiss · focus: the newest toast’s heading · out-of-order dismiss: next open toast, else the trigger

    Dismiss one out of order: the browser would restore focus to a toast that is gone, so it is decided before focus leaves.

  • Panel tethered to its button

    An optional panel in the top layer

    Halden Coffee

    Coffees

    Last action: none yet

    Filters

    popover="auto" · popovertarget toggles it · focus: the heading · Esc and outside click close · Close is popovertargetaction="hide" · after: back to Filters

    The opener’s expanded state is exposed by the browser: no aria-expanded to keep in step.

  • Toggletip on a figure

    A note that opens on click

    Halden Coffee

    Ristretto

    Intensity 9

    Intensity

    How strong the cup tastes, from 1 to 13. Ristretto is 9.

    Opens on click (1.4.13 hover rules never arise) · button named by what it does (“About intensity”), 24×24 · focus: the heading · Esc, outside click or Close

    The button says what it does, not what the panel says.

  • Tour card (coach mark)

    Unasked, so light dismiss is right

    Halden Coffee

    Your coffees

    Last action: none yet

    Save your favourites

    Step 1 of 3. Tap the heart on any coffee to keep it here.

    popover="auto" · unasked, so light dismiss is right · cost: clicking the ♡ it points at is outside and ends the tour · Skip tour named by the result

    Showing an auto popover closes any other open one.

  • Toast stack as one popover

    Manual: it survives Esc and an outside click

    Halden Coffee

    New coffees

    Last action: none yet

      Showing an auto popover closes the others: a toast stack is one popover · manual survives Esc and outside click, so each toast keeps its own Close

      Each toast owes a Close, because Esc and an outside click no longer close it.

    Props

    Props
    PropDoesDefault
    idRequired. The opener’s popovertarget or commandfor points to it, and the id of its heading comes from it—
    title, ariaLabeltitle is the h2 that names it and takes focus when it is raised. ariaLabel names one with no heading of its own—
    waymarkup: <dialog open> from first paint. show: raised with show(). popover: <dialog popover>, opened by an invokershow
    placementflow: part of the page. bar: pinned to the bottom, full width. toast: pinned bottom-end. anchored: tethered to its openerflow
    closedbynone for what holds an answer, closerequest for a toast. Never any: every click on the page is outsidenone
    popoverauto closes with Esc and a click outside. manual with neither: each toast owes its own Closeauto
    open, onClose(returnValue)Raise and lower it from state; for markup it is how it comes back after it was answered. onClose gets the returnValue of the exit, read once and reset. A popover opened by popovertarget needs neither—
    showClose, closeLabelThe 44 × 44 Close inside, and its name. A popover gets popovertargetaction="hide", the rest the close command: no handlerfalse, Close
    standInThe id of the control that stands for a markup-open dialog: focus goes there when it closes, because the browser restores nothing—
    initialFocusA selector for what takes focus when it is raised, such as the newest toast of a stackThe heading
    stackDialogs of one stack hand focus to the nearest open one when one is dismissed out of order, and to the control that raised the first when none is left—
    tone, compact, barenotice is the --wash fill. compact has less padding. bare has no box of its own, for a popover that holds toastsplain, false, false
    NonModalLevel, anchorStyle(id)The level of its heading. The style that makes a button the anchor of the popover whose id it is given2

    Example

    A button raises it with show() through open. Focus goes to the heading, Undo answers with returnValue, and Close and Esc leave it with focus back on the button.

    import { useState } from 'react'
    import { ModalDialogButton } from '@/components/ModalDialogButton'
    import { NonModalDialog } from '@/components/NonModalDialog'
    
    export const SaveDraft = ({ onUndo }: { onUndo: () => void }) => {
      const [open, setOpen] = useState(false)
      return (
        <>
          <ModalDialogButton onClick={() => setOpen(true)}>
            Save draft
          </ModalDialogButton>
          <NonModalDialog
            id="saved"
            title="Draft saved"
            placement="toast"
            closedby="closerequest"
            showClose
            open={open}
            onClose={(answer) => {
              setOpen(false)
              if (answer === 'undo') onUndo()
            }}
          >
            <form method="dialog" className="nmd-actions">
              <ModalDialogButton type="submit" value="undo">
                Undo
              </ModalDialogButton>
            </form>
          </NonModalDialog>
        </>
      )
    }

    Specs

    Measurements

    Measurements
    PartValue
    Box1.5px edge in ink, 12px corner, 0 6px 9px shadow at 18%. A bar has no corner, and a box in the flow no shadow
    Padding1.25rem. A bar 1.125rem, a note or a card tethered to a control 1rem, a toast in a stack 0.875rem
    Headingh2, 15px bold, line 1.45, in ink
    Text13px, line 1.45, in ink-soft
    Close44 × 44, 8px corner, an 18px glyph in a 3px line (Dialog / Close button)
    AnswersDialog / Button: 47px tall, never under 44px. Every answer the same size
    Toggletip button24 × 24, the least that 2.5.8 allows, 2px edge, a 12px corner
    BarFull width, pinned to the bottom, up to half the height of the viewport
    Toast300px wide, pinned bottom-end, 1rem from the edges. Cookie settings 340px, a toast of a stack 270px
    PopoverA panel 320px, a note or a card 300px, tethered under its button

    Ways to open

    Ways to open
    WayEffect on focusUse when
    <dialog open> in markupNone (focus on body at load).Present from first paint.
    dialog.show()Moves to the first focusable child, else the dialog; a real autofocus picks the target.Raised at runtime.
    <dialog popover> + popovertarget or showPopover()Same as show(); returns to the previous element on close.Optional panel in the top layer.

    show() on an open non-modal is a no-op; on an open modal it throws InvalidStateError. The component guards with if (!dialog.open).

    Placement

    Placement
    RuleHow
    UA defaultposition: absolute and centred with margin: auto.
    In the flowposition: static.
    Floatingposition: fixed, as a direct child of <body>: no top layer, so a transformed or clipped ancestor traps it and a later sibling’s z-index covers it. Need the top layer? <dialog popover>.
    A fixed bar never hides focusscroll-padding-block-end equal to its height on html, and the same padding-block-end on body. The component sets both while the bar is open and pinned to the window.
    320 px and 400%A fixed bar must not fill the viewport. Put it in the flow, or cap its height and let it scroll.
    Focus ring3:1 against the bar’s own fill. Forced colors: the edge stays, 1px solid CanvasText.
    MotionWhat is raised moves 0.5rem and fades in 0.2s; what is in the markup at first paint does not move. Reduced motion keeps display and the fade at 1ms.

    Adapting to viewports

    Adapting to viewports
    WidthBarPopover
    1280 CSS px · 100%Fixed to the bottom, full width; scroll padding = its heightTethered to its button with anchor positioning.
    640 CSS px · 200%Fixed, text above the answersflip-block when there is no room below.
    390 · mobileFixed, capped height, scrolls insideFull width under the button, max-block-size, scrolls inside.
    320 CSS px · 400% (1.4.10)In the flow (position: static), first in bodyFull width, never wider than the viewport.
    Text 200% (1.4.4)Height from contentHeight from content; text wraps.

    Markup

    The consent bar, present from first paint, and a popover with its anchor.

    <body>
      <dialog open class="bar" aria-labelledby="ck-h">  <!-- first in body: met first -->
        <h2 id="ck-h">Cookies on this site</h2>
        <p>Essential ones keep the bag working. Others help us improve.</p>
        <form method="dialog">
          <button value="essential">Essential only</button>
          <button value="all">Accept all</button>
        </form>
      </dialog>
      <header>…</header>
      <main>…</main>
      <footer><a href="#" id="cookie-settings">Cookie settings</a></footer>
    </body>
    <button popovertarget="filters">Filters</button>
    <dialog id="filters" popover aria-labelledby="f-h">
      <h2 id="f-h" tabindex="-1" autofocus>Filters</h2>
      <button popovertarget="filters" popovertargetaction="hide" aria-label="Close">×</button>
      …
    </dialog>
    
    [popovertarget="filters"] { anchor-name: --filters; }
    #filters { position-anchor: --filters; position-area: block-end span-inline-end;
               position-try-fallbacks: flip-block; inset: auto; margin: 0; }
    dialog[open], dialog:popover-open { display: grid; }  /* never on the base rule */

    Script

    Markup first. Script only for what is raised at runtime and for stacks.

    const toast = document.getElementById('saved')
    const cookieLink = document.getElementById('cookie-settings')
    const bar = document.querySelector('dialog.bar') // markup-open consent bar
    
    // Raised at runtime: focus enters the heading, which is what AT speaks
    let trigger = null
    function showToast() {
      trigger = document.activeElement // for out-of-order dismiss
      if (!toast.open) toast.show()
    }
    
    toast.addEventListener('close', () => {
      if (toast.returnValue === 'undo') undoSave() // the exact word
      toast.returnValue = '' // reset after reading
    }) // show() returns focus to the trigger
    
    // Markup-open bar: nothing to restore, so move focus to its stand-in
    bar.addEventListener('close', () => {
      const on = document.activeElement
      if (bar.contains(on) || on === document.body) cookieLink.focus()
    })
    
    // Out-of-order dismiss in a stack: decide before focus leaves
    document.querySelectorAll('dialog.toast').forEach((dialog) => {
      dialog.addEventListener('beforetoggle', (event) => {
        if (event.newState !== 'closed' || !dialog.contains(document.activeElement)) return
        const next = [...document.querySelectorAll('dialog.toast[open]')].find(
          (other) => other !== dialog,
        )
        queueMicrotask(() => (next ? next.querySelector('h2') : trigger).focus())
      })
    })

    Tokens

    Component tokens

    The values of the Figma page. Dialog / Button and Dialog / Close button are the ones of the Modal Dialog.

    Component tokens
    PartPropertyTokenValue
    BoxEdge · corner—1.5px · 12px (a bar has no corner)
    BoxFill · textwhite · ink#ffffff · #16161a
    BoxShadow—0 6px 9px at 18%
    NoticeFillwash#edebf4 (the Figma draws #efedf6)
    HeadingSize · weight · line—15px · 700 · 1.45
    TextSize · line · colourink-soft13px · 1.45 · #4b4b55
    PaddingBox · bar · compact · toast of a stack—20px · 18px · 16px · 14px
    CloseSize · cornerradius44 × 44 · 8px
    AnswersDialog / Button—47px, never under 44px · 6px corner (as in the Modal Dialog)
    Toggletip buttonSize · edge · cornerbw24 × 24 · 2px · 12px
    CheckboxSize · accentlilac24px · #5e548e
    MotionRise · fade—0.5rem · 0.2s
    Focus ringColour · widthfocus-ring-primary · focus-ring-widthThe site’s, not redeclared

    Guidelines

    Non-modal or something else?

    Non-modal or something else?
    NeedUse
    Present from first paint (consent, locale, maintenance)<dialog open> in the markup: no script has to run; showModal() can’t
    Raised at runtime (click, key, timer, result): toast, noticedialog.show()
    Optional panel or note tethered to its button, light dismiss wanted, must not be clipped<dialog popover>
    Must block (age gate, terms): the page behind becomes inertNot a non-modal. A non-modal never makes the page inert: Modal Dialog
    Must block and waitshowModal() · Modal Dialog
    Text only, no controls, no dismissNot a dialog: a plain section

    closedby

    Dismissing is not an answer. Pick the smallest close that fits.

    closedby
    closedbyEscOutside clickUse
    none (default)nothingnothingBars that hold an answer: dismissing is not an answer.
    closerequestcloses the newest shown, wherever focus isnothingTransient: toasts.
    anysamecloses: every click on the page is outsideNever.

    Answers and timing

    Answers and timing
    RuleHow
    returnValueform method="dialog" + submit value works as in a modal. Compare the exact word, reset to '' after reading.
    A refusal that must not closetype="button".
    An answer barEvery answer one click, same size, same step. Refusing never costs an extra screen.
    No timer on text to read or a control2.2.1. If it must expire, pause on hover and focus.

    Stacks

    Each toast is its own dialog with its own dismiss. Focus goes to the newest toast’s heading.

    Stacks
    RuleHow
    One dialog per toastNamed by its title, with its own dismiss button. Focus goes to the newest toast’s heading.
    Out-of-order dismissIn beforetoggle (newState === "closed") read dialog.contains(document.activeElement): by close, focus has left. Then focus the next open toast, else the trigger.
    As popoversauto popovers close each other: a toast stack is one popover.
    Under an open modalA non-modal or popover outside an open modal takes no focus and no clicks. Raise it inside the modal, or hold it until the modal closes.

    Popover

    The one non-modal in the top layer: no clipping ancestor, no z-index fights. For an optional panel or note tethered to its button. What stays in the page uses show().

    Popover
    RuleHow
    Freepopovertarget toggles it with no handler or state; auto closes on Esc and outside click; focus goes in on open and back to the invoker on every exit; the invoker’s expanded state is exposed: no aria-expanded to sync.
    Close insideA second invoker: popovertarget + popovertargetaction="hide", no handler.
    Focus inThe heading: tabindex="-1" + a real autofocus. Without it focus lands on the first control.
    auto or manualauto for panels, notes and tours: Esc and outside click are how a person leaves. manual survives both and owes a replacement for each.
    One at a timeShowing an auto closes any other open auto that is not its ancestor. Cost: any click on the page is outside. Right for something unasked, wrong for a form.
    Tab past the endLeaves it open. To close: hide on blur when relatedTarget is outside the panel; note pointerdown inside first.
    Invoker goneFocus falls to body: send it to the page’s own heading, only if nothing else took it.
    CSSNever gets open: display on :popover-open, not [open]. Clear the UA inset: 0; margin: auto.
    Tetheranchor-name on the button, position-anchor + position-area on the panel, position-try-fallbacks: flip-block. Without anchor positioning, place it on scroll and resize.
    Not a menurole="menu" is the arrow-key contract for commands; controls you Tab between are not one.
    ::backdropA popover gets one and makes nothing inert: the page stays live. A dim means nothing unless everything else carries inert.
    AX treeChromium marks an open dialog popover as modal: true though the page stays live. Not checked in AT.

    Copy

    Title: what and the consequence. Body: one or two sentences. Buttons named by the result.

    Copy
    PartDon’tDo
    TitleImportant noticeOrdering pauses Saturday 02:00–04:00 UTC
    BodyWe are performing scheduled maintenance to improve your experience…Your bag stays saved. Checkout opens again at 04:00.
    ButtonsOK · Yes · No · CancelEssential only · Accept all · Got it
    AcknowledgementGot it + Close + DismissOne acknowledgement is one button.
    Toggletip button“Intensity is how strong the cup tastes”“About intensity”: what the button does, not what the panel says.

    Controls by scenario and context

    Each row is one use case. Each column shows the controls that dialog gets at that size. Bars are scaled-down copies of the real components (Dialog / Button, Dialog / Close button). Zoom uses the reflow of Adapting to viewports.

    • Solid: required in that context, never dropped
    • Opener · Stand-inOutside the dialog: where focus returns, or the control that stands for a markup-open bar

    Zoom 400% · 320 CSS px. At 200% text bars go in the flow.

    • Consent bar

      <dialog open> · first in body

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Essential only · Accept all (same size, same step)

      Stand-in

      Focus stays at load. Pinned bottom with scroll padding. After: focus to “Cookie settings”.

      Smaller viewport · 400–639 pxNarrow column

      Essential only · Accept all

      Text above, answers below; still one click each.

      Zoom 400% · 320 CSS pxReflow

      Stacked, in the flow

      At 320 px it goes in the flow (position: static), never filling the viewport.

    • Maintenance notice

      <dialog open> · in the flow

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Got it

      One acknowledgement, one button. Title says what and the consequence.

      Smaller viewport · 400–639 pxNarrow column

      Got it

      Same.

      Zoom 400% · 320 CSS pxReflow

      Got it

      Text wraps; in the flow at every size.

    • Locale banner

      <dialog open> · first in body

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Italiano · Stay in English

      Buttons named by the result, never Yes / No.

      Smaller viewport · 400–639 pxNarrow column

      Italiano · Stay in English

      Same.

      Zoom 400% · 320 CSS pxReflow

      Stacked

      In the flow; full-width buttons.

    • Toast

      show() · closerequest

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Heading (focused) · Undo · Close

      Opener

      Focus to the heading on show(); back to the trigger on close. Esc closes the newest. No timer.

      Smaller viewport · 400–639 pxNarrow column

      Undo · Close

      Opener

      Bottom, full width minus 16px.

      Zoom 400% · 320 CSS pxReflow

      Undo · Close

      Height capped; never covers the focused control.

    • Toast stack

      One dialog per toast

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Close per toast · Undo where it applies

      Opener

      Focus to the newest toast’s heading. Out-of-order dismiss: next open toast, else the trigger.

      Smaller viewport · 400–639 pxNarrow column

      Close per toast

      Opener

      Bottom, full width minus 16px. Each toast keeps its own Close.

      Zoom 400% · 320 CSS pxReflow

      Close per toast

      Bottom, full width minus 16px; height capped.

    • Cookie settings

      show() from a link

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Close · Save choices

      Opener

      Focus to the heading (autofocus). On close the browser returns focus to the link.

      Smaller viewport · 400–639 pxNarrow column

      Close · Save choices

      Opener

      Same.

      Zoom 400% · 320 CSS pxReflow

      Close · Save choices

      In the flow under the link; no trap.

    • Popover panel

      <dialog popover> · auto

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Close (popovertargetaction="hide") · Show 24 coffees

      Opener

      Tethered under Filters with anchor positioning. Esc and outside click close; focus back to Filters.

      Smaller viewport · 400–639 pxNarrow column

      Close · Show 24 coffees

      Opener

      position-try-fallbacks: flip-block when there is no room below.

      Zoom 400% · 320 CSS pxReflow

      Close · Show 24 coffees

      Full width under the button; max height, scrolls inside; never wider than the viewport.

    • Toggletip

      <dialog popover> · on a figure

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Close

      Opener

      Opens on click; the 24×24 button says what it does (“About intensity”).

      Smaller viewport · 400–639 pxNarrow column

      Close

      Opener

      Same.

      Zoom 400% · 320 CSS pxReflow

      Close

      Full width under the figure; text wraps.

    • Tour card

      <dialog popover> · auto · unasked

      Live example

      Desktop · 640 px and upBar, toast or panel at full width

      Skip tour · Next · Close

      Opener

      Light dismiss is right for something unasked. Clicking what it points at ends the tour.

      Smaller viewport · 400–639 pxNarrow column

      Skip tour · Next · Close

      Opener

      Same.

      Zoom 400% · 320 CSS pxReflow

      Stacked

      Card below its target, full width.

    Accessibility

    Requirements

    Requirements
    RuleWCAG
    Never showModal() and never aria-modal="true" here: it tells AT the page is inert when it isn’t.4.1.2
    Name with aria-labelledby (the heading); aria-label only without one.4.1.2 · 1.3.1
    Markup open at load: focus stays put, or it steals the caret from someone reading. Raised at runtime (toast, notice, result): focus travels into it, to the heading. Focus entering is what AT speaks.2.4.3 · 4.1.2
    Every exit leaves focus on a real control. Raised by show(), focus returns to the trigger. A markup-open dialog restores nothing: focus fell to body.2.4.3
    No focus trap, no Tab wrap: a trap on a page that is still live is a keyboard trap.2.1.2
    A visible, named dismiss button inside, reachable by focus. Esc is a bonus, never the only way out.2.1.1
    Closed means out of the tab order: display only on [open] (:popover-open on a popover), never on the base rule.2.4.3
    Contents follow the accessible frontend rules.—

    Focus management

    Who raised it decides where focus goes.

    Focus management
    CaseFocus
    Page load, markup openNowhere. The bar waits in the tab order.
    Anything raised at runtime (button, “Cookie settings” link, timer, result)The heading: tabindex="-1" and a real autofocus attribute (show() reads it; React’s autoFocus prop never sets it).
    Closed after show() or a popoverThe browser returns focus to the element that had it, if it still exists.
    Markup open, or the trigger is goneMove it yourself: to the control that now stands for the bar (a “Cookie settings” link), else the next sensible control. Never body.
    DOM positionIs tab order and reading order. A bar the person must meet goes first in <body>, pinned with CSS. Last in the DOM means after the whole page.

    Keyboard

    No wrap, no trap: the page is still live.

    Keyboard
    KeyDoes
    Tab · Shift+TabIn, through and out in DOM order. No wrap. Tab past the last control of a popover leaves it open.
    EscNothing, unless closerequest (the newest toast) or popover="auto".
    Enter · SpaceActivates the focused button. Enter in a method="dialog" form submits and closes.

    WCAG criteria

    What each success criterion asks of a non-modal dialog, and what meets it.

    WCAG criteria
    CriterionMet by
    1.3.1 Info and Relationships (A)Named by its heading; form method="dialog" answers
    1.3.2 Meaningful Sequence (A)DOM position = reading order: a bar to meet goes first in body
    1.4.3 Contrast (Minimum) (AA)Text 4.5:1 against the bar’s own fill
    1.4.10 Reflow (AA)At 320 px the bar goes in the flow; a popover fits the viewport
    1.4.11 Non-text Contrast (AA)Focus ring 3:1 against the bar’s fill; forced colors keep a border
    1.4.13 Content on Hover or Focus (AA)Not triggered: toggletips open on click
    2.1.1 Keyboard (A)Every answer and dismiss is a button
    2.1.2 No Keyboard Trap (A)No trap, no wrap: in, through and out
    2.2.1 Timing Adjustable (A)No timer on text or a control; pause on hover and focus
    2.4.3 Focus Order (A)Runtime: focus to the heading, back to the trigger; load: none; every exit on a real control
    2.4.7 Focus Visible (AA)The site’s ring on the dialog’s controls
    2.4.11 Focus Not Obscured (Minimum) (AA)scroll-padding-block-end = the fixed bar’s height
    2.5.8 Target Size (Minimum) (AA)Answers 44 × 44; toggletip button at least 24 × 24
    3.2.4 Consistent Identification (AA)Same title and buttons wherever the bar appears
    4.1.2 Name, Role, Value (A)Native dialog role; never aria-modal; popover expanded state exposed by the invoker

    Related

    References

    This section is still in progress.