How to make Relume Modal Slide-In Component accessible

Share this post

Screenshot of Graceful Web Studio’s website showing a partially open slide-in contact form titled ‘Contact’ with fields for Name, Email, and Message.

Developers, if you love Relume as much as I do, you already know it's the secret sauce that speeds up Webflow projects and makes them look stunning. I am a huge fan of the Relume library—it seamlessly integrates into our workflow (even if I do spend a bit of extra time cleaning up those quirky naming class number conventions home-header3777777_Content 😜). But here's the kicker: while Relume boosts my productivity, its out-of-the-box components rarely meet the accessibility standards I insist on. As an accessibility-first Webflow agency, I can’t ship anything that isn’t fully accessible, so I roll up my sleeves and script the fixes myslef. I’m here to share how I transformed the Relume modal slide-in component into a WCAG 2.2 AA compliant dialog—because I’m sure there are plenty of fellow developers out there who care about accessibility and need a little guidance on making their projects as inclusive as they are innovative.

Relume UI Elements Modal 2

Relume UI Element Modal 2 was my base component ( I add my own accessible form - that will be explained in a future "How-to Create Accessible Forms in Webflow" blog post)

P.S. Developers if you are not already subscribed to Relume - please subscribe now or this tutorial will not work for you. 😉

Identifying the Accessibility Challenges

The default Relume modal component looked great and worked smoothly. However, it wasn’t accessible by default. Key issues included:

  • Missing Accessible Name: The dialog lacked an accessible name.
  • Keyboard Navigation Issues: Users navigating with a keyboard weren’t properly guided through the modal content.
  • Focus Issues: Focus was not sent to the dialog and not trapped within dialog leaving keyboard users navigating elements behind it.
  • Insufficient ARIA Roles: Essential ARIA roles and attributes were missing on key interactive elements.

Actionable Accessibility Enhancements

1. Establishing a Clear Accessible Name

  • Unique IDs: I assigned a unique ID to the modal’s content wrapper and to the heading inside the dialog.
  • ARIA Labeling: I added an aria-labelledby attribute to the modal wrapper—using the heading’s ID—set the role to dialog, marked it as aria-modal="true", and included tabindex="-1". These changes, made via Webflow’s custom attributes, ensure screen readers announce the dialog with a clear, descriptive name.

2. Optimizing the Close Button

  • Decorative Image: I set the image inside the close button as decorative so screen readers don’t announce unnecessary details.
  • Proper Role and Label: By adding a role of button and an aria-label="Close", I ensured that the close button is clearly identifiable and functions correctly for keyboard users.

3. Enhancing the Modal Trigger Button

  • Unique ID and Role: I gave the trigger button a unique ID (contact-modal-trigger). Although it was originally a link, I assigned it the role of button since its purpose is to open a dialog.
  • ARIA Attributes: I added an aria-haspopup="dialog" attribute to signal that this button controls a dialog popup—providing essential context for assistive technologies.

4. Managing Focus with a Custom jQuery Script

Since Webflow handles the dialog’s visibility through its interactions, my custom jQuery script focused solely on managing focus—addressing the issue where focus was not sent to the dialog and was left on elements behind it:

  • Activation Listener: The script listens for both click and key press events on the dialog trigger. Once activated, and after a short delay (to allow the modal to open), it explicitly moves focus to the modal.
  • Focus Trap: I implemented a focus trap that confines keyboard navigation to the modal while it’s open, preventing users from accidentally tabbing to elements behind the dialog.
  • Restoring Focus: When the close button is activated via click or key press, the script returns focus to the original trigger button, ensuring a seamless experience.

Below is the complete code snippet that brings these enhancements to life:

<!-- jQuery CDN -->
<script defer src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script defer>
$(document).ready(function() {
  // Function to trap focus within the modal
  function trapFocus(modal) {
    var focusableElements = modal.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])');
    var firstFocusable = focusableElements.first();
    var lastFocusable = focusableElements.last();

    // Listen for Tab/Shift+Tab keydown events on the modal
    modal.on('keydown.trap', function(e) {
      if (e.key === 'Tab' || e.keyCode === 9) {
        if (e.shiftKey) {
          // If Shift+Tab pressed on first element, move focus to the last element
          if ($(document.activeElement).is(firstFocusable)) {
            e.preventDefault();
            lastFocusable.focus();
          }
        } else {
          // If Tab pressed on last element, move focus to the first element
          if ($(document.activeElement).is(lastFocusable)) {
            e.preventDefault();
            firstFocusable.focus();
          }
        }
      }
    });
  }

  // Function to remove the focus trap event listener from the modal
  function removeTrapFocus(modal) {
    modal.off('keydown.trap');
  }

  // Listen for click or key press on the trigger button
  $('#contact-modal-trigger').on('click keydown', function(e) {
    // Delay to allow Webflow's interaction to open the modal
    setTimeout(function() {
      var modal = $('#contact-modal');
      if (modal.is(':visible')) {
        modal.focus(); // Explicitly send focus to the modal
        trapFocus(modal); // Activate focus trapping
      }
    }, 100); // Adjust delay if needed
  });

  // Listen for click or key press on the close button
  $('.modal_close-button').on('click keydown', function(e) {
    // For keydown events, only act on Enter or Space keys
    if (e.type === 'keydown' && !(e.key === 'Enter' || e.key === ' ')) {
      return;
    }
    var modal = $('#contact-modal');
    removeTrapFocus(modal); // Remove the focus trap
    // Delay before returning focus to the trigger button
    setTimeout(function() {
      $('#contact-modal-trigger').focus();
    }, 100);
  });
});
</script>

Conclusion

By adding unique IDs, proper ARIA attributes, and a robust focus management script, I transformed the Relume modal slide-in component into an accessible, WCAG 2.2 AA compliant dialog. Fixing the focus issues—ensuring focus is sent directly to the dialog and trapped within it—has not only made my projects more inclusive but also significantly improved the overall user experience.

I understand that accessibility isn’t easy—it takes time, practice, and commitment. I could have had this dialog added to my website, styled and published in under 30 minutes. Instead, I did it the right way, which took over an hour. Then I decided to take another hour to write this blog post and share my process because accessibility matters and so do the people I am developing for. By adding unique IDs, proper ARIA attributes, and a robust focus management script, I transformed the Relume modal slide-in component into an accessible, WCAG 2.2 AA compliant dialog. Fixing the focus issues—ensuring focus is sent directly to the dialog and trapped within it—not only made my projects more inclusive but also significantly improved the overall user experience.

If you’re a developer who cares about accessibility, I hope this guide inspires you to take the extra time to do things right. 🤩 Happy coding and accessible designing!

— Crystal Scott, Certified Professional in Web Accessibility

Your Website Deserves Better, Let's Team Up

Send a message or request a project quote for an estimate within 24 hours. Prefer to chat? Book a call, and let’s find the right solution for you!