Environment
- @nuxt/ui 4.8.1 (Vue +
@nuxt/ui/vite plugin), reproducible with the default theme anywhere, including the docs demos
- Latest release checked: v4.8.2 (theme unchanged on
v4 branch)
Is this bug related to Nuxt or Vue?
Both (default theme, framework-independent)
Package
v4.x
Version
v4.8.1 / v4 branch
Reproduction
Open any USelectMenu/USelect demo on the docs with the default theme, e.g. https://ui.nuxt.com/docs/components/select-menu, open the menu and hover an option or navigate with arrow keys. The highlight is barely perceptible in light mode and effectively invisible in dark mode. (No custom playground needed — this is the default theme on the docs site itself; happy to provide a play.ui.nuxt.com link if required.)
Description
Where the problem is
The highlighted (hovered / keyboard-active) option paints its background via a ::before overlay using bg-elevated/50:
data-highlighted:not-data-disabled:before:bg-elevated/50
With the default neutral: 'slate' palette this computes to:
| Mode |
Menu background (bg-default) |
Highlight (bg-elevated/50 blended) |
Contrast ratio |
| light |
#ffffff |
slate-100 @ 50% → ≈ #f8fafc |
≈ 1.03:1 |
| dark |
slate-900 #0f172a |
slate-800 @ 50% → ≈ #162032 |
≈ 1.1:1 |
WCAG 2.1 SC 1.4.11 Non-text Contrast requires 3:1 for visual information that identifies UI component states. Both modes are an order of magnitude below that.
This is more than a cosmetic issue: inside an open listbox/combobox, DOM focus stays on the trigger/input and arrow-key navigation moves only data-highlighted. The highlight therefore is the focus indicator for keyboard users (SC 2.4.7 Focus Visible), and at ~1:1 contrast keyboard users — especially low-vision users — cannot tell which option is active.
Same pattern in sibling components
Also note the active variant (text-highlighted before:bg-elevated, e.g. select.ts active.true) is solid bg-elevated — slate-100 on white ≈ 1.07:1 — though the selected state at least has the trailing check icon as a non-color indicator.
Suggested direction
A solid, sufficiently dark/light highlight — the classic pattern used by native selects and most design systems — passes comfortably, e.g. primary-600 with inverted text in light mode (white on indigo-600 ≈ 6.3:1; bg vs white menu ≈ 6.3:1) and primary-400 with inverted text in dark mode (slate-900 on indigo-400 ≈ 5.8:1). Alternatively keep it neutral but raise the tone well past elevated/50 and pair it with a visible non-background cue.
Workaround we ship today (app config / vite plugin ui option):
select: {
variants: {
active: {
true: { item: 'data-highlighted:not-data-disabled:text-inverted data-highlighted:not-data-disabled:before:bg-(--ui-color-primary-600) dark:data-highlighted:not-data-disabled:before:bg-(--ui-color-primary-400)' },
false: {
item: 'data-highlighted:not-data-disabled:text-inverted data-highlighted:not-data-disabled:before:bg-(--ui-color-primary-600) dark:data-highlighted:not-data-disabled:before:bg-(--ui-color-primary-400)',
itemLeadingIcon: 'group-data-highlighted:not-group-data-disabled:text-inverted'
}
}
}
}
This came out of a WCAG audit of our app (the audit flagged the default USelect highlight at ~1:1) — happy to open a PR if you agree on the direction.
Additional context
Related in spirit to #1284 (default theme failing an accessibility audit), but a different component and criterion.
Logs
Environment
@nuxt/ui/viteplugin), reproducible with the default theme anywhere, including the docs demosv4branch)Is this bug related to Nuxt or Vue?
Both (default theme, framework-independent)
Package
v4.x
Version
v4.8.1 / v4 branch
Reproduction
Open any
USelectMenu/USelectdemo on the docs with the default theme, e.g. https://ui.nuxt.com/docs/components/select-menu, open the menu and hover an option or navigate with arrow keys. The highlight is barely perceptible in light mode and effectively invisible in dark mode. (No custom playground needed — this is the default theme on the docs site itself; happy to provide a play.ui.nuxt.com link if required.)Description
Where the problem is
The highlighted (hovered / keyboard-active) option paints its background via a
::beforeoverlay usingbg-elevated/50:src/theme/select.ts#L20(inherited bySelectMenuviadefuFninsrc/theme/select-menu.ts):With the default
neutral: 'slate'palette this computes to:bg-default)bg-elevated/50blended)#ffffff#f8fafc#0f172a#162032WCAG 2.1 SC 1.4.11 Non-text Contrast requires 3:1 for visual information that identifies UI component states. Both modes are an order of magnitude below that.
This is more than a cosmetic issue: inside an open listbox/combobox, DOM focus stays on the trigger/input and arrow-key navigation moves only
data-highlighted. The highlight therefore is the focus indicator for keyboard users (SC 2.4.7 Focus Visible), and at ~1:1 contrast keyboard users — especially low-vision users — cannot tell which option is active.Same pattern in sibling components
DropdownMenu:src/theme/dropdown-menu.ts#L37—data-highlighted:before:bg-elevated/50CommandPalette:src/theme/command-palette.ts#L125—data-highlighted:not-data-disabled:before:bg-elevated/50InputMenupresumably as well (shares the select theme)Also note the
activevariant (text-highlighted before:bg-elevated, e.g. select.tsactive.true) is solidbg-elevated— slate-100 on white ≈ 1.07:1 — though the selected state at least has the trailing check icon as a non-color indicator.Suggested direction
A solid, sufficiently dark/light highlight — the classic pattern used by native selects and most design systems — passes comfortably, e.g. primary-600 with inverted text in light mode (white on indigo-600 ≈ 6.3:1; bg vs white menu ≈ 6.3:1) and primary-400 with inverted text in dark mode (slate-900 on indigo-400 ≈ 5.8:1). Alternatively keep it neutral but raise the tone well past
elevated/50and pair it with a visible non-background cue.Workaround we ship today (app config / vite plugin
uioption):This came out of a WCAG audit of our app (the audit flagged the default
USelecthighlight at ~1:1) — happy to open a PR if you agree on the direction.Additional context
Related in spirit to #1284 (default theme failing an accessibility audit), but a different component and criterion.
Logs