Modals

Demo

				
					
<div class="modalButtonwrap">
    <button id="modalButton" class="openModal">This is a Modal</button>
</div>

<div role="dialog" id="modal" class="modalWrapper">
    <button tabindex="-1" id="closeModal" class="closeButton" aria-label="close modal">X</button>
    <div class="modalContent">
        <div class="modalHeader">
            <h2>Sign Up For A Newsletter</h2>
        <p>Content goes here</p>
        <p>Fields marked with * are required.</p>
    </div>
        <form id="modalForm">
            <div role="status" id="errorDiv"></div>
            <label for="firstName">First Name *</label>
            <input type="text" aria-required="true" id="firstName">
            <div id="firstNameErr"></div>
            <label for="lastName">Last Name *</label>
            <input type="text" aria-required="true" id="lastName">
            <div id="lastNameErr"></div>
            <label for="country">Country</label>
            <select id="country" name="country">
                <option value="australia">Australia</option>
                <option value="canada">Canada</option>
                <option value="usa">USA</option>
            </select>
            <label for="email">Email *</label>
            <input type="text" aria-required="true" id="Email">
            <div id="emailErr"></div>
            <input type="submit" value="Submit" id="submitBtn">
        </form>
    </div>
</div>
				
			
				
					
.openModal {
    height: 4em;
    line-height: 4em;
    width: 80%;
    font-size: 1.2em;
    border-radius: 10px;
    border: 2px #3A2D3F solid;
    background-color: white;
    cursor: pointer;
}

.modalButtonwrap {
    width: 40%;
    margin: auto;
    display: flex;
    justify-content: center;
    margin-top: 5em;   
}

#modalButton:hover, #modalButton:focus {
    background-color: #58345f62;
    outline: 2px black solid;
}

.closeButton {
    background: white;
    cursor: pointer;
    margin-top: 1em;
    border: none;
    font-size: 1.5rem;
    margin-left: 90%;
}

.modalWrapper {
    position:absolute;
    left:-10000px;
    top:auto;
    width:1px;
    height:1px;
    overflow:hidden;
    border-radius: 10px;
}

.modalHeader {
    text-align: center;
}

.modalWrapper.showModal {
    height: 75%;
    width: 50%;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    border: 2px solid #3A2D3F;
    z-index: 2;
    background-color: white;
    transition: all .5s ease-out;
    visibility: visible;
    overflow: visible;
}

body.faded {
    overflow: hidden;
    background-color: rgba(0, 0, 0, 0.515);
}

label {
    margin-left: 15px;
    margin-top: 1em;
}

input[type=text], select {
    width: 98%;
    padding: 12px 0;
    margin-left: 1em;
    margin-right: 1em;
    display: inline-block;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
  }

  input[type=submit] {
    width: 80%;
    background-color: #5c2e46;
    color: white;
    font-size: 1.5em;
    padding: 14px 20px;
    margin: 8px ;
    margin-top: 1em;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    position: absolute;
    left: 10%;
  }
				
			
				
					
let modalOpen = document.getElementById("modal");
let modalButton = document.getElementById("modalButton");
let modalClose = document.getElementById("closeModal")
let element = document.getElementById("modal");
let modalSubmit = document.getElementById("submitBtn")
let errorDiv = document.getElementById("errorDiv");
let firstNameErr = document.getElementById("firstNameErr");
let lastNameErr = document.getElementById("lastNameErr");
let emailErr = document.getElementById("emailErr");
let modalForm = document.getElementById("modalForm");
let valid = false;

function tabStopFix() {
    var focusableEls = document.getElementById("modal").querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])');
    if (element.ariaHidden === "false") {
        focusableEls.forEach(item => {
            item.removeAttribute("tabindex");
        })
    } else {
        focusableEls.forEach(item => {
            item.setAttribute("tabindex", "-1");
        })
    }
}

function trapFocus(element) {
    var focusableEls = document.getElementById("modal").querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled]), input[type="submit"]:not([disabled])');
    var firstFocusableEl = focusableEls[0];  
    var lastFocusableEl = focusableEls[focusableEls.length - 1];
    var KEYCODE_TAB = 9;
  
    element.addEventListener('keydown', function(e) {
      var isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);
  
      if (!isTabPressed) { 
        return; 
      }
  
      if ( e.shiftKey ) {
        if (document.activeElement === firstFocusableEl) {
          lastFocusableEl.focus();
            e.preventDefault();
          }
        } else {
        if (document.activeElement === lastFocusableEl) {
          firstFocusableEl.focus();
            e.preventDefault();
          }
        }
    });
  }

modalButton.addEventListener("click", function(e) {
    modalOpen.classList.toggle("showModal");
    document.body.classList.toggle("faded");
    document.body.setAttribute("visibility", "hidden")
    e.target.style.visibility = "hidden";
    modalClose.focus();
})

