Drawers
Demo
@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?
- 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).
- 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.
- 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.
- 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.
- 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.
- Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
- 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.
- 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.
- 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.