New Delhi · September 2026
Preceda

09 Overlays

Overlays

Dialogs, confirms, menus, popovers, tooltips, the mobile sheet and its dock, the tour’s coachmark. Native top layer, focus in and back, Esc everywhere, and the desk never lost.

  1. Dialog
  2. Confirm dialog
  3. Menu
  4. Popover
  5. Tooltip
  6. Sheet
  7. Dock
  8. Coachmark
  9. Kbd

01

Dialog

One bounded question on a raised sheet over a dimmed desk. The page behind is inert; focus goes in and comes back; Esc returns counsel to the work.

Anatomy

<dialog class="preceda-dialog">
Native modal: top layer, inert page, `cancel` on Esc. Raised paper, rule-strong border, dialog shadow.
__header / __kicker / __title
Optional small-caps kicker, the serif heading-role title, a 40px close control.
__description
One sentence of context in the body role; wired to aria-describedby.
__body
Scrolls inside the dialog; the header and foot stay put.
__actions / __footnote
A ruled foot, primary act last; a tertiary line of consequence.
--narrow / --wide
480 px for confirms, 768 px for reference sheets such as shortcuts.

Variants

Live — share

Tab stays inside; Esc or the close control returns focus to “Share draft”; the copy toast renders inside the dialog.

Paper

Secure read-only link

Share Anticipatory bail, brother-in-law

Recipients see a frozen snapshot, even if the draft changes later.

