👁️‍🗨️ fix: Replace Select with Menu in AccountSettings for Screen Reader Accuracy (#11980)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions

AccountSettings was using Select, but it makes more sense (for a11y)
to use Menu. The Select has the wrong role & behavior for the purpose
of AccountSettings; the "listbox" role it uses is for selecting
values in a form.

Menu matches the actual content better for screen readers; the
"menu" role is more appropriate for selecting one of a number of links.
This commit is contained in:
Daniel Lew 2026-02-28 15:58:50 -06:00 committed by GitHub
parent 723acd830c
commit 0e5ee379b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,5 @@
import { useState, memo, useRef } from 'react'; import { useState, memo, useRef } from 'react';
import * as Select from '@ariakit/react/select'; import * as Menu from '@ariakit/react/menu';
import { FileText, LogOut } from 'lucide-react'; import { FileText, LogOut } from 'lucide-react';
import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client'; import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client';
import { MyFilesModal } from '~/components/Chat/Input/Files/MyFilesModal'; import { MyFilesModal } from '~/components/Chat/Input/Files/MyFilesModal';
@ -20,8 +20,8 @@ function AccountSettings() {
const accountSettingsButtonRef = useRef<HTMLButtonElement>(null); const accountSettingsButtonRef = useRef<HTMLButtonElement>(null);
return ( return (
<Select.SelectProvider> <Menu.MenuProvider>
<Select.Select <Menu.MenuButton
ref={accountSettingsButtonRef} ref={accountSettingsButtonRef}
aria-label={localize('com_nav_account_settings')} aria-label={localize('com_nav_account_settings')}
data-testid="nav-user" data-testid="nav-user"
@ -38,8 +38,8 @@ function AccountSettings() {
> >
{user?.name ?? user?.username ?? localize('com_nav_user')} {user?.name ?? user?.username ?? localize('com_nav_user')}
</div> </div>
</Select.Select> </Menu.MenuButton>
<Select.SelectPopover <Menu.Menu
className="account-settings-popover popover-ui z-[125] w-[305px] rounded-lg md:w-[244px]" className="account-settings-popover popover-ui z-[125] w-[305px] rounded-lg md:w-[244px]"
style={{ style={{
transformOrigin: 'bottom', transformOrigin: 'bottom',
@ -59,43 +59,29 @@ function AccountSettings() {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
<Select.SelectItem <Menu.MenuItem onClick={() => setShowFiles(true)} className="select-item text-sm">
value=""
onClick={() => setShowFiles(true)}
className="select-item text-sm"
>
<FileText className="icon-md" aria-hidden="true" /> <FileText className="icon-md" aria-hidden="true" />
{localize('com_nav_my_files')} {localize('com_nav_my_files')}
</Select.SelectItem> </Menu.MenuItem>
{startupConfig?.helpAndFaqURL !== '/' && ( {startupConfig?.helpAndFaqURL !== '/' && (
<Select.SelectItem <Menu.MenuItem
value=""
onClick={() => window.open(startupConfig?.helpAndFaqURL, '_blank')} onClick={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
className="select-item text-sm" className="select-item text-sm"
> >
<LinkIcon aria-hidden="true" /> <LinkIcon aria-hidden="true" />
{localize('com_nav_help_faq')} {localize('com_nav_help_faq')}
</Select.SelectItem> </Menu.MenuItem>
)} )}
<Select.SelectItem <Menu.MenuItem onClick={() => setShowSettings(true)} className="select-item text-sm">
value=""
onClick={() => setShowSettings(true)}
className="select-item text-sm"
>
<GearIcon className="icon-md" aria-hidden="true" /> <GearIcon className="icon-md" aria-hidden="true" />
{localize('com_nav_settings')} {localize('com_nav_settings')}
</Select.SelectItem> </Menu.MenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<Select.SelectItem <Menu.MenuItem onClick={() => logout()} className="select-item text-sm">
aria-selected={true}
onClick={() => logout()}
value="logout"
className="select-item text-sm"
>
<LogOut className="icon-md" aria-hidden="true" /> <LogOut className="icon-md" aria-hidden="true" />
{localize('com_nav_log_out')} {localize('com_nav_log_out')}
</Select.SelectItem> </Menu.MenuItem>
</Select.SelectPopover> </Menu.Menu>
{showFiles && ( {showFiles && (
<MyFilesModal <MyFilesModal
open={showFiles} open={showFiles}
@ -104,7 +90,7 @@ function AccountSettings() {
/> />
)} )}
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />} {showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
</Select.SelectProvider> </Menu.MenuProvider>
); );
} }