Build a Priority-Based Modals system
Extends the modal manager: each enqueued modal has a priority (number or enum). Modals queue but only the highest-priority one is shown; a new higher-priority modal preempts the current one (or stacks on top depending on policy). Useful for cohesive UX when multiple systems want to show modals (auth, billing, onboarding, errors).
A priority-based modal system avoids the bug where three independent systems each open() a modal and the user sees a stack of unrelated dialogs. It coordinates which modal is visible right now based on priority and policy.
1. The shape of the problem
Multiple unrelated callers want to show modals:
- Onboarding tooltip.
- Billing "card expiring" warning.
- Auth re-login prompt.
- Critical error.
Without coordination: the user gets all four stacked. With priority: only the most important shows now; the rest queue and surface as the user dismisses each.
2. Priority model
const Priority = { LOW: 0, NORMAL: 1, HIGH: 2, CRITICAL: 3 };Or just numbers. Document the meaning.
3. The store
class ModalQueue {
constructor() {
this.queue = []; // pending: { id, priority, content, options }
this.current = null; // currently shown
this.subscribers = new Set();
}
open({ content, priority = 0, options = {} }) {
const id = crypto.randomUUID();
const item = { id, content, priority, options };
// Decide policy: preempt or queue
if (!this.current) {
this.current = item;
} else if (priority > this.current.priority) {
this.queue.push(this.current); // demote current
this.current = item;
} else {
this.queue.push(item);
}
this.queue.sort((a, b) => b.priority - a.priority);
this._notify();
return id;
}
dismiss(id) {
if (this.current?.id === id) {
this.current = this.queue.shift() ?? null;
} else {
this.queue = this.queue.filter((q) => q.id !== id);
}
this._notify();
}
subscribe(fn) { this.subscribers.add(fn); return () => this.subscribers.delete(fn); }
_notify() { for (const fn of this.subscribers) fn(this.current); }
}4. Two policies — preempt vs stack
- Preempt (above): higher priority replaces the current modal; the previous one re-queues for later.
- Stack alternative: higher priority renders on top; the previous stays underneath. Use when the lower modal is contextually important.
Make it an option per open:
open({ ..., policy: "preempt" | "stack" })5. React integration
const ModalCtx = createContext(null);
const queue = new ModalQueue();
export function ModalProvider({ children }) {
const [current, setCurrent] = useState(queue.current);
useEffect(() => queue.subscribe(setCurrent), []);
return (
<ModalCtx.Provider value={queue}>
{children}
{current && <ModalShell onClose={() => queue.dismiss(current.id)}>{current.content}</ModalShell>}
</ModalCtx.Provider>
);
}6. Rules for callers
- Pick a priority deliberately, not "always HIGH".
- CRITICAL is for things that block the user from continuing safely (session expired, payment failed during checkout).
- Avoid showing low-priority modals after route changes — they'll feel disconnected.
7. Extras
- Suppress while user is in a flow (e.g., typing in a form) — defer non-CRITICAL modals.
- Dedupe — if the same modal is opened twice, ignore the second.
- Expiry — drop low-priority modals from the queue after N minutes.
- Logging — record which modals queued behind which, helps detect modal spam.
8. Accessibility / UX
- Focus management when a modal preempts another — return focus to the trigger of whichever closes last.
- Don't auto-dismiss a low-priority modal that was visible just because a higher one arrives — re-queue it.
- Announce the new modal via the dialog ARIA roles (the dialog itself handles this — see [[build-a-modal-management-system]]).
Interview framing
"Wrap the modal store with a priority queue. open checks the current modal's priority: higher priority preempts (push current back into the queue, sorted), equal-or-lower queues. Dismiss promotes the next-highest queued modal to current. Make the policy (preempt vs stack on top) an option. Add dedupe, suppression during user flows, and expiry for low-priority items. The point is coordination — without it, every independent system opens its own modal and the user sees four unrelated dialogs at once."
Follow-up questions
- •Preempt vs stack — when do you choose each?
- •How do you prevent CRITICAL-modal abuse?
- •How does this integrate with the basic modal manager?
- •What's the right deduplication strategy?
Common mistakes
- •Showing every modal opened, regardless of context.
- •No re-queue when preempted — modal is lost.
- •Treating priority as a quality signal — everyone marks theirs HIGH.
- •Allowing low-priority modals to interrupt critical user flows.
Performance considerations
- •Tiny queue; sort cost is negligible. Subscribe/unsubscribe correctly to avoid leaks.
Edge cases
- •Two CRITICAL modals open simultaneously.
- •Preemption mid-animation.
- •Page navigation with a queued modal (drop or persist?).
- •Stale queued modals (expire after N min).
Real-world examples
- •Onboarding tour, billing dunning, system-wide notifications, A/B-test prompts.