Two bugs, same field: the list covers the question above it, and it goes back roughly 20 years further than it needs to.
The list renders from the top of the modal, hiding the heading and the first year field underneath it.
Four years only. Opens directly under its own field — the heading and the other year field stay visible.
<div class="pzn-field"> <label id="pzn-y1-label">Year</label> <button type="button" class="pzn-yearbtn" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="pzn-y1-label" data-year-trigger="1"> <span data-year-display="1" class="ph">Year</span> <svg class="chev" viewBox="0 0 10 10" fill="none"> <path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/> </svg> </button> <!-- populated by JS with exactly 4 buttons: this year + previous 3 --> <div class="pzn-yearlist" role="listbox" data-year-list="1" aria-labelledby="pzn-y1-label"></div> </div>
/* Anchored to its own field via position:relative on the parent .pzn-field, not fixed to the page or the modal. This is what stops it from covering the heading — it can only render where its own field is, and only as tall as its own content (4 items, capped). */ .pzn-field { position: relative; } .pzn-yearlist { position: absolute; top: calc(100% + 3px); left: 0; right: 0; z-index: 5; background: #fff; border: 1px solid #C9CFE0; border-radius: 4px; box-shadow: 0 6px 16px rgba(28,36,64,.14); max-height: 160px; /* generous headroom; 4 items never fill it */ overflow-y: auto; padding: 4px; display: none; } .pzn-yearlist.is-open { display: block; } .pzn-yearlist button { width: 100%; text-align: left; background: none; border: 0; border-radius: 3px; font-family: inherit; font-size: 12.5px; color: #4A5061; padding: 6px 8px; cursor: pointer; } .pzn-yearlist button:hover { background: #F4F6FA; } .pzn-yearlist button[aria-selected="true"] { background: #EDF1FB; color: #233C90; font-weight: 700; }
// Always the current year plus the previous 3 — recalculated at // render time, never a hardcoded array, so it stays correct every // January without a content update. function getYearOptions() { const now = new Date().getFullYear(); return [now, now - 1, now - 2, now - 3]; } function buildYearList(listEl, selectedYear) { listEl.innerHTML = ''; getYearOptions().forEach(year => { const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = year; btn.setAttribute('role', 'option'); btn.setAttribute('aria-selected', String(year === selectedYear)); listEl.appendChild(btn); }); } // Wire each trigger/list pair document.querySelectorAll('[data-year-trigger]').forEach(trigger => { const id = trigger.dataset.yearTrigger; const list = document.querySelector(`[data-year-list="${id}"]`); const display = document.querySelector(`[data-year-display="${id}"]`); buildYearList(list, null); trigger.addEventListener('click', () => { // close any other open list first document.querySelectorAll('.pzn-yearlist.is-open').forEach(l => { if (l !== list) l.classList.remove('is-open'); }); const open = list.classList.toggle('is-open'); trigger.setAttribute('aria-expanded', String(open)); }); list.addEventListener('click', e => { if (e.target.tagName !== 'BUTTON') return; display.textContent = e.target.textContent; display.classList.remove('ph'); buildYearList(list, Number(e.target.textContent)); list.classList.remove('is-open'); trigger.setAttribute('aria-expanded', 'false'); // onYearSelect(id, e.target.textContent); }); }); // click-away closes any open list document.addEventListener('click', e => { if (e.target.closest('.pzn-field')) return; document.querySelectorAll('.pzn-yearlist.is-open').forEach(l => l.classList.remove('is-open')); document.querySelectorAll('[data-year-trigger]').forEach(t => t.setAttribute('aria-expanded', 'false')); });
| Item | Current | Fixed |
|---|---|---|
| Year options | ~23 years (2004–2026) | 4 years — current + previous 3, computed at render time |
| List position | Renders near the top of the modal regardless of which field opened it | Anchored directly below its own field via position:absolute inside a position:relative field wrapper |
| List height | Tall enough to cover the heading and the first year field | Capped at 160px; 4 items never come close to filling it |
| Two lists open at once | Not addressed — could presumably overlap each other | Opening one closes the other automatically |
| Everything else | Question text, help link, Amount fields, Back/Next — unchanged | |
<select>, this replaces it with a
custom button + listbox pattern, since native selects can't be capped in height or reliably
positioned across browsers. The trade-off is slightly more JS to wire up, in exchange for control
over both bugs at once.getYearOptions() always
returns the current year and the three before it, so it never needs a content update again.