**Approach (brief)** I’d build a semantic, keyboard-accessible dropdown that works as a native select fallback when JS is off, layers ARIA roles/states when JS is present, supports ArrowUp/Down, Enter, Escape, Home/End, and traps focus inside when open. Below is minimal HTML/CSS/JS; I’ll explain testing after.**HTML (graceful degradation / works without JS)**html
<!-- HTML -->
<div class="dropdown" data-js="dropdown">
<label for="native-select" class="visually-hidden">Choose option</label>
<select id="native-select" class="native-select">
<option>Option A</option>
<option>Option B</option>
<option>Option C</option>
</select>
<!-- JS-enhanced custom dropdown (hidden by default for no-JS) -->
<div class="custom" hidden aria-hidden="true">
<button class="trigger" aria-haspopup="listbox" aria-expanded="false">Option A</button>
<ul class="list" role="listbox" tabindex="-1" hidden>
<li role="option" aria-selected="true" tabindex="-1">Option A</li>
<li role="option" tabindex="-1">Option B</li>
<li role="option" tabindex="-1">Option C</li>
</ul>
</div>
</div>
**CSS**css
/* CSS */
.visually-hidden { position: absolute; left:-10000px; }
.native-select { display:block; } /* visible when JS disabled */
.dropdown[data-js] .native-select { display:block; } /* initial */
.custom { position:relative; }
.trigger { /* visual styles */ }
.list { position:absolute; margin-top:4px; background:#fff; border:1px solid #ccc; max-height:200px; overflow:auto; }
.list li[aria-selected="true"]{ background:#eef; }
**JavaScript**javascript
// JS
document.addEventListener('DOMContentLoaded', ()=> {
const root = document.querySelector('[data-js="dropdown"]');
if(!root) return;
const native = root.querySelector('.native-select');
const custom = root.querySelector('.custom');
const trigger = custom.querySelector('.trigger');
const list = custom.querySelector('.list');
const items = Array.from(list.querySelectorAll('[role="option"]'));
// swap visibility: hide native, show custom
native.hidden = true;
custom.hidden = false;
custom.setAttribute('aria-hidden','false');
let open = false, index = 0;
function openList(){ open = true; trigger.setAttribute('aria-expanded','true'); list.hidden = false; list.focus(); items[index].focus(); }
function closeList(){ open = false; trigger.setAttribute('aria-expanded','false'); list.hidden = true; trigger.focus(); }
function select(i){ items.forEach(it=> it.setAttribute('aria-selected','false')); items[i].setAttribute('aria-selected','true'); trigger.textContent = items[i].textContent; native.selectedIndex = i; index = i; }
// keyboard handlers
trigger.addEventListener('click', ()=> open ? closeList() : openList());
trigger.addEventListener('keydown', e=>{
if(e.key === 'ArrowDown'){ e.preventDefault(); openList(); }
if(e.key === 'ArrowUp'){ e.preventDefault(); openList(); items[items.length-1].focus(); index = items.length-1; }
});
list.addEventListener('keydown', e=>{
if(e.key === 'Escape') { closeList(); }
if(e.key === 'ArrowDown'){ e.preventDefault(); index = (index+1)%items.length; items[index].focus(); }
if(e.key === 'ArrowUp'){ e.preventDefault(); index = (index-1+items.length)%items.length; items[index].focus(); }
if(e.key === 'Home'){ e.preventDefault(); index = 0; items[0].focus(); }
if(e.key === 'End'){ e.preventDefault(); index = items.length-1; items[index].focus(); }
if(e.key === 'Enter' || e.key === ' ') { e.preventDefault(); select(index); closeList(); }
// trap focus: Tab closes and returns to trigger
if(e.key === 'Tab'){ closeList(); }
});
items.forEach((it,i)=>{
it.addEventListener('click', ()=> { select(i); closeList(); });
it.addEventListener('focus', ()=> index = i);
});
// click outside close
document.addEventListener('click', e=> { if(!root.contains(e.target)) closeList(); });
});
Testing strategy (how I’d test as a UI Designer)- Manual keyboard tests: Tab to component, Arrow keys navigate, Enter selects, Escape closes, Home/End jump, focus order and trap.- Screen reader tests: NVDA/VoiceOver with keyboard only to verify role/listbox, aria-selected updates and announcement.- No-JS: disable JS and confirm native <select> visible and usable.- Cross-browser and mobile: Safari, Chrome, Firefox; check touch behavior and visual states.- Automated: small accessibility checks with axe, and keyboard focus tests via Playwright to assert focus/aria state changes.Why this fits UI Designer role- Visual styles, states, and focus indicators are explicit; component degrades gracefully; interaction patterns follow platform conventions and accessibility best practices.