fix(a11y): improve model selector accessibility with proper listbox/option pattern (#21706)

- Replace incorrect aria-roledescription='model-item' with role='option' and aria-selected on ModelItem.svelte. The previous attribute was not a valid ARIA role description and provided no useful information to screen readers.

- Add contextual aria-label to each model item button (e.g. 'Select GPT-4 model') instead of just the raw model name, making the action clear to screen reader users.

- Add role='listbox' and aria-label='Available models' to the scrollable model list container in Selector.svelte so screen readers announce the container's purpose and navigate items correctly.

- Make the model selector trigger button's aria-label dynamic: it now announces 'Selected model: GPT-4' when a model is selected, falling back to 'Select a model' when nothing is selected.

- Add aria-label to the eject (unload) button in ModelItem.svelte so screen readers announce its purpose.

- Add aria-label to the cancel download button in Selector.svelte with the specific model name being canceled.

- Improve model profile image alt text from generic 'Model' to contextual '{{modelName}} profile image'.
This commit is contained in:
Classic298
2026-02-21 23:14:09 +01:00
committed by GitHub
parent b559606387
commit 08f1c823ad
2 changed files with 9 additions and 4 deletions

View File

@@ -44,8 +44,9 @@
</script>
<button
aria-roledescription="model-item"
aria-label={item.label}
role="option"
aria-selected={value === item.value}
aria-label={$i18n.t('Select {{modelName}} model', { modelName: item.label })}
class="flex group/item w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl cursor-pointer data-highlighted:bg-muted {index ===
selectedModelIdx
? 'bg-gray-100 dark:bg-gray-800 group-hover:bg-transparent'
@@ -78,7 +79,7 @@
<Tooltip content={$user?.role === 'admin' ? (item?.value ?? '') : ''} placement="top-start">
<img
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${item.model.id}&lang=${$i18n.language}`}
alt="Model"
alt={$i18n.t('{{modelName}} profile image', { modelName: item.label })}
class="rounded-full size-5 flex items-center"
loading="lazy"
/>
@@ -235,6 +236,7 @@
>
<button
class="flex"
aria-label={$i18n.t('Eject model')}
on:click={(e) => {
e.preventDefault();
e.stopPropagation();

View File

@@ -402,7 +402,7 @@
class="relative w-full {($settings?.highContrastMode ?? false)
? ''
: 'outline-hidden focus:outline-hidden'}"
aria-label={placeholder}
aria-label={selectedModel ? $i18n.t('Selected model: {{modelName}}', { modelName: selectedModel.label }) : placeholder}
id="model-selector-{id}-button"
>
<div
@@ -600,6 +600,8 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="max-h-64 overflow-y-auto"
role="listbox"
aria-label={$i18n.t('Available models')}
bind:this={listContainer}
on:scroll={() => {
listScrollTop = listContainer.scrollTop;
@@ -681,6 +683,7 @@
<Tooltip content={$i18n.t('Cancel')}>
<button
class="text-gray-800 dark:text-gray-100"
aria-label={$i18n.t('Cancel download of {{model}}', { model: model })}
on:click={() => {
cancelModelPullHandler(model);
}}