Modals and exit panels
1) When to use what
Modal (dialogue with the backdrop) - for critical solutions and short tasks that require full attention: confirmation of action, legal consent, dangerous operations, short forms ≤ 1-2 fields. Locks the background.
Drawer/Sheet (pull-out panel) - for in-place extension: object details, attribute editing, selection from the list, auxiliary navigation. The background is visible → the context is preserved.
- If the action needs concentration and confirmation → Modal.
- When to save the context and give a "parallel" overview of the → Drawer.
2) Structure and dimensions
Modal
Title (required) → body → CTA (Primary/Secondary/Destructive).
Dimensions: S (480-560 px), M (640-720 px), L (≤ 840 px). On the mobile - full-screen sheet.
Drawer / Sheet
Direction: right edge (desktop, editing), bottom (mobile, actions), sometimes left (navigation).
Width: 360-480 (S), 480-640 (M), 640-800 (L) On mobile: 90-100% width/height.
The height of the content is always limited, scrolling inside; header/STA are fixed.
3) Copywriting and CTA
Title = Action/Meaning: Confirm Rate, Select Payment Method, KYC Required.
Short text, 1-2 sentences. Avoid vague formulas.
CTA: one Primary, next Secondary ("Cancellation") and, if necessary, Destructive.
For risky actions, add a 1-line explanation: "The action is irreversible. You will be able to cancel within 10 seconds (if available)"
4) Behavior and states
Opening: instant response ≤ 100 ms, then animation 120-180 ms.
Closing: faster than opening (80-140 ms), return focus to the trigger.
Busy: 'aria-busy = "true"' on container, button with retry lock.
Unsaved (dirty form): at closing - dialog-warning ("There are unsaved changes").
Escape/click on background: acceptable for non-dangerous dialogs; for critical - only explicit buttons.
5) Availability (A11y)
Container: 'role = "dialog"' and 'aria-modal = "true"' (for a real modal).
The title is linked via 'aria-labelledby'; description - 'aria-descripedby'.
Focus trap within; the primary focus is on the header or the first interactive item.
Returns focus to the original trigger after closing.
No scrolling background: 'document. body {overflow: hidden;} 'or' inert 'on the rest of the DOM.
Keyboard support: Tab/Shift + Tab are cyclical; Esc closes (unless scripted prohibited).
Consider'prefers-reduced-motion ': disabling/simplifying animations.
html
<div class="backdrop" data-open hidden></div>
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="d-title" aria-describedby="d-desc" hidden>
<h2 id =" d-title "> Confirm Bid </h2>
<p id =" d-desc "> Sum of 200 ₴ by factor 1. 85</p>
<div class="actions">
<button class =" btn btn--primary "> Confirm </button>
<button class =" btn btn--ghost "> Cancel </button>
</div>
</div>
6) Performance and architecture
Rendering through a portal (layer on top of an application) → fewer z-index problems.
Mount content lazily when first opened, unmount after closing animation (or translate offscreen).
Animate only 'transform/opacity'; avoid expensive blur/oversized shadows.
Lock the background scroll (scroll-lock), keep the current position so that it does not "jump" after closing.
For large lists in drawer - use virtualization.
7) Mobile patterns
Bottom sheet for quick actions/confirmations: swipe down gestures to close (with threshold).
Sticky-CTA at the bottom; close button - from the top left.
Safe-area indents (notch/gesture areas).
The on-screen keyboard should not overlap the CTA; layout - "lifting" content or a fixed panel above the keyboard.
8) Motion design
Input: fade + light shift (modal: along Y, drawer: along the axis of appearance). 120-180 ms.
Output: shorter (80-140 ms), easing'cubic-bezier (0. 2,0,0. 2,1)`.
Background-Opacity is 0 → 0. 4–0. 6. Without pulsations and endless glare.
For 'prefers-reduced-motion': no shift, only fade.
9) Closing management
Immediate closure only for safe operations.
If there is an error, we stay in the dialogue, show the reason and Retry.
In background execution - close the dialog and show the toast "We execute in the background...," plus the "History" section.
10) Typical iGaming Scenarios
10. 1 Bid confirmation (Modal)
Content: event, coefficient, amount, potential gain, coefficient validity period.
Buttons: "Confirm" (primary), "Cancel."
Delay pattern> 3 s: text "Waiting for confirmation..."; if the coefficient changes, an honest update.
10. 2 Cashout (Modal/Sheet)
Displays the current cashout amount and window timer.
Confirmation + possible Undo (if regulation allows).
10. 3 Choosing a payment method (Drawer)
List of methods with commissions/ETA; Selects the → of the mini form.
Saving the default method; Return without loss of input
10. 4 KYC (Drawer → Modal)
Drawer for loading documents/prompts.
Modal when trying to close with incomplete load: warning about unsaved.
10. 5 Responsible play limits (Modal)
Radio "Day/Week/Month," amount field, line "Will take effect in...."
11) Anti-patterns
Nested modals (modal over modal). Use a single dialog or step sequence.
Modalka for regular viewing of content (better drawer/page).
Hidden cross or closing only by "microzone."
Risky action → permission to close "by background."
Long shape in a modal (move the → into a separate screen/panel).
No focus return to trigger.
12) Design system tokens (example)
json
{
"dialog": {
"radius": 12,
"shadow": "var(--elev-4)",
"sizes": { "s": 520, "m": 680, "l": 840 },
"backdropOpacity": 0. 5,
"padding": "20 24",
"gap": 16
},
"drawer": {
"width": { "s": 360, "m": 480, "l": 640 },
"edge": "right",
"radius": 12,
"shadow": "var(--elev-4)"
},
"motion": {
"inMs": 160,
"outMs": 120,
"ease": "cubic-bezier(0. 2,0,0. 2,1)",
"reduce": true
},
"a11y": {
"useAriaModal": true,
"focusTrap": true,
"returnFocus": true
}
}
CSS presets (concept):
css
.backdrop[data-open]{position:fixed; inset:0; background:rgba(0,0,0,.5); backdrop-filter:saturate(80%); opacity:1; transition:opacity. 16s}
.dialog,[data-drawer]{position:fixed; background:var(--bg-elevated); border-radius:12px; box-shadow:var(--elev-4);}
.dialog{inset:auto; left:50%;top:50%;transform:translate(-50%,-50%); max-width:840px; width:min(100% - 32px, var(--dialog-w,680px));}
[data-drawer="right"]{top:0; right:0; height:100%;width:var(--drawer-w,480px); transform:translateX(0)}
.dialog[hidden],.backdrop[hidden]{display:none}
13) Snippets of behavior
Focus trap + focus return:js const openBtn = document. getElementById('open');
const dlg = document. querySelector('.dialog');
let prevFocus;
function openDialog() {
prevFocus = document. activeElement;
dlg. hidden = false; document. body. style. overflow = 'hidden';
const focusable = dlg. querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
(focusable[0] dlg). focus();
function onKey(e){
if(e. key==='Escape') return closeDialog();
if(e. key!=='Tab') return;
const first = focusable[0], last = focusable[focusable. length-1];
if(e. shiftKey && document. activeElement===first){ e. preventDefault(); last. focus(); }
else if(!e.shiftKey && document. activeElement===last){ e. preventDefault(); first. focus(); }
}
dlg. addEventListener('keydown', onKey);
dlg. dataset. off = ()=> dlg. removeEventListener('keydown', onKey);
}
function closeDialog() {
dlg. dataset. off && dlg. dataset. off();
dlg. hidden = true; document. body. style. overflow = '';
prevFocus && prevFocus. focus();
}
Sheet with closing gesture (mobile, simplified):
js let startY=0, delta=0;
const sheet = document. querySelector('.sheet');
sheet. addEventListener('touchstart', e => startY = e. touches[0].clientY);
sheet. addEventListener('touchmove', e => {
delta = Math. max(0, e. touches[0].clientY - startY);
sheet. style. transform = `translateY(${delta}px)`;
});
sheet. addEventListener('touchend', () => {
if (delta > 120) sheet. classList. remove('open'); else sheet. style. transform = '';
delta = 0;
});
14) Metrics and experiments
Open Rate/Completion Rate by modal: how many opened and completed the action.
Time-to-Decision: From opening to clicking on Primary.
Dismiss Rate and reasons (Esc/background close vs "Cancel").
Error/Retry Rate in busy scripts.
A/B: modal vs drawer, CTA text, field order, "confirm" vs "undo."
15) QA checklist
Availability
- 'role = "dialog"', 'aria-modal = "true"', correct 'aria-labelledby/-describedby'.
- Focus trap works; focus returns to the trigger.
- Esc closes (if allowed); Tab is cyclical.
- Contrast ≥ AA; it's not just color that conveys meaning.
Behavior
- TTFF ≤ 100 ms; animation in 120-180 ms/out 80-140 ms.
- Scroll-lock the background without "jumping" the page.
- Unsaved-guard in dirty form.
- Busy state, correct Retry/errors.
Interface
- Clear header and one Primary-CTA.
- A cross/close button is available.
- Dimensions are adaptive; on the mobile - sheet with a gesture.
Performance
- Portals/z-index are correct; without "transmission."
- Lazy initialization; only transform/opacity are animated.
16) Documentation in the design system
Components: 'Modal', 'Drawer/Sheet', 'ConfirmDialog', 'UnsavedGuard'.
Tokens: dimensions, indents, shadows, animations, backdrop, focus-ring.
Guides: "When modal vs drawer," copywriting patterns, risky actions (confirm/undo), scroll-lock and portals, reduce-motion.
Do/Don't gallery: nested modals (don't), long forms in modal (don't), sheet for expanding context (do).
Brief Summary
Modalka - for decisions under full attention, drawer - to expand the context without breaking the flow. Keep the structure simple, CTA unambiguous, and interactions predictable and accessible. Respect the performance, lock the background and return focus. In iGaming scenarios, this directly affects trust: bet confirmations, cashout, payment method selection, and KYC must be fair, fast, and secure.