modalClose.addEventListener("click", function() {
    modalOpen.classList.toggle("showModal");
    document.body.classList.toggle("faded");
    document.body.setAttribute("visibility", "visible")
    modalButton.style.visibility = "visible";
    modalButton.focus();
})

modalClose.addEventListener("focus", function() {
    element.setAttribute("aria-hidden", "false")
   tabStopFix();
})

modalButton.addEventListener("focus", function() {
    element.setAttribute("aria-hidden", "true")
    tabStopFix();
 })

 modalSubmit.addEventListener("click", function(e) {
   e.preventDefault();
  let inputElems = document.getElementById("modal").querySelectorAll('input[type="text"]:not([disabled])');
  let errorDivs = document.getElementById("modal").querySelectorAll(".errMessage");

  for (x=0; x < inputElems.length; x++) {
  if (inputElems[x].value === "") {
      errorDiv.innerHTML = '<p style="color: rgb(165, 5, 5)">There are one or more errors in the form</p>'
    }
  }
  if (document.getElementById("firstName").value.match(/\d+/g) !== null ||document.getElementById("firstName").value === "" ) {
    console.log("fired")
    firstNameErr.innerHTML = '<p style="color: rgb(165, 5, 5)" class="errMessage">Ensure this is not empty and only includes letters</p>'
  }
  if (document.getElementById("lastName").value.match(/\d+/g) !== null ||document.getElementById("lastName").value === "" ) {
    console.log("fired")
    lastNameErr.innerHTML = '<p style="color: rgb(165, 5, 5)" class="errMessage">Ensure this is not empty and only includes letters</p>'
  }
  if (document.getElementById("Email").value.includes("@") === null ||document.getElementById("Email").value === "" ) {
    console.log("fired")
    emailErr.innerHTML = '<p style="color: rgb(165, 5, 5)" class="errMessage" >Ensure this is a valid email address</p>';
  }

  for (i=-0; i < errorDivs.length; i++) {
    console.log("this should remove")
    errorDivs[i].remove();
  }

  if ( document.getElementById("modal").querySelectorAll(".errMessage").length > 0) {
    valid = false;
    console.log("error messages found")
} else {valid = true}
  console.log(valid);
  console.log(modalForm);
  if (valid) modalForm.submit();
});

  trapFocus(element);
				
			

Why use a modal?

Modals in web design should be used strategically to enhance user experience and engagement in specific situations. They are effective when you need to focus a user’s attention on a particular piece of content or functionality without navigating away from the current page. Modal dialogs are commonly employed for tasks such as displaying login or registration forms, providing quick information or alerts, requesting user input, or showcasing multimedia content. However, it’s essential to use modals sparingly and thoughtfully, as their overuse can disrupt the flow of a website and potentially create accessibility issues. When used judiciously, modals can offer a seamless and unobtrusive way to present relevant information or interactions to users within the context of their current browsing session.

Here are a few situations in which a modal may be appropriate:

  • Login and Registration
  • Product information and Details
  • Media Content
  • Interactive Widgets
  • Content Previews

What are some accessibility concerns?

  • Keyboard Navigation:
    • Ensure that all modal elements and controls are keyboard navigable. Users should be able to open, close, and interact with the modal using keyboard inputs, such as the Tab key, Enter key, and Escape key.
  • Focus Management:
    • Properly manage focus when a modal is opened. Set focus to the first focusable element within the modal and ensure that users can easily exit the modal and return to the main content.
  • Screen Reader Compatibility:
    • Use semantic HTML elements and ARIA roles and attributes to convey the purpose and state of the modal to screen reader users. Announce the modal’s title and provide a mechanism for screen reader users to understand its presence and purpose.
  • Overlay and Background:
    • Ensure that when the modal is open, the background content is not accessible or navigable by keyboard, preventing users from interacting with elements outside the modal.
  • Close Button and Dismissal:
    • Provide a clear and visible close button within the modal for users to close it. Allow users to dismiss the modal using the keyboard, typically by pressing the Escape key.
  • Modal Triggers:
    • Provide clear and accessible modal triggers (buttons or links) and ensure that users can activate them with keyboard inputs.
  • Keyboard Focus Trapping:
    • Implement keyboard focus trapping to prevent users from moving focus outside the modal while it’s open.
Tested using
Chrome
with NVDA
Firefox
with NVDA
  • W3C SVG
    1.3.1
    Info and Relationships ( level A ) :
    Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
  • W3C SVG
    2.1.1
    Keyboard ( level A ) :
    All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints.
  • W3C SVG
    2.1.2
    No Keyboard Trap ( level A ) :
    If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface, and, if it requires more than unmodified arrow or tab keys or other standard exit methods, the user is advised of the method for moving focus away.
  • W3C SVG
    4.1.2
    Name, Role, Value ( level A ) :
    For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.