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.
-
Content can be presented without loss of information or functionality, and without requiring scrolling in two dimensions for:
User Interface Components: Visual information required to identify user interface components and states, except for inactive components or where the appearance of the component is determined by the user agent and not modified by the author;Graphical Objects: Parts of graphics required to understand the content, except when a particular presentation of graphics is essential to the information being conveyed.
- 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.
- Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.
- Labels or instructions are provided when content requires user input.
- 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.