DEV Community

Cover image for Lead Frontend Interview Experience: Designing a Global Modal Architecture
Shri Shah
Shri Shah

Posted on

Lead Frontend Interview Experience: Designing a Global Modal Architecture

I Was Asked to Build a Global Modal System in a Lead Frontend Interview — Here’s How I Designed It

In a recent interview for a Lead Frontend Engineer role, I got a live system design + implementation challenge:

“Design a global modal architecture that can be used from anywhere in the app.”

What looked like a simple UI task quickly became a great discussion on architecture, state boundaries, accessibility, and API design.

I was also asked to write custom CSS for the modal UI, so the exercise covered both behavior and presentation.

This post is a breakdown of my approach, the follow-up questions I got, and the refinements I made afterward.


The Requirements I Wrote Down First

Before coding, I confirmed the contract:

  1. Open a modal from anywhere in the app.
  2. Close an open modal.
  3. Support stacking multiple modals.
  4. Keep last-opened modal active; when it closes, reveal the previous one.

One thing I intentionally added was custom content support. It was not explicitly stated, but I read between the lines and treated it as an implicit requirement for a reusable modal system.

That gave me a clear target and helped keep implementation decisions focused.


Architecture Choice: Context + Provider + Portal

I used a global ModalProvider and exposed a useModal() hook through React Context.

// src/contexts/ModalContext.tsx
const ModalContext = createContext<ModalContextType | undefined>(undefined)

export function useModal(): ModalContextType {
  const context = useContext(ModalContext)
  if (context === undefined) {
    throw new Error('useModal must be used within a ModalProvider')
  }
  return context
}
Enter fullscreen mode Exit fullscreen mode

At the app root, I mounted the provider and portal once:

// src/routes/__root.tsx
<ModalProvider>
  {children}
  <ModalPortal />
</ModalProvider>
Enter fullscreen mode Exit fullscreen mode

This gives every route/component access to openModal and closeModal without prop drilling.


Why Context Was the Right Fit (Not Redux)

One of the strongest interview discussions was around state-management tradeoffs.

I explained why Context was a better fit than Redux/external stores for this specific problem:

  • Modal state is UI-local to the React tree.
  • Updates are event-based and low frequency (open/close), not high-throughput.
  • Scope is narrow (modal stack + lifecycle handlers).
  • Consumers are known and contained under one provider.

Using Redux here would add ceremony (store setup, actions, reducers, selectors, middleware decisions) without meaningful upside.

My framing was simple: pick the least complex abstraction that fully satisfies current requirements.


Modal API Design: Flexible but Controlled

I kept openModal composable by accepting custom content and optional callbacks:

Even though custom content was not directly requested, I considered it part of solving the real problem (a globally reusable modal, not a one-off dialog).

// src/contexts/ModalContext.tsx
export interface OpenModalOptions {
  title: string
  content: ReactNode
  onClose?: () => void
  onConfirm?: () => void | Promise<void>
}
Enter fullscreen mode Exit fullscreen mode

Example usage:

// src/routes/index.tsx
openModal({
  title: 'Modal Example',
  content: <p>This is a modal.</p>,
})
Enter fullscreen mode Exit fullscreen mode

This supports plain text, rich JSX, and feature-specific components without changing modal internals.


Closing Strategy: Top-Only by Default, ID-Based When Needed

I implemented close behavior with two modes:

  • closeModal() → closes the top modal
  • closeModal(id) → closes a specific modal
// src/contexts/ModalContext.tsx
const closeModal = (id?: string): void => {
  let modalToClose: Modal | undefined

  setModals((prevModals) => {
    if (prevModals.length === 0) return prevModals

    if (!id) {
      modalToClose = prevModals[prevModals.length - 1]
      return prevModals.slice(0, -1)
    }

    const updatedModals = prevModals.filter((m) => {
      const shouldKeep = m.id !== id
      if (!shouldKeep) modalToClose = m
      return shouldKeep
    })

    return updatedModals.length === prevModals.length ? prevModals : updatedModals
  })

  modalToClose?.onClose?.()
}
Enter fullscreen mode Exit fullscreen mode

I also wired Escape and backdrop-click close only for the active modal.


Stacking Behavior: Array as LIFO Stack

To support multiple modals, I modeled state as modals: Modal[] and append on open.

// src/contexts/ModalContext.tsx
const [modals, setModals] = useState<Modal[]>([])

const openModal = (options: OpenModalOptions): string => {
  const id = crypto.randomUUID()
  const newModal: Modal = { ...options, id }
  setModals((prevModals) => [...prevModals, newModal])
  return id
}
Enter fullscreen mode Exit fullscreen mode

In the portal, index === modals.length - 1 determines the active modal and z-order.


Follow-Up Question: “What If Consumers Need Extra Actions?”

Toward the end, I got this question:

“What if users want to dispatch additional actions on close or confirm?”

That’s exactly why I added optional callbacks:

  • onClose: analytics, cleanup, UI sync actions
  • onConfirm: sync/async domain actions, then close flow

This keeps modal infrastructure generic while letting product features inject business behavior at call sites.


Accessibility Follow-Up: Focus Trapping

I was also asked how I’d prevent keyboard focus from escaping the modal.

I implemented focus trapping for Tab / Shift+Tab, and restored focus to the previously focused element when the modal unmounts.

This significantly improves keyboard UX and accessibility compliance for stacked dialogs.


What I Refined After the Interview

I had extra time afterward, so I polished the solution further:

  • clarified API boundaries
  • tightened callback behavior
  • improved accessibility details

That post-interview refinement made the implementation feel more production-ready than interview-only.


Final Takeaway

My core principle was to treat modals as app infrastructure, not one-off UI widgets:

  • globally accessible API
  • predictable stack semantics
  • composable content model
  • accessibility-first behavior

If you’d review this architecture differently, I’d love to hear what you would change.


Final Implementation

You can explore the full implementation here:

https://codesandbox.io/p/github/shrinivasshah/modal-stack-lld/main

Top comments (0)