Modal & Popup Implementation with Focus Trap and Animation

Implementing Modal/Popup Windows on a Website

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1414
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    982
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    995

Implementing Modal/Popup Windows on a Website

While developing a corporate portal on React, we encountered a problem: after opening a confirmation modal for deleting a record, the background scroll kept working, and focus shifted to a button at the bottom of the page. This led to user errors — they accidentally deleted data without seeing the confirmation. Conversion on the target action dropped by 12% in the first week. We had to rewrite the entire system from scratch using native <dialog> and custom focus trap. Now we implement modals turnkey, solving these issues at the architecture level. Experience from over 50 projects shows that proper modal implementation saves up to 40% of UI debugging time and reduces incidents by 25%. Average support budget savings are around 30%.

Why Native <dialog> Is Better Than Custom Solutions?

Many developers create modals with divs from scratch, forgetting about accessibility and focus management. Native <dialog> with showModal() automatically blocks the background, traps focus, and supports closing with Escape. Our experience shows that this approach cuts development time by half compared to custom solutions. Moreover, native dialog ensures correct behavior with screen readers and requires no additional libraries.

Parameter Native <dialog> Custom <div>
Focus trap Built into showModal() Needs manual implementation
Scroll lock Automatic via ::backdrop Requires overflow: hidden on body
Accessibility ARIA support, role=dialog Must add roles and attributes
Nested modals Stack managed natively Own stack manager needed
Animation Via CSS animation on elements Same

How to Implement Focus Trap Without Native Dialog?

If a project requires a custom solution (e.g., due to design system constraints), the focus trap is implemented manually. We collect all focusable elements inside the modal and intercept Tab/Shift+Tab. On open, we save the previous active element so we can return focus on close. In React, this is conveniently wrapped in a useFocusTrap hook.

Implementing a Modal in React: Step-by-Step

  1. Create a Modal component that accepts isOpen, onClose, title, children, and other props.
  2. Use createPortal to render the modal in body.
  3. Inside useEffect, open and close the native <dialog> via showModal()/close().
  4. Manage overflow: hidden on body when open.
  5. Implement focus trap: save the previous active element and return focus on close.
  6. Add backdrop click handling to close.
  7. Apply CSS animations via keyframes.

Example component:

import { useEffect, useRef, ReactNode } from 'react' import { createPortal } from 'react-dom' interface ModalProps { isOpen: boolean onClose: () => void title?: string children: ReactNode size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' closeOnBackdrop?: boolean } export function Modal({ isOpen, onClose, title, children, size = 'md', closeOnBackdrop = true, }: ModalProps) { const dialogRef = useRef<HTMLDialogElement>(null) const previousFocusRef = useRef<HTMLElement | null>(null) useEffect(() => { const dialog = dialogRef.current if (!dialog) return if (isOpen) { previousFocusRef.current = document.activeElement as HTMLElement dialog.showModal() document.body.style.overflow = 'hidden' } else { dialog.close() document.body.style.overflow = '' previousFocusRef.current?.focus() } }, [isOpen]) useEffect(() => { const dialog = dialogRef.current const handleClose = () => onClose() dialog?.addEventListener('close', handleClose) return () => dialog?.removeEventListener('close', handleClose) }, [onClose]) function handleBackdropClick(e: React.MouseEvent<HTMLDialogElement>) { if (!closeOnBackdrop) return const rect = dialogRef.current!.getBoundingClientRect() if ( e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom ) { onClose() } } return createPortal( <dialog ref={dialogRef} className={`modal modal--${size}`} onClick={handleBackdropClick} aria-labelledby={title ? 'modal-title' : undefined} > <div className="modal__content" onClick={e => e.stopPropagation()}> {title && ( <div className="modal__header"> <h2 id="modal-title" className="modal__title">{title}</h2> <button className="modal__close" onClick={onClose} aria-label="Close"> <svg viewBox="0 0 24 24" width="20" height="20"> <path d="M6 6l12 12M18 6l-12 12" stroke="currentColor" strokeWidth="2"/> </svg> </button> </div> )} <div className="modal__body">{children}</div> </div> </dialog>, document.body ) } 

Common Mistakes in Modal Implementation

  1. Ignoring scroll lock — the background scrolls under the open modal. Solution: set overflow: hidden on <body> when opening and remove on close. When using native <dialog>, this doesn't happen automatically, so add it manually.
  2. Losing focus — after closing the modal, focus doesn't return to the element that triggered it. In the React component above, previousFocusRef is saved to correct this.
  3. Incorrect behavior on mobile — standard <dialog> doesn't look like a bottomsheet. Use media queries to make the modal slide up from the bottom on mobile.

Mobile Adaptation: Bottomsheet

On mobile devices, bottomsheets are common — the modal slides up from the bottom. In CSS, this is done with a media query: on screens up to 768px wide, change the animation to translateY and round only the top corners. Also account for the browser address bar offset using dvh for height.

What's Included in Modal Implementation?

  • API documentation for the component
  • Source code in TypeScript/React/Vue
  • Testing on desktop and mobile (iOS, Android)
  • Analytics integration (display triggers)
  • Post-deployment support (1 month)

Time Estimates

Stage Time
Basic implementation (native <dialog>) 3–4 hours
React component with portal and focus trap 1 day
System with modal stack and bottomsheet 1.5–2 days
Full WCAG adaptation +0.5 day

We have over 7 years of interface development experience and more than 50 projects with modal windows. We guarantee cross-browser compatibility and accessibility. Order a consultation on modal implementation for your project — we'll find the optimal solution. Contact us to discuss the details.