AhaSlides Design
v0.52.0React · Vue · Lit

Patterns · composition guide

Overlays pattern

How to build a modal, drawer, or popover — surface choice, safety-critical dismiss behaviour, state reset, drawer placement/width, the async-confirm pattern, inline error surfacing, and danger-styled confirms — built from existing components.

◆ generated from guidelines/overlays.json + parts/overlays.guide.md — do not edit by hand

Overlays are where a stray backdrop click can destroy data, so they need shared safety rules or every confirm/delete/detail surface drifts. This pattern ships NO new component: it documents how to compose an AntD Modal/Drawer/Popover with the shared Button, the AhaAlert banner, and useIsMobile() so dismiss behaviour, state reset, and error handling stay consistent and safe. The narrative 'why', worked examples, and full assertion set live in the aha-design-overlays skill; this artifact carries only what the repo can enforce, traced back by ref.

Based on

The rationale, worked examples, and the full assertion set live in the design skill — this pattern distils the enforceable subset and links each rule back to it.

build aha-design:aha-design-overlays judge aha-design:aha-design-overlays-judge

Choose the surface

SurfaceUse forExample
Modala focused decision or short flow that should block the pageconfirmation, destructive action, one-time config
Drawera detail view or medium-complexity panel that supplements the page without replacing ita record's report, distribution settings
Popovera small, anchored, contextual snippet — not multi-step flowsquick info, a compact input, an upsell

Composed of

What a compliant overlays surface reuses from this design system — the pattern's link into the component graph.

