Fix dropdown active item outline clipped by adjacent items (#32073)

* Initial plan

* Fix selected item outline overlapping next item in dropdown menus

Replace position:relative + absolute positioning with flexbox layout
on .dropdown-item to prevent stacking context overlap of active outline.

Co-authored-by: aeaton-overleaf <75253002+aeaton-overleaf@users.noreply.github.com>

* Fix invalid HTML - replace `div` with `span` inside dropdown item description container

* Do not suppress keyboard focus outlines in toolbar dropdown menus

* Add explicit keyboard focus ring for dropdown items

* Avoid overlapping link focus rings with inset box-shadow

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aeaton-overleaf <75253002+aeaton-overleaf@users.noreply.github.com>
Co-authored-by: Rebeka <o.dekany@gmail.com>
GitOrigin-RevId: ce4d1b01f04476fd154b6c05a52fc5632bf8b8dc
This commit is contained in:
Copilot
2026-04-07 13:27:06 +02:00
committed by Copybot
parent ea699c65b5
commit fb9fed2b74
5 changed files with 29 additions and 34 deletions
@@ -64,19 +64,14 @@ export function setupSearch(formEl: Element) {
linkEl.setAttribute('role', 'menuitem')
liEl.append(linkEl)
const contentWrapperEl = document.createElement('div')
const contentWrapperEl = document.createElement('span')
contentWrapperEl.className = 'dropdown-item-description-container'
linkEl.append(contentWrapperEl)
const pageNameEl = document.createElement('div')
const pageNameEl = document.createElement('span')
pageNameEl.innerHTML = DOMPurify.sanitize(rawPageName)
contentWrapperEl.append(pageNameEl)
const iconEl = materialIcon('open_in_new')
iconEl.classList.add('dropdown-item-trailing-icon')
iconEl.setAttribute('aria-hidden', 'true')
contentWrapperEl.append(iconEl)
if (sectionName) {
const sectionEl = document.createElement('span')
sectionEl.className = 'dropdown-item-description'
@@ -84,6 +79,11 @@ export function setupSearch(formEl: Element) {
contentWrapperEl.append(sectionEl)
}
const iconEl = materialIcon('open_in_new')
iconEl.classList.add('dropdown-item-trailing-icon')
iconEl.setAttribute('aria-hidden', 'true')
linkEl.append(iconEl)
resultsContainerEl.append(liEl)
}
if (nbHits > 0) {
@@ -83,17 +83,15 @@ function DropdownItem(
ref={ref}
>
{leadingIconComponent}
<div
className={classnames({
'dropdown-item-description-container': description,
})}
>
{children}
{trailingIconComponent}
{description && (
{description ? (
<span className="dropdown-item-description-container">
{children}
<span className="dropdown-item-description">{description}</span>
)}
</div>
</span>
) : (
children
)}
{trailingIconComponent}
</BS5DropdownItem>
)
}
@@ -117,7 +117,8 @@
}
@mixin box-shadow-button-input {
box-shadow: 0 0 0 2px var(--blue-30);
box-shadow: inset 0 0 0 2px var(--blue-30);
outline: 2px solid transparent;
}
@mixin animation($animation) {
@@ -110,11 +110,9 @@ $dropdown-item-min-height: 36px;
--bs-dropdown-item-border-radius: var(--border-radius-base);
display: grid;
grid-auto-flow: column;
place-content: center start;
display: flex;
align-items: center;
min-height: $dropdown-item-min-height; // a minimum height of 36px to be accessible for touch screens
position: relative;
background-color: var(--dropdown-background);
&:active {
@@ -131,6 +129,10 @@ $dropdown-item-min-height: 36px;
background: none;
}
&:focus-visible {
@include box-shadow-button-input;
}
&:hover:not(.active),
&:focus-visible:not(.active),
&.nested-dropdown-toggle-shown {
@@ -179,17 +181,16 @@ $dropdown-item-min-height: 36px;
}
.dropdown-item-description-container {
grid-auto-flow: row;
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
min-height: 44px;
display: grid;
place-content: center start;
}
.dropdown-item-trailing-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
margin-left: auto;
padding-left: var(--spacing-04);
}
.dropdown-item-leading-icon {
@@ -123,11 +123,6 @@
}
}
> a:focus,
button:focus {
outline: none;
}
> a:not(.btn),
> button,
.toolbar-left > a:not(.btn),