Anyone with the link can view. Uploaded documents are never shared.

    Lamp

    Secure read-only link

    Share Anticipatory bail, brother-in-law

    Recipients see a frozen snapshot, even if the draft changes later.

    Anyone with the link can view. Uploaded documents are never shared.

      Live — keyboard shortcuts

      Wide, with Kbd rows. Replaces the hand-rolled shortcuts modal.

      Paper

      Keyboard shortcuts

      Global

      Show keyboard shortcuts
      Focus search
      Go to matters
      Switch Paper · Lamp

      Case

      Next paragraph
      Previous paragraph
      Toggle the margin

      Lamp

      Keyboard shortcuts

      Global

      Show keyboard shortcuts
      Focus search
      Go to matters
      Switch Paper · Lamp

      Case

      Next paragraph
      Previous paragraph
      Toggle the margin

      Default (drawn inline)

      Paper

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Lamp

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Narrow

      Paper

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Lamp

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      State matrix

      StatePaperLamp
      open

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      primary hover

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      primary focus-visible

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      At 390 px

      Below 640 px the dialog rises from the foot at full width with square lower corners, clears the home indicator, and stacks its actions full width with the primary act nearest the thumb (44 px).

      390 px

      Which court passed the impugned order?

      This completes the jurisdiction paragraph of the revision.

      Accessibility

      • showModal(): the rest of the page is inert and hidden from assistive technology.
      • Focus moves to [autofocus], `initialFocus`, or the first control; it returns to the opener (or `returnFocus`) on close.
      • Tab and Shift+Tab wrap inside the dialog; Esc fires onClose("escape") unless `dismissible={false}`.
      • aria-labelledby is the title; aria-describedby is the description.

      Do

      • Ask one question, with the context to answer it.
      • Name the primary act with a verb.
      • Keep the dialog controlled; the product owns open state.

      Don’t

      • Open a dialog for a notification.
      • Stack dialogs.
      • Hand-roll a fixed overlay — use the native dialog.

      Usage

      HTML
      <dialog class="preceda-dialog" aria-labelledby="d-title" aria-describedby="d-desc">
        <div class="preceda-dialog__sheet">
          <header class="preceda-dialog__header">
            <h2 class="preceda-dialog__title" id="d-title">Which court passed the impugned order?</h2>
            <button class="preceda-dialog__close" type="button" aria-label="Close">…</button>
          </header>
          <p class="preceda-dialog__description" id="d-desc">This completes the jurisdiction paragraph.</p>
          <div class="preceda-dialog__body">…</div>
          <div class="preceda-dialog__actions">…</div>
        </div>
      </dialog>
      React
      <Dialog open={open} onClose={() => setOpen(false)}
        kicker="Secure read-only link" title={`Share ${title}`}
        description="Recipients see a frozen snapshot, even if the draft changes later."
        footnote="Anyone with the link can view. Uploaded documents are never shared.">
        <ShareForm />
      </Dialog>

      02

      Confirm dialog

      Replaces window.confirm(). The question is the title, the consequence is the sentence beneath, and the button names the act. A destructive confirm starts on Cancel.

      Anatomy

      <ConfirmDialog>
      A narrow Dialog with role="alertdialog", Cancel and the named act.
      destructive
      The act uses `preceda-button--destructive` (T3) and initial focus lands on Cancel.
      busy
      An async onConfirm shows the busy label; Esc, backdrop and Cancel are disabled until it settles.
      error
      A rejected onConfirm keeps the dialog open with a compact error Note, announced assertively.

      Variants

      Live — destructive

      Focus starts on Cancel; confirming shows “Deleting…”, then closes.

      Paper

      Delete this matter?

      Anticipatory bail, brother-in-law — the draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      Lamp

      Delete this matter?

      Anticipatory bail, brother-in-law — the draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      Live — failure then retry

      The first attempt fails and says so; the second succeeds.

      Paper

      Revoke this link?

      Anyone holding it will see “This link has been revoked.” You can create a new link afterwards.

      Lamp

      Revoke this link?

      Anyone holding it will see “This link has been revoked.” You can create a new link afterwards.

      Not destructive

      Paper

      Send answers to the associate?

      Two of five questions are unanswered; the associate will draft around them.

      Lamp

      Send answers to the associate?

      Two of five questions are unanswered; the associate will draft around them.

      State matrix

      StatePaperLamp
      default

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      busy

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      error

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

      At 390 px

      At 390 px it rises from the foot like every dialog; Cancel sits above the act so the destructive button is not under the resting thumb by accident — both are 44 px and full width.

      Accessibility

      • role="alertdialog": the title and consequence are read on open.
      • Destructive confirms put initial focus on Cancel, so Enter never destroys by reflex.
      • While busy the act has aria-busy and the dialog cannot be dismissed half-way through.

      Do

      • Name the act: “Delete matter”, “Revoke link”.
      • State exactly what will be lost.

      Don’t

      • Write “Are you sure?” or “OK”.
      • Confirm a reversible act — offer Undo in a toast instead.

      Usage

      HTML
      <dialog class="preceda-dialog preceda-dialog--narrow" role="alertdialog" aria-labelledby="t" aria-describedby="c">…
        <div class="preceda-dialog__actions">
          <button class="preceda-button preceda-button--quiet" autofocus>Cancel</button>
          <button class="preceda-button preceda-button--destructive">Delete matter</button>
        </div>
      </dialog>
      React
      <ConfirmDialog open={open} destructive onClose={() => setOpen(false)}
        title="Delete this matter?" description="The draft, 3 letters and 6 authorities will be deleted. This cannot be undone."
        confirmLabel="Delete matter" busyLabel="Deleting…" onConfirm={() => deleteMatter(id)} />

      03

      Menu

      A menu button opens a short list of acts on a raised slip. Everything is reachable from the keyboard, and the list says why an act is unavailable instead of hiding it.

      Anatomy

      .preceda-menu-trigger
      aria-haspopup="menu", aria-expanded; ruled text button with ▾, `--quiet`, or `--kebab` (⋯, needs aria-label).
      .preceda-menu
      role="menu" in a native auto popover (top layer, light dismiss), positioned by the internal helper: flips and shifts to stay in the visual viewport.
      __item
      role="menuitem", 40 px (44 px on phones); `__hint` at right; `__description` beneath.
      __item--destructive
      Binding ink; names what it destroys; follow with a ConfirmDialog.
      [aria-disabled="true"]
      Focusable, not choosable, ink-tertiary; the description says why.
      __separator / __group-label
      A hairline between groups; a small-caps group label (role="group").

      Variants

      Live — Actions ▾ and the kebab

      Arrow keys, Home/End, typeahead (type “d”), Enter/Space, Esc returns focus to the trigger, Tab closes. Choosing an act shows its toast.

      Paper

      Delete this matter?

      The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

        Lamp

        Delete this matter?

        The draft, 3 letters and 6 authorities will be deleted. This cannot be undone.

          Open (drawn inline)

          Paper

          Lamp

          State matrix

          StatePaperLamp
          trigger
          trigger hover
          trigger focus-visible
          trigger expanded
          trigger disabled
          item hover
          item focus-visible
          disabled, focused
          destructive, focused

          At 390 px

          At 390 px items grow to 44 px and the list shifts to stay 8 px inside the visual viewport; with the keyboard up it flips above the trigger when there is more room there.

          Accessibility

          • WAI-ARIA menu button: Enter, Space or ArrowDown opens on the first item; ArrowUp opens on the last.
          • In the list: Arrow keys wrap, Home/End jump, printable keys typeahead, Esc closes and returns focus, Tab closes.
          • Disabled items stay focusable with aria-disabled so their reason (aria-describedby) is read.
          • data-testid and aria-* on MenuItem pass through to the element.

          Do

          • Keep to seven items or fewer, grouped by hairlines.
          • Explain an unavailable act in its description.
          • Confirm destructive items.

          Don’t

          • Put navigation in an action menu.
          • Hide an unavailable act without saying why.
          • Nest submenus.

          Usage

          HTML
          <button class="preceda-menu-trigger" aria-haspopup="menu" aria-expanded="false" aria-controls="m">Actions ▾</button>
          <div class="preceda-menu" id="m" role="menu" popover="auto">
            <button class="preceda-menu__item" role="menuitem" tabindex="-1"><span class="preceda-menu__label">Copy</span><span class="preceda-menu__hint">Rich text</span></button>
            <div class="preceda-menu__separator" role="separator"></div>
            <button class="preceda-menu__item" role="menuitem" tabindex="-1" aria-disabled="true" aria-describedby="why">…</button>
          </div>
          React
          <Menu placement="bottom-end">
            <MenuTrigger>Actions</MenuTrigger>
            <MenuList>
              <MenuItem hint="Rich text" onSelect={copy} data-testid="artifact-copy">Copy</MenuItem>
              <MenuSeparator />
              <MenuItem disabled description={wordBlocker} data-testid="export-word">Word unavailable</MenuItem>
              <MenuItem destructive onSelect={() => setConfirming(true)}>Delete draft</MenuItem>
            </MenuList>
          </Menu>

          04

          Popover

          A small, non-modal panel anchored to what opened it, for a short task in place — adding a judgment to a matter. For a label, use a tooltip; for a list of acts, use a menu.

          Anatomy

          PopoverTrigger
          aria-haspopup="dialog", aria-expanded, aria-controls.
          .preceda-popover
          role="dialog" in a native auto popover: top layer, light dismiss; positioned against the trigger (flip, shift).
          __title
          Small-caps heading over a hairline; names the panel.
          __list / __option / __option-state
          A flush list of choices, 40 px each, with a state word at right (“Added”).
          __foot
          A hairline and one secondary act.

          Variants

          Live — Add to matter

          Focus moves into the panel; Esc or a click outside closes it and focus returns to the trigger.

          Paper

          Lamp

          State matrix

          StatePaperLamp
          loading
          empty
          choices
          added

          At 390 px

          At 390 px the panel keeps 8 px from each edge and caps its height to the space available; a task longer than a few choices belongs in a Sheet instead.

          Accessibility

          • Non-modal: the page stays interactive; the panel is announced as a dialog named by its title.
          • Focus moves to the first control on open, and back to the trigger on Esc or light dismiss.
          • State words (“Added”) accompany disabled options, so the state is not colour alone.

          Do

          • Keep the task to one step.
          • Say what will happen in the title: “Add Arnesh Kumar v. State of Bihar to”.

          Don’t

          • Put a form of several fields in a popover.
          • Open a popover on hover.

          Usage

          HTML
          <button aria-haspopup="dialog" aria-expanded="false" aria-controls="p">Add to matter</button>
          <div class="preceda-popover" id="p" role="dialog" aria-labelledby="p-t" popover="auto">
            <h2 class="preceda-popover__title" id="p-t">Add to matter</h2>
            <ul class="preceda-popover__list"><li><button class="preceda-popover__option">…</button></li></ul>
          </div>
          React
          <Popover>
            <PopoverTrigger>Add to matter</PopoverTrigger>
            <PopoverContent title={`Add ${caseName} to`}>
              <MatterChoices onAdd={add} />
            </PopoverContent>
          </Popover>

          05

          Tooltip

          A supplementary label for a control whose purpose is not written beside it — an icon-only button, a truncated title. Never the only home of information counsel needs.

          Anatomy

          .preceda-tooltip
          role="tooltip" in a manual popover (it never closes an open menu). Ink slip, paper text, small role.
          __keys
          An optional shortcut hint after the label.
          relation
          `describe` (aria-describedby) keeps the control’s own name; `label` (aria-labelledby) names an icon-only control.

          Variants

          Live

          Hover for 500 ms or focus with the keyboard; Esc hides it; the pointer may move onto it.

          Paper

          Copy the draft⌘CDownloadShare a read-only linkSatender Kumar Antil v. Central Bureau of InvestigationSatender Kumar Antil v. Central Bureau of Investigation, (2022) 10 SCC 51

          Lamp

          Copy the draft⌘CDownloadShare a read-only linkSatender Kumar Antil v. Central Bureau of InvestigationSatender Kumar Antil v. Central Bureau of Investigation, (2022) 10 SCC 51

          Drawn inline

          Paper

          Copy the draft⌘CShare a read-only link

          Lamp

          Copy the draft⌘CShare a read-only link

          State matrix

          StatePaperLamp
          hidden
          trigger focus-visible
          shown
          Copy the draft
          Copy the draft

          At 390 px

          Touch never shows a tooltip. On a phone every icon-only control must also carry its accessible name, and anything counsel must read is written on the page.

          Accessibility

          • Shows on keyboard focus (focus-visible) at once and on hover after a delay; hides on blur, Esc and pointer leave.
          • Hoverable and persistent (WCAG 1.4.13): moving onto the tooltip keeps it; Esc dismisses it without moving focus or closing a dialog.
          • Icon-only controls keep aria-label; the tooltip repeats it visually.

          Do

          • Keep it to a few words.
          • Use it for icon-only controls and truncated titles.

          Don’t

          • Put links, buttons or required instructions in a tooltip.
          • Use `title=` where the label matters.

          Usage

          HTML
          <button aria-label="Copy the draft" aria-labelledby="t">…</button>
          <span class="preceda-tooltip" id="t" role="tooltip" popover="manual">Copy the draft</span>
          React
          <Tooltip content="Copy the draft" shortcut="⌘C" relation="label">
            <IconButton aria-label="Copy the draft" icon="copy" />
          </Tooltip>

          06

          Sheet

          On a phone, secondary surfaces rise from the foot: correspondence pulled up from the dock, a footnote from a mark. The desk behind is inert but visible; a drag down returns to it.

          Anatomy

          <dialog class="preceda-bottom-sheet">
          Native modal: top layer, inert page, Esc. Paper, rounded top corners, dialog shadow.
          __grip
          A 40 px-wide button over a short rule. Drag between snaps; past the lowest it dismisses. Arrow Up/Down step snaps; Enter cycles.
          __header / __title / __close
          Small-caps title (“Correspondence”) and the return control (“Desk ▾”). The head drags too.
          __body
          Scrolls; overscroll is contained so the page does not move.
          __footer
          The docked composer; padded by the safe-area inset, lifted above the keyboard via the visual viewport.
          --peek / --half / --full / --content
          Snap heights from `--preceda-bottom-sheet-{peek,half,full}` (36, 58 dvh, full less the top inset); `content` fits up to half.

          Variants

          Live — correspondence

          Half and full snaps. Drag the grip or head; Arrow keys on the grip; Esc or “Desk” returns focus.

          Paper

          Correspondence

          Counsel

          Client arrested yesterday under 498-A and 406, Saket. FIR attached. Anticipatory bail for the brother-in-law, who was abroad.

          Associate

          I have read the FIR. The dominant instrument is an application under s. 482 BNSS before the Sessions Court, Saket, with the Arnesh Kumar directions as the spine. Two facts the file does not hold: the dates he was outside India, and whether a notice under s. 35(3) BNSS was served on him.

          Counsel

          Abroad 3 March to 19 August 2026. No notice served.

          Reading authorities on 482 BNSS

          Lamp

          Correspondence

          Counsel

          Client arrested yesterday under 498-A and 406, Saket. FIR attached. Anticipatory bail for the brother-in-law, who was abroad.

          Associate

          I have read the FIR. The dominant instrument is an application under s. 482 BNSS before the Sessions Court, Saket, with the Arnesh Kumar directions as the spine. Two facts the file does not hold: the dates he was outside India, and whether a notice under s. 35(3) BNSS was served on him.

          Counsel

          Abroad 3 March to 19 August 2026. No notice served.

          Reading authorities on 482 BNSS

          Live — footnote

          A fitted sheet from a verification mark; focus returns to the mark.

          Paper

          …contrary to Arnesh Kumar, (2014) 8 SCC 273.

          Verified · ¶

          Arnesh Kumar v. State of Bihar, (2014) 8 SCC 273, para 11 — quoted from the report in the corpus. Citation and proposition match: police must satisfy themselves of the necessity of arrest under s. 41 CrPC (now s. 35 BNSS).

          Lamp

          …contrary to Arnesh Kumar, (2014) 8 SCC 273.

          Verified · ¶

          Arnesh Kumar v. State of Bihar, (2014) 8 SCC 273, para 11 — quoted from the report in the corpus. Citation and proposition match: police must satisfy themselves of the necessity of arrest under s. 41 CrPC (now s. 35 BNSS).

          State matrix

          StatePaperLamp
          full

          Correspondence

          Associate

          Two facts the file does not hold.

          Correspondence

          Associate

          Two facts the file does not hold.
          fitted

          Correspondence

          Associate

          Two facts the file does not hold.

          Correspondence

          Associate

          Two facts the file does not hold.
          grip focus-visible

          At 390 px

          This is a mobile surface. At desk width the sheet caps at 672 px and centres, but the workspace uses the always-open correspondence pane instead.

          390 px

          Correspondence

          Associate

          Two facts the file does not hold.

          Accessibility

          • Modal dialog: the page is inert; focus moves to the sheet (its title is read) and returns to the opener.
          • The grip is a named button with Arrow Up/Down and Enter; dragging is never the only way to resize or dismiss.
          • Esc and the visible return control both close; Tab stays inside.
          • The footer rides above the on-screen keyboard; safe-area insets clear the home indicator.

          Do

          • Keep the composer in the footer so it stays above the keyboard.
          • Return counsel to exactly where they were on the desk.

          Don’t

          • Nest sheets.
          • Use a sheet at desk width where a pane or dialog fits.
          • Make drag the only way to close.

          Usage

          HTML
          <dialog class="preceda-bottom-sheet preceda-bottom-sheet--full" aria-labelledby="s-t">
            <div class="preceda-bottom-sheet__grip-row"><button class="preceda-bottom-sheet__grip" aria-label="Resize sheet"><span class="preceda-bottom-sheet__grip-bar"></span></button></div>
            <header class="preceda-bottom-sheet__header"><h2 class="preceda-bottom-sheet__title" id="s-t">Correspondence</h2><button class="preceda-bottom-sheet__close">Desk ▾</button></header>
            <div class="preceda-bottom-sheet__body">…</div>
            <div class="preceda-bottom-sheet__footer">…composer…</div>
          </dialog>
          React
          <Sheet open={open} onClose={() => setOpen(false)} title="Correspondence" closeLabel="Desk"
            snapPoints={['half', 'full']} footer={<Composer … />}>
            {letters}
          </Sheet>

          07

          Dock

          On the phone the desk comes first; correspondence lives in a dock at the foot that always shows how much there is, whether a question waits, the composer, and the working line.

          Anatomy

          .preceda-dock
          Paper, a rule above, safe-area padding below. `--fixed` pins it to the viewport foot (hidden from 1024 px).
          __grip
          A button over a short rule; pull up more than 24 px, or tap, to open the sheet.
          __summary
          One line and a button (aria-expanded): “Correspondence · 3 letters” in the serif; the status at right.
          __status--waiting
          “1 question waiting” in binding ink — the words carry it, the colour marks it.
          __content
          The composer and the working line (on a hairline).

          Variants

          Live — pull up to the sheet

          Tap the line or pull the grip up; the sheet opens and returns focus here.

          Paper

          Reading authorities on 482 BNSS

          Correspondence

          Counsel

          Client arrested yesterday under 498-A and 406, Saket. FIR attached. Anticipatory bail for the brother-in-law, who was abroad.

          Associate

          I have read the FIR. The dominant instrument is an application under s. 482 BNSS before the Sessions Court, Saket, with the Arnesh Kumar directions as the spine. Two facts the file does not hold: the dates he was outside India, and whether a notice under s. 35(3) BNSS was served on him.

          Counsel

          Abroad 3 March to 19 August 2026. No notice served.

          Reading authorities on 482 BNSS

          Lamp

          Reading authorities on 482 BNSS

          Correspondence

          Counsel

          Client arrested yesterday under 498-A and 406, Saket. FIR attached. Anticipatory bail for the brother-in-law, who was abroad.

          Associate

          I have read the FIR. The dominant instrument is an application under s. 482 BNSS before the Sessions Court, Saket, with the Arnesh Kumar directions as the spine. Two facts the file does not hold: the dates he was outside India, and whether a notice under s. 35(3) BNSS was served on him.

          Counsel

          Abroad 3 March to 19 August 2026. No notice served.

          Reading authorities on 482 BNSS

          Nothing waiting

          Paper

          Lamp

          State matrix

          StatePaperLamp
          waiting
          working

          Drafting paragraph 4

          Drafting paragraph 4

          summary hover
          summary focus-visible

          At 390 px

          The dock is the mobile form of the correspondence pane (ph-desk-paper). The page pads its foot by the dock height so the last paragraph of the draft is never hidden.

          390 px

          Reading authorities on 482 BNSS

          Accessibility

          • A region named “Correspondence”; the grip and summary are buttons with aria-expanded and aria-controls pointing at the sheet.
          • Pulling is never required: tap or Enter opens the sheet.
          • “1 question waiting” is words first; binding ink is the second signal.

          Do

          • Keep the composer usable without opening the sheet.
          • Show the count of questions waiting, not just a dot.

          Don’t

          • Cover the draft: pad the page by the dock.
          • Show the dock at desk width.

          Usage

          HTML
          <section class="preceda-dock preceda-dock--fixed" aria-label="Correspondence">
            <button class="preceda-dock__grip" aria-label="Open correspondence" aria-expanded="false" aria-controls="sheet"><span class="preceda-dock__grip-bar"></span></button>
            <button class="preceda-dock__summary" aria-expanded="false" aria-controls="sheet">
              <span class="preceda-dock__title">Correspondence<span class="preceda-dock__count">3 letters</span></span>
              <span class="preceda-dock__status preceda-dock__status--waiting">1 question waiting</span>
            </button>
            <div class="preceda-dock__content">…composer… …working line…</div>
          </section>
          React
          <Dock fixed title="Correspondence" count={`${letters} letters`} status={waiting ? `${waiting} question waiting` : undefined}
            waiting={waiting > 0} expanded={open} controls={sheetId} onExpand={() => setOpen(true)}>
            <Composer … />
            <WorkingLine label={phase} since={turn.startedAt} />
          </Dock>

          08

          Coachmark

          The tour’s slip of paper, pinned beside the part of the workspace it describes. It says what the thing is and what counsel does with it, in a sentence, and never stands between counsel and the page.

          Anatomy

          .preceda-coachmark
          role="dialog", non-modal, in a manual top-layer popover; positioned against its anchor (flip, shift). A binding rule on the edge that faces the anchor.
          __kicker / __count
          Small caps: “Tour” and the count, “2 of 9”, in tabular figures.
          __title / __body
          A serif subheading, one line; a sentence or two in ink-secondary.
          __choices / __choice
          Optional acts in the body: the recorded questions on a sample matter.
          __controls
          Skip tour at the start (a text act); Back (quiet) and Next or Finish (ink) at the end, over a hairline.
          .preceda-coachmark-spotlight
          A cutout over the anchor whose shadow dims the page; a hairline binding rule marks the edge. Never takes the pointer.

          Variants

          Live — three steps

          Focus moves to Next; Back and Next walk the steps; Esc or Skip tour closes without asking, and focus returns to “Take the tour”.

          Paper

          Anticipatory bail, brother-in-law

          Write to the associate…

          Lamp

          Anticipatory bail, brother-in-law

          Write to the associate…

          State matrix

          StatePaperLamp
          first step
          with recorded questions
          last step

          At 390 px

          Below 640 px the same step rises as the bottom Sheet: the count as its small-caps head, the head and body in its body, Skip · Back · Next in its foot. The anchor is scrolled to the top of the screen above it.

          390 px

          Accessibility

          • A non-modal dialog named by its head and described by its body; the page stays usable around it.
          • Focus moves to Next on open and on each step, and back to where it was on close.
          • Esc skips the tour at once, with no confirmation; a menu open under it closes first.
          • The count is text (“Step 3 of 9”), not a row of dots. Motion is a short fade and is dropped under reduced motion.

          Do

          • Anchor each step to a stable test id, so a step fails loudly when the page moves.
          • Say what counsel does, in the chambers’ voice: “Ask the associate a question.”

          Don’t

          • Block the page with a modal wall.
          • Point with arrows, beaks or pulsing dots.
          • Ask “Are you sure?” on Skip: the tour is always replayable.

          Usage

          HTML
          <div class="preceda-coachmark" role="dialog" aria-modal="false" aria-labelledby="c-t" popover="manual" data-side="bottom">
            <p class="preceda-coachmark__kicker"><span>Tour</span><span class="preceda-coachmark__count">2 of 9</span></p>
            <h2 class="preceda-coachmark__title" id="c-t">Ask the associate a question.</h2>
            <div class="preceda-coachmark__body"><p>…</p></div>
            <div class="preceda-coachmark__controls">
              <button class="preceda-coachmark__skip">Skip tour</button>
              <span class="preceda-coachmark__moves"><button class="preceda-button preceda-button--quiet preceda-button--sm">Back</button><button class="preceda-button preceda-button--ink preceda-button--sm">Next</button></span>
            </div>
          </div>
          React
          <Coachmark open={active} anchor='[data-testid="matter-composer"]'
            title="Ask the associate a question." step={3} total={9}
            onBack={back} onNext={next} onSkip={skip}>
            <p>…</p>
          </Coachmark>

          09

          Kbd

          A key is printed as a small key-cap in chrome sans: a hairline with a heavier foot. Combinations nest keys; sequences say “then”.

          Anatomy

          .preceda-kbd
          One key: small role, ink-secondary on raised paper, rule border, rule-strong foot.
          .preceda-kbd-combo
          An outer <kbd> holding keys and joiners (“+” or “then”); spoken as “Command + K”.
          .preceda-shortcut
          A shortcut list row on a hairline: the act at left, the keys at right.

          Variants

          Single keys

          Paper

          ?/EscJ

          Lamp

          ?/EscJ

          Combination

          Paper

          Lamp

          Sequence

          Paper

          Lamp

          Shortcut list

          Paper

          Focus search
          Go to matters
          Send the instruction
          New line in the instruction

          Lamp

          Focus search
          Go to matters
          Send the instruction
          New line in the instruction

          State matrix

          StatePaperLamp
          static

          At 390 px

          Shortcut lists are hidden on touch-only devices; where keys appear on a phone (an external keyboard), rows keep the act and keys on one line.

          Accessibility

          • Combinations carry a spoken aria-label (“Command + K”); symbol keys have names.
          • Keys are text, not images, so they scale with the page.

          Do

          • Use the platform’s symbols for modifier keys where known.
          • Pair every shortcut with a visible control.

          Don’t

          • Use key-caps as buttons.
          • Teach a shortcut that has no other way to reach its act.

          Usage

          HTML
          <kbd class="preceda-kbd-combo" aria-label="Command + K"><kbd class="preceda-kbd" aria-hidden="true">⌘</kbd><span class="preceda-kbd-combo__joiner" aria-hidden="true">+</span><kbd class="preceda-kbd" aria-hidden="true">K</kbd></kbd>
          React
          <Kbd keys={['⌘', 'K']} />
          <Shortcut action="Go to matters" keys={['G', 'M']} sequence />