ReusesKindForIn DS?
buttoncomponentthe confirm/cancel CTAs; destructive confirm is the danger Button (okButtonProps danger + loading), danger on the button never the title/bodyavailable
iconcomponentthe X close glyph and footer action glyphs — functional icons only, via the shared Iconavailable
--aha-color-errortokendanger confirm colour — never hardcode a hexavailable
modalcomponentthe blocking-decision surface (confirmations, destructive actions) — AntD v6 Modal with mask/keyboard/destroyOnHidden props wired per these rulesmissing
drawercomponentthe detail/panel surface — AntD v6 Drawer with placement=right, responsive width, footer actions, destroyOnClosemissing
popovercomponentthe anchored contextual snippet surface — AntD v6 Popovermissing
alertcomponentthe inline in-overlay error banner (AhaAlert / DS V3 Alert, owned by aha-design-feedback) — never a bare AntD Alertmissing
⚠︎ 4 referenced components not yet in the DS — modal, drawer, popover, alert. composedOf marks 4 components this pattern needs that the DS does not yet ship (modal, drawer, popover, alert). Per the charter, a pattern that leans on a missing component is a signal to add it HERE — this is the pattern surfacing the component roadmap (the Alert overlaps aha-design-feedback's banner).

Rules

The shippable checklist — each rule traces to an assertion in aha-design:aha-design-overlays.

RuleSkill assertion
Pick the surface by the job: Modal for a blocking decision or short flow, Drawer for a detail/medium-complexity panel that supplements the page, Popover for a small anchored snippet — never a multi-step flow in a popover.OVERLAY-01
Destructive overlays MUST NOT dismiss on outside/mask click — set mask={{ closable: false }} on the Modal, leaving Esc (keyboard) and the X button as the only dismiss paths. Non-destructive overlays may allow mask-close.OVERLAY-02
While an async action is in flight (busy), disable cancel and suppress onCancel/onClose so the user cannot dismiss mid-operation.OVERLAY-03
Always set destroyOnHidden (Modal) / destroyOnClose (Drawer) so transient state — form input, error text, busy flag — resets between opens. If the instance can stay mounted, also clear local error and busy on open; don't rely on unmount alone.OVERLAY-04
Drawers default to placement="right" with responsive width width={isMobile ? '100%' : 720} via useIsMobile() — full-bleed on mobile, fixed panel on desktop. The width is a prop, not gated through CSS.OVERLAY-05
Put navigation/actions (prev/next, retry, position indicator) in the drawer footer, and use styles={{ body: { padding: 0 } }} when the body renders its own padded content.OVERLAY-05
For confirmations, the modal title is a question ("Delete this survey permanently?").OVERLAY-06
A destructive confirm uses okButtonProps={{ danger: true, loading: busy }} — the danger styling lives on the action button, never the title or body — and cancel is disabled while busy (cancelButtonProps={{ disabled: busy }}).OVERLAY-06
onConfirm returns a promise: guard re-entry (if (busy) return), set busy, clear prior error, then await the action. On rejection stay open, surface the error inline, and clear busy; on success the caller closes. Never close a destructive overlay before its action resolves, and never fire-and-forget.OVERLAY-07
Surface failures inside the overlay with an inline DS V3 Alert (AhaAlert type="error", styling owned by aha-design-feedback) — never a bare AntD Alert, never a nested modal stacked on top, never a downgrade to a transient toast. The overlay stays open on failure so the user can retry without re-opening.OVERLAY-08

Composition code

Doc-only — this pattern ships no wrapper from this repo. Doc-only pattern — ships no composition wrapper from this repo. The canonical overlay references live in the product (DeleteSurveyConfirmModal.tsx for the destructive modal, RespondentReportDrawer.tsx for the detail drawer); the AhaAlert banner is owned by aha-design-feedback and useIsMobile() by aha-design-shared-components. If a shared overlay wrapper graduates into this DS, add a reuse block and it is gated like a composite.

Guide

Overlays — composition guide

Distilled from aha-design-overlays. The DS now owns this build ruleset — it is the single source of truth for how an overlay is constructed, and the plugin skill is generated from it. The rationale, worked examples, and the judge/evals stay in the skill; this guide is the shippable checklist. When the two ever disagree, regenerate this file from the owning ruleset.

Overlays — modals, drawers, popovers — are transient surfaces layered over the page, and they are where a stray backdrop click can destroy data. They reuse existing components — the shared Button, the AhaAlert banner, useIsMobile() — so this pattern adds no new control; it defines the house rules that keep dismiss behaviour, state reset, and error handling consistent and safe across every AhaSlides product surface. Overlays are built on AntD v6 Modal / Drawer / Popover.

Pick the surface first

SurfaceUse for
ModalA focused decision or short flow that should block the page — confirmations, destructive actions, one-time config
DrawerA detail view or medium-complexity panel that supplements the page without fully replacing it — a record's report, distribution settings
PopoverA small, anchored, contextual snippet — quick info, a compact input, an upsell. Not for multi-step flows

Dismiss behaviour (safety-critical)

  • Destructive overlays MUST NOT dismiss on outside/mask click. Set mask={{ closable: false }} on the Modal. Keep keyboard (Esc) and the X button as the only dismiss paths, so a stray backdrop click can't trigger destruction.
  • Non-destructive overlays may allow mask-close.
  • While an async action is in flight (busy), disable cancel and suppress onCancel/onClose so the user can't dismiss mid-operation.

State reset

  • Always set destroyOnHidden (Modal) / destroyOnClose (Drawer) so transient state — form input, error text, busy flag — resets between opens.
  • On open, also clear local error and busy state explicitly — don't rely on unmount alone if the instance can stay mounted.

Drawer conventions

  • Default placement="right".
  • Responsive width: width={isMobile ? '100%' : 720} via useIsMobile() — full-bleed on mobile, fixed panel on desktop. Do not gate this through CSS; the width is a prop.
  • Put navigation/actions (prev/next, retry, position indicator) in the drawer footer.
  • Use styles={{ body: { padding: 0 } }} when the body renders its own padded content.

Modal conventions

  • For confirmations, the title is a question — "Delete this survey permanently?".
  • Destructive confirm button: okButtonProps={{ danger: true, loading: busy }} — the danger styling lives on the action button, never the title or body.
  • Disable cancel while busy: cancelButtonProps={{ disabled: busy }}.

Async confirm pattern

onConfirm returns a promise. The overlay:

1. Guards re-entry (if (busy) return), sets busy, clears prior error. 2. awaits the action. 3. On rejection, stays open and surfaces the error inline (see below), then clears busy. 4. On success, the caller closes the overlay.

Never close a destructive overlay before its action resolves, and never fire-and-forget.

Errors stay inline

  • Surface failures inside the overlay with an inline DS V3 Alert (AhaAlert type="error" — the banner component and its styling are owned by aha-design-feedback; do not drop in a bare AntD <Alert>). This pattern owns *where* the error goes (inline, in the overlay); feedback owns *what it looks like*.
  • Never stack a second modal on top, and never downgrade the error to a transient toast.
  • The overlay remains open on failure so the user can retry without re-opening.

*Full detail, the DeleteSurveyConfirmModal / RespondentReportDrawer references, and the

OVERLAY-01..08 assertions live in the aha-design-overlays skill. Self-check any built overlay with aha-design-overlays-judge and fix every FAIL before shipping.*