{"id":1636,"date":"2025-03-06T14:00:00","date_gmt":"2025-03-06T12:00:00","guid":{"rendered":"https:\/\/accessdrum.com\/?p=1636"},"modified":"2026-01-23T10:18:35","modified_gmt":"2026-01-23T08:18:35","slug":"tips-tricks-thursday-09-trap-focus-in-modals-code-improvements","status":"publish","type":"post","link":"https:\/\/accessdrum.com\/bg\/tips-tricks-thursday-09-trap-focus-in-modals-code-improvements\/","title":{"rendered":"Tips &#038; Tricks Thursday 09: Trap Focus in Modals Code Improvements"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" style=\"font-size:28px\">Trap Focus in Modals and Pop-Ups<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"margin-top:0;margin-bottom:0;line-height:1.4\"><strong>Focus Management<\/strong>: When a modal or pop-up opens, trap the focus within it until the user closes it. This prevents users from inadvertently navigating to elements behind the modal.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code><div id=\"modal\" role=\"dialog\" aria-labelledby=\"modal-title\" aria-describedby=\"modal-description\" aria-modal=\"true\">\n  <h2 id=\"modal-title\">Modal Title<\/h2>\n  <p id=\"modal-description\">This is a modal dialog.<\/p>\n  <button id=\"close-modal\">Close<\/button>\n<\/div>\n\n<script>\n  const modal = document.getElementById('modal');\n  const focusableElements = modal.querySelectorAll('button, &#91;href], input, select, textarea, &#91;tabindex]:not(&#91;tabindex=\"-1\"])');\n  const firstFocusableElement = focusableElements&#91;0];\n  const lastFocusableElement = focusableElements&#91;focusableElements.length - 1];\n\n  document.addEventListener('keydown', function(event) {\n    if (event.key === 'Tab') {\n      if (event.shiftKey) {\n        \/\/ Shift + Tab\n        if (document.activeElement === firstFocusableElement) {\n          event.preventDefault();\n          lastFocusableElement.focus();\n        }\n      } else {\n        \/\/ Tab\n        if (document.activeElement === lastFocusableElement) {\n          event.preventDefault();\n          firstFocusableElement.focus();\n        }\n      }\n    }\n  });\n\n  \/\/ Set initial focus\n  firstFocusableElement.focus();\n<\/script><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\" style=\"line-height:1.4\">The sample code is a solid start in terms of structure, but there are a few areas where improvements can be made.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" style=\"font-size:28px\">Key Areas for Improvement:<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">1. Handle Focus on Modal Open\/Close<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"margin-top:0;margin-bottom:0;line-height:1.4\">When the modal opens, you should store the element that had focus prior to the modal being opened. This ensures that when the modal is closed, focus is returned to the previous element (usually the button that triggered the modal).<\/li>\n\n\n\n<li style=\"margin-top:0;margin-bottom:0;line-height:1.4\">When the modal closes, focus should be restored to that previously focused element.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code>let previouslyFocusedElement;\n\nfunction openModal() {\n  previouslyFocusedElement = document.activeElement; \/\/ Store the previously focused element\n  modal.style.display = 'block'; \/\/ Make modal visible\n  firstFocusableElement.focus(); \/\/ Focus the first focusable element inside the modal\n}\n\nfunction closeModal() {\n  modal.style.display = 'none'; \/\/ Hide modal\n  previouslyFocusedElement.focus(); \/\/ Return focus to the previously focused element\n}\n\ndocument.getElementById('close-modal').addEventListener('click', closeModal);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">2. Handle Focus for Dynamically Loaded Content<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"margin-top:0;margin-bottom:0;line-height:1.4\">If the modal contains dynamically loaded content (like form elements), you should re-query the <code>focusableElements<\/code> each time the modal opens, in case new elements are added or removed from the DOM<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code>function updateFocusableElements() {\n  focusableElements = modal.querySelectorAll('button, &#91;href], input, select, textarea, &#91;tabindex]:not(&#91;tabindex=\"-1\"])');\n  firstFocusableElement = focusableElements&#91;0];\n  lastFocusableElement = focusableElements&#91;focusableElements.length - 1];\n}\n\n\/\/ Update focusable elements each time the modal is opened\nopenModal = function() {\n  updateFocusableElements();\n  \/\/ Rest of the code\n};<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">3. Close Modal with Escape Key<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"line-height:1.4\">Users should be able to close modals using the <code>Escape<\/code> key. This is a standard accessibility practice for modals and dialogs.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code>document.addEventListener('keydown', function(event) {\n  if (event.key === 'Escape') {\n    closeModal(); \/\/ Close modal when 'Escape' key is pressed\n  }\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">4. Ensure <code>aria-hidden<\/code> and Inert State for Background Elements<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"line-height:1.4\">While the modal is open, you should ensure that the rest of the page content is inaccessible to screen readers and keyboard users. This can be done using <code>aria-hidden=\"true\"<\/code> on the background elements or applying the <code>inert<\/code> attribute, if supported<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code>const backgroundContent = document.querySelector('main'); \/\/ Assuming main contains the background content\n\nfunction openModal() {\n  previouslyFocusedElement = document.activeElement;\n  backgroundContent.setAttribute('aria-hidden', 'true'); \/\/ Hide the background content from screen readers\n  modal.style.display = 'block';\n  firstFocusableElement.focus();\n}\n\nfunction closeModal() {\n  modal.style.display = 'none';\n  backgroundContent.removeAttribute('aria-hidden');\n  previouslyFocusedElement.focus();\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">5. <code>Tabindex<\/code> Management and Focus Safeguarding<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li style=\"line-height:1.4\">Ensure the modal itself (or its container) has <code>tabindex=\"-1\"<\/code> so it can receive focus if needed. This ensures that users relying on assistive technologies can perceive the modal as the currently active region.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);line-height:1.4\"><strong>Example<\/strong>:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code><div id=\"modal\" role=\"dialog\" aria-labelledby=\"modal-title\" aria-describedby=\"modal-description\" aria-modal=\"true\" tabindex=\"-1\">\n  <!-- Modal content -->\n<\/div><\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"block-71284d13-dd63-4ed0-81f9-2f3829bdfba6\" style=\"font-size:24px\">6. Consider Focus Wrap Behavior on Elements Not Yet in DOM<\/h3>\n\n\n\n<ul id=\"block-90a6a2f9-7f99-4e69-8ed0-1c33babb4cd3\" class=\"wp-block-list\">\n<li>If you have elements that are added to the DOM after the modal opens, like via AJAX or dynamic rendering, be sure to update the focus-trap logic dynamically. The <code>focusableElements<\/code> list should be recalculated whenever the content changes.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" style=\"font-size:28px\">Final Example<\/h2>\n\n\n\n<pre class=\"wp-block-code\" style=\"line-height:1.3\"><code><div id=\"modal\" role=\"dialog\" aria-labelledby=\"modal-title\" aria-describedby=\"modal-description\" aria-modal=\"true\" tabindex=\"-1\" style=\"display: none;\">\n  <h2 id=\"modal-title\">Modal Title<\/h2>\n  <p id=\"modal-description\">This is a modal dialog.<\/p>\n  <button id=\"close-modal\">Close<\/button>\n<\/div>\n\n<script>\n  const modal = document.getElementById('modal');\n  let previouslyFocusedElement;\n  let focusableElements;\n  let firstFocusableElement;\n  let lastFocusableElement;\n\n  function updateFocusableElements() {\n    focusableElements = modal.querySelectorAll('button, &#91;href], input, select, textarea, &#91;tabindex]:not(&#91;tabindex=\"-1\"])');\n    firstFocusableElement = focusableElements&#91;0];\n    lastFocusableElement = focusableElements&#91;focusableElements.length - 1];\n  }\n\n  function openModal() {\n    previouslyFocusedElement = document.activeElement;\n    updateFocusableElements();\n    modal.style.display = 'block';\n    firstFocusableElement.focus();\n    document.querySelector('main').setAttribute('aria-hidden', 'true'); \/\/ Assuming 'main' contains the background content\n  }\n\n  function closeModal() {\n    modal.style.display = 'none';\n    document.querySelector('main').removeAttribute('aria-hidden');\n    previouslyFocusedElement.focus();\n  }\n\n  document.getElementById('close-modal').addEventListener('click', closeModal);\n\n  document.addEventListener('keydown', function(event) {\n    if (event.key === 'Escape') {\n      closeModal(); \/\/ Close modal on Escape\n    }\n    if (event.key === 'Tab') {\n      if (event.shiftKey) {\n        \/\/ Shift + Tab\n        if (document.activeElement === firstFocusableElement) {\n          event.preventDefault();\n          lastFocusableElement.focus();\n        }\n      } else {\n        \/\/ Tab\n        if (document.activeElement === lastFocusableElement) {\n          event.preventDefault();\n          firstFocusableElement.focus();\n        }\n      }\n    }\n  });\n\n  \/\/ Example of opening modal (could be tied to a button click event)\n  openModal();\n<\/script><\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" style=\"font-size:24px\">Summary of Improvements<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Restoring focus<\/strong> to the previously focused element when the modal is closed.<\/li>\n\n\n\n<li><strong>Handling dynamically loaded content<\/strong> by updating focusable elements each time the modal opens.<\/li>\n\n\n\n<li><strong>Support for Escape key<\/strong> to allow users to close the modal using the keyboard.<\/li>\n\n\n\n<li><strong><code>aria-hidden<\/code> or <code>inert<\/code><\/strong> attribute for the background content while the modal is open, making sure it&#8217;s inaccessible to screen readers.<\/li>\n\n\n\n<li style=\"margin-top:0;margin-bottom:0\"><strong><code>Tabindex<\/code> and focus management<\/strong> to ensure that the modal can receive focus and that focus is trapped within it while it&#8217;s open.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Trap Focus in Modals and Pop-Ups The sample code is a solid start in terms of structure, but there are [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":1557,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[610,609,13],"tags":[206,207],"class_list":["post-1636","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-for-content-creators","category-for-developers","category-tips","tag-tips-tricks","tag-tips-tricks-thursday"],"uagb_featured_image_src":{"full":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday.webp",1024,1024,false],"thumbnail":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday-150x150.webp",150,150,true],"medium":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday-300x300.webp",300,300,true],"medium_large":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday-768x768.webp",768,768,true],"large":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday.webp",1024,1024,false],"1536x1536":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday.webp",1024,1024,false],"2048x2048":["https:\/\/accessdrum.com\/wp-content\/uploads\/2025\/01\/DALL\u00b7E-2025-01-28-14.10.23-Tips-Tricks-Thursday.webp",1024,1024,false]},"uagb_author_info":{"display_name":"Angel Petrov","author_link":"https:\/\/accessdrum.com\/bg\/author\/angel-petrov\/"},"uagb_comment_info":0,"uagb_excerpt":"Trap Focus in Modals and Pop-Ups The sample code is a solid start in terms of structure, but there are [&hellip;]","_links":{"self":[{"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/posts\/1636","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/comments?post=1636"}],"version-history":[{"count":5,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/posts\/1636\/revisions"}],"predecessor-version":[{"id":1925,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/posts\/1636\/revisions\/1925"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/media\/1557"}],"wp:attachment":[{"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/media?parent=1636"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/categories?post=1636"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/accessdrum.com\/bg\/wp-json\/wp\/v2\/tags?post=1636"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}