Drawers

Demo

				
					
<!-- Dark overlay for expanded drawers -->
<div class="overlay"></div>

<!-- Buttons to trigger drawer expansion -->
<div class="select-container">
<button class="drawer-button" aria-label="expand top drawer" data-direction="top">
  Top Drawer
</button>
<button class="drawer-button" aria-label="expand left drawer" data-direction="left">
  Left Drawer
</button>
<button class="drawer-button" aria-label="expand right drawer" data-direction="right">
  Right Drawer
</button>
<button class="drawer-button" aria-label="expand bottom drawer" data-direction="bottom">
  Bottom Drawer
</button>
</div>

<!-- Top Drawer -->
<div class="drawer top" aria-hidden="true">
<button class="close-button">Close</button>
<!-- Navigation links for the top drawer -->
<nav class="drawer-content">
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#">Link 3</a>
  <a href="#">Link 4</a>
  <a href="#">Link 5</a>
  <a href="#">Link 6</a>
</nav>
</div>

<!-- Left Drawer -->
<div class="drawer left" aria-hidden="true">
<button class="close-button">Close</button>
<!-- Navigation links for the left drawer -->
<nav class="drawer-content">
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#">Link 3</a>
  <a href="#">Link 4</a>
  <a href="#">Link 5</a>
  <a href="#">Link 6</a>
</nav>
</div>

<!-- Right Drawer -->
<div class="drawer right" aria-hidden="true">
<button class="close-button">Close</button>
<!-- Navigation links for the right drawer -->
<nav class="drawer-content">
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#">Link 3</a>
  <a href="#">Link 4</a>
  <a href="#">Link 5</a>
  <a href="#">Link 6</a>
</nav>
</div>

<!-- Bottom Drawer -->
<div class="drawer bottom" aria-hidden="true">
<button class="close-button">Close</button>
<!-- Navigation links for the bottom drawer -->
<nav class="drawer-content">
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#">Link 3</a>
  <a href="#">Link 4</a>
  <a href="#">Link 5</a>
  <a href="#">Link 6</a>
</nav>
</div>
				
			
				
					
@import url("https://fonts.googleapis.com/css2?family=Barlow&family=Oswald&display=swap");

body {
  font-family: "Barlow", sans-serif;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: #f4f4f4;
  transition: background-color 0.5s; /* Transition for background color change */
}

.select-container {
  display: flex;
  gap: 10px;
}

.drawer {
  position: fixed;
  z-index: 1000;
  opacity: 0;
  transition: transform 0.5s, opacity 0.5s;
  background-color: #fff; /* Set the background color to white */
}

/* Adjust the positioning of the close button */
.drawer-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 20px;
  position: relative; /* Ensure positioning context for the absolute button */
}

.close-button {
  cursor: pointer;
  background: none;
  border: none;
  font-size: 16px;
  color: #005e86;
  position: relative;
  left: 20px;
  top: 10px;
}

.top,
.bottom,
.left,
.right {
  border: 2px solid transparent; /* Adding a transparent border initially */
}

.top {
  width: 100%;
  max-height: 60vh;
  overflow-y: auto;
  transform-origin: top;
  top: 0;
  left: 0;
  pointer-events: none; /* Disable pointer events */
  user-select: none; /* Disable user selection */
}

.bottom {
  transform-origin: bottom;
  transform: translateY(-100%);
  bottom: 0;
  left: 0;
  width: 100%;
  max-height: 60vh;
  overflow-y: auto;
  pointer-events: none; /* Disable pointer events */
  user-select: none; /* Disable user selection */
}

.left {
  height: 100%;
  max-width: 140vw;
  overflow-x: auto;
  transform-origin: left;
  transform: translateX(-100%);
  top: 0;
  left: 0;
  pointer-events: none; /* Disable pointer events */
  user-select: none; /* Disable user selection */
}

.right {
  height: 100%;
  max-width: 140vw;
  overflow-x: auto;
  transform-origin: right;
  transform: translateX(100%);
  top: 0;
  right: 0;
  pointer-events: none; /* Disable pointer events */
  user-select: none; /* Disable user selection */
}

.top .drawer-content,
.bottom .drawer-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.left .drawer-content,
.right .drawer-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
}

.drawer-content a {
  text-decoration: none;
  color: #005e86;
  transition: color 0.3s;
  font-size: 18px; /* Increased font size for better readability */
  width: 90%;
  padding: 10px;
}

.drawer-content a:hover,
.drawer-content a:focus {
  color: #211224;
  background-color: rgba(211, 211, 211, 0.774);
}

.drawer-button {
  padding: 10px 20px;
  background-color: #005e86;
  color: #fff;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.drawer-button:hover {
  background-color: #99b4c0;
}

.close-button:hover {
  color: #99b4c0;
}

/* Adjust drawer appearance when it's open */
.drawer-open .drawer {
  border-color: #005e86; /* Change to your desired border color */
}

/* Adjust drawer appearance when it's open */
.drawer-open .top,
.drawer-open .bottom,
.drawer-open .left,
.drawer-open .right {
  border-color: #005e86; /* Change to your desired border color */
}

/* Dark overlay for the main page when drawer is opened */
.drawer-open .overlay {
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(
    0,
    0,
    0,
    0.3
  ); /* Adjust the alpha value to change darkness */
  pointer-events: none; /* Allows interaction with elements behind the overlay */
}

/* Adjust drawer appearance when it's open */
.drawer-open .drawer {
  z-index: 1001; /* Ensures drawers are on top of the overlay */
}
				
			
				
					
document.addEventListener("DOMContentLoaded", function () {
  // Set initial tabindex to -1 for all drawer links and close buttons
  const drawerLinks = document.querySelectorAll(".drawer-content a");
  drawerLinks.forEach((link) => {
    link.tabIndex = -1;
  });

  const closeButtons = document.querySelectorAll(".close-button");
  closeButtons.forEach((button) => {
    button.tabIndex = -1;
  });

  // Logic for opening/closing drawers
  const buttons = document.querySelectorAll(".drawer-button");
  const closeButton = document.querySelectorAll(".close-button");

  // Function to close all drawers
  function closeAllDrawers() {
    const allDrawers = document.querySelectorAll(".drawer");
    allDrawers.forEach((drawer) => {
      drawer.style.transform = "scale(0)";
      drawer.style.opacity = "0";
      drawer.setAttribute("aria-hidden", "true");
      drawer.style.pointerEvents = "none";
      drawer.style.userSelect = "none";
    });
  }

  // Function to close a specific drawer
  function closeDrawer(drawer) {
    drawer.style.transform = "scale(0)";
    drawer.style.opacity = "0";
    drawer.setAttribute("aria-hidden", "true");
    drawer.style.pointerEvents = "none";
    drawer.style.userSelect = "none";
  }

  // Event listeners for opening drawers
  buttons.forEach((button) => {
    button.addEventListener("click", function () {
      // Close all drawers before opening a new one
      closeAllDrawers();

      const direction = button.getAttribute("data-direction");
      const selectedDrawer = document.querySelector(`.drawer.${direction}`);

      // Open the selected drawer
      selectedDrawer.style.transform = "scale(1)";
      selectedDrawer.style.opacity = "1";
      selectedDrawer.setAttribute("aria-hidden", "false");

      // Move focus into the opened drawer
      selectedDrawer.querySelector("a").focus();

      // Enable links in the opened drawer for focus
      const drawerLinks = selectedDrawer.querySelectorAll(".drawer-content a");
      drawerLinks.forEach((link) => {
        link.tabIndex = 0;
      });

      // Enable close button for focus
      const closeBtn = selectedDrawer.querySelector(".close-button");
      closeBtn.tabIndex = 0;

      // Enable pointer events and user selection for the opened drawer
      selectedDrawer.style.pointerEvents = "auto";
      selectedDrawer.style.userSelect = "auto";

      // Add class to body to enable overlay and borders
      document.body.classList.add("drawer-open");

      // Trap focus inside the opened drawer
      selectedDrawer.addEventListener("keydown", trapFocus);
    });
  });

  // Event listeners for closing drawers
  closeButton.forEach((button) => {
    button.addEventListener("click", function () {
      const drawer = button.closest(".drawer");

      // Find the button that triggered the opening of this drawer
      const associatedButton = document.querySelector(
        `.drawer-button[data-direction="${drawer.classList[1]}"]`
      );

      // Close the drawer
      closeDrawer(drawer);

      // Disable links in the closed drawer for focus
      const drawerLinks = drawer.querySelectorAll(".drawer-content a");
      drawerLinks.forEach((link) => {
        link.tabIndex = -1;
      });

      // Disable close button for focus
      button.tabIndex = -1;

      // Move focus back to the associated button that triggered opening this drawer
      associatedButton.focus();

      // Remove class from body to disable overlay and borders
      document.body.classList.remove("drawer-open");

      // Remove focus trap when drawer is closed
      drawer.removeEventListener("keydown", trapFocus);
    });
  });

  // Log clicked element for debugging
  document.addEventListener("click", function (event) {
    console.log("Clicked Element:", event.target);
  });

  // Function to trap focus inside the opened drawer
  function trapFocus(event) {
    const focusableElements = event.currentTarget.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const firstFocusable = focusableElements[0];
    const lastFocusable =
      focusableElements[focusableElements.length - 1];

    if (event.key === "Tab") {
      if (
        event.shiftKey &&
        document.activeElement === firstFocusable
      ) {
        lastFocusable.focus();
        event.preventDefault();
      } else if (
        !event.shiftKey &&
        document.activeElement === lastFocusable
      ) {
        firstFocusable.focus();
        event.preventDefault();
      }
    }
  }
});

				
			

Why use drawer components?

Drawer components in web development offer an effective way to present additional content, options, or navigation without occupying constant screen space. These components typically slide in or out from the edge of the screen, providing users with quick access to supplementary information or functionalities. From an accessibility standpoint, well-implemented drawer components can enhance user experience by allowing users to focus on the main content while accessing secondary or contextual information when needed. They ensure that the content within the drawer is easily navigable and perceivable by all users, including those relying on assistive technologies. Additionally, drawer components contribute to a more organized and clutter-free interface, maintaining a balance between accessibility and functionality by presenting content in an unobtrusive yet accessible manner. They serve as a versatile tool for optimizing screen real estate and accommodating diverse user needs without compromising accessibility.

What are some accessibility concerns?

  1. Keyboard Accessibility:
    • Concern: Keyboard-only users might face challenges navigating and interacting with drawer content.
    • Mitigation: Ensure keyboard accessibility by allowing users to open, close, and navigate within the drawer using keyboard controls (such as Tab, Arrow keys, and Esc).
  2. Screen Reader Compatibility:
    • Concern: Screen reader users may encounter difficulties perceiving and accessing the drawer’s content.
    • Mitigation: Use proper semantic markup (e.g., <aside> or <nav> for drawers) and provide descriptive labels or headings to assist screen reader users in understanding the purpose and content of the drawer.
  3. Visual and Cognitive Accessibility:
    • Concern: Users with visual impairments or cognitive disabilities might have difficulty identifying the drawer’s presence or understanding its content.
    • Mitigation: Ensure there are clear visual indicators (e.g., icons, labels, or buttons) indicating the presence and functionality of the drawer. Maintain sufficient color contrast and provide text alternatives where necessary.
  4. Focus Management and Trap:
    • Concern: Users might get “trapped” within the drawer, unable to navigate back to the main content.
    • Mitigation: Implement proper focus management to ensure that keyboard focus moves appropriately between the main content and the drawer. Avoid trapping users within the drawer by allowing easy navigation in and out of the drawer.
  5. Mobile and Touch Accessibility:
    • Concern: Drawer interactions might pose challenges for touch device users or smaller screens.
    • Mitigation: Optimize drawer interactions for touch devices, providing large enough touch targets and intuitive gestures for opening and closing the drawer.
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
    2.4.3
    Focus Order ( level A ) :
    If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.