Custom Select
Demo
- Option 1
- Option 2
- Option 3
- Option 1
- Option 2
- Option 3
/* Styles for custom select component */
#container {
margin: auto;
width: 50%;
text-align: center;
margin-top: 50px;
}
.custom-select {
position: relative;
display: inline-block;
}
.select-trigger {
display: flex;
align-items: center;
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
align-text: center;
font-size: 1.5em;
}
.options-list {
position: absolute;
top: 100%;
left: 0;
z-index: 1;
display: none;
margin: 0;
padding: 0;
list-style: none;
border: 1px solid #ccc;
border-top: none;
border-radius: 0 0 4px 4px;
background-color: #fff;
}
.options-list li {
padding: 8px 16px;
cursor: pointer;
}
.options-list li:hover {
background-color: #f0f0f0;
}
const customSelect = document.querySelector('.custom-select');
const selectTrigger = document.querySelector('.select-trigger');
const optionsList = document.querySelector('.options-list');
const options = document.querySelectorAll('.options-list li');
let isOpen = false;
function toggleOptionsList() {
isOpen = !isOpen;
if (isOpen) {
optionsList.style.display = 'block';
selectTrigger.setAttribute('aria-expanded', 'true');
optionsList.setAttribute('aria-hidden', 'false');
// Set tabindex for options and focus the options list
optionsList.querySelectorAll('li').forEach((option) => {
option.setAttribute('tabindex', '0');
});
optionsList.focus();
optionsList.addEventListener('keydown', collapseOnEscape);
optionsList.addEventListener('keydown', navigateOptions);
customSelect.addEventListener('focusout', collapseDropdown);
} else {
optionsList.style.display = 'none';
selectTrigger.setAttribute('aria-expanded', 'false');
optionsList.setAttribute('aria-hidden', 'true');
// Reset tabindex for options and focus selectTrigger
optionsList.querySelectorAll('li').forEach((option) => {
option.setAttribute('tabindex', '-1');
});
selectTrigger.focus();
}
}
function collapseOnEscape(event) {
if (event.key === 'Escape') {
isOpen = false;
optionsList.style.display = 'none';
selectTrigger.setAttribute('aria-expanded', 'false');
selectTrigger.focus();
}
}
function navigateOptions(event) {
const focusedIndex = Array.from(options).indexOf(document.activeElement);
if (event.key === 'ArrowDown') {
event.preventDefault();
const previousIndex = (focusedIndex - 1 + options.length) % options.length;
options[previousIndex].focus();
} else if (event.key === 'ArrowUp') {
event.preventDefault();
const nextIndex = (focusedIndex + 1) % options.length;
options[nextIndex].focus();
}
}
function collapseDropdown(event) {
if (!event.relatedTarget || !optionsList.contains(event.relatedTarget)) {
isOpen = false;
optionsList.style.display = 'none';
selectTrigger.setAttribute('aria-expanded', 'false');
selectTrigger.focus();
}
}
selectTrigger.addEventListener('click', toggleOptionsList);
optionsList.addEventListener('keydown', trapFocus);
options.forEach((option) => {
option.addEventListener('click', () => {
selectOption(option);
});
option.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
selectOption(option);
} else if (event.key === 'ArrowDown') {
navigateOptions(event);
} else if (event.key === 'ArrowUp') {
navigateOptions(event);
} else if (event.key === 'Escape') {
toggleOptionsList();
}
});
});
function selectOption(option) {
selectTrigger.querySelector('#selectedOption').innerText = option.innerText;
options.forEach((opt) => {
opt.setAttribute('aria-selected', 'false');
});
option.setAttribute('aria-selected', 'true');
setTimeout(() => {
toggleOptionsList();
}, 100);
}
function trapFocus(event) {
const focusableElements = optionsList.querySelectorAll('li');
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
const isTabPressed = event.key === 'Tab' || event.keyCode === 9;
if (!isTabPressed) {
return;
}
if (event.shiftKey) {
if (document.activeElement === firstFocusable) {
event.preventDefault();
lastFocusable.focus();
}
} else {
if (document.activeElement === lastFocusable) {
event.preventDefault();
firstFocusable.focus();
}
}
}
Why use a select element?
Select components are fundamental in web development as they provide users with an intuitive interface for making choices or selections from a predefined set of options. These components, commonly known as dropdowns or select menus, offer a compact and organized way to present multiple choices, making them ideal for forms, settings, or navigation elements. For users who rely on screen readers or keyboard navigation due to disabilities or limitations, properly designed select components ensure that the options are programmatically accessible and navigable. Accessibility concerns often revolve around ensuring keyboard operability, clear labeling, and semantic markup, allowing all users, including those with disabilities, to efficiently interact with and select options within these components. By prioritizing accessible design in select components, web developers contribute to a more inclusive digital landscape, fostering usability and equal access for everyone.
What are some accessibility concerns?
- Keyboard Accessibility:
- Concern: Users who rely on keyboards for navigation may encounter difficulties when accessing select components or navigating through options.
- Mitigation: Ensure full keyboard operability by allowing users to navigate and select options within the dropdown using keyboard controls, such as arrow keys, Enter, and Esc. Ensure proper focus management and use ARIA roles to convey the select element’s purpose.
- Screen Reader Compatibility and Labels:
- Concern: Screen readers may not announce select components clearly or might miss label associations, impacting users’ understanding of the selectable options.
- Mitigation: Use proper labeling techniques, associating labels explicitly with the select element using <label> tags or ARIA aria-label and aria-labelledby attributes. Ensure screen readers announce the label and provide context for the select component.
- Visual and Semantic Markup:
- Concern: Select components might lack clear visual cues or semantic markup, making it challenging for users with disabilities to perceive and understand them.
- Mitigation: Ensure consistent styling and visual cues indicating the dropdown nature of the select component. Use semantic HTML elements and ARIA roles (like role=”combobox”, role=”listbox”, and role=”option”) to convey the structure and purpose to assistive technologies.
- Complexity and Cognitive Load:
- Concern: Dropdowns with an extensive list of options might overwhelm users, causing difficulties in finding or selecting the desired choice.
- Mitigation: Implement features like filtering, autocomplete, or categorization to manage large sets of options. Provide clear instructions or context to aid users in making selections within the dropdown.
- Responsive Design and Accessibility:
- Concern: Dropdowns may not scale or function well on smaller devices, impacting usability for users accessing the site on mobile or touch devices.
- Mitigation: Ensure responsive design, allowing select components to adapt and remain usable across various screen sizes and devices. Use touch-friendly interfaces, larger tap areas, and well-designed dropdown behavior for touch-based interactions.
-
Article not found
-
Article not found
-
Article not found
-
Article not found
-
Article not found