fix: name the switches in Admin Settings rows (WCAG 4.1.2, 1.3.1) (#27510)
`common/Switch.svelte` already accepts `id`, `ariaLabel` and `ariaLabelledbyId`, but **not one of the 148 `<Switch>` instances under `src/lib/components/admin/` passes any of them**. `admin/Settings/AdminSettingRow.svelte` renders the row label as a plain `<div>` and the control in a **sibling** slot, so there is nothing tying them together. bits-ui renders the switch as a `<button role="switch">` whose subtree is a text free thumb, so it has no accessible name from any source. A screen reader user working through Admin Settings hears a long run of "switch, on" and "switch, off" with no indication of what any of them controls. Breaks WCAG 4.1.2 Name, Role, Value (Level A) and 1.3.1 Info and Relationships (Level A). Fix: `AdminSettingRow` mints a per instance id, puts it on the label element, and hands it to the default slot, so each row's switch can point at the label that is already rendered next to it. This is the pattern `chat/Settings/Interface.svelte` already uses by hand in 45 places, hoisted into the shared row component so call sites stop hand authoring ids. `aria-labelledby` rather than a wrapping `<label>`: per HTML-AAM a `<button>` takes its name from `aria-labelledby`, then `aria-label`, then its own subtree, never from an associated `<label>`. `chat/Settings/Subagents.svelte` already wraps two switches in a `<label>` and they are still unnamed, which is the same trap. Using the existing label element also guarantees the accessible name is byte identical to the visible text, which keeps voice control working. The `description` paragraph deliberately sits outside the referenced element, so verbose help text is not pulled into the name. Scope: this covers the **72** switches that live inside an `AdminSettingRow`, which is every switch that flows through the shared row component. There are no rows containing more than one switch, so nothing is silently skipped. The remaining 76 admin switches are not in this component and are not touched. 64 of them are in `admin/Users/Groups/Permissions.svelte`, which hand rolls its own row markup, and the other 12 are per entity toggles in lists and dropdowns where the label is a dynamic row name. `Permissions.svelte` is the worst remaining case, 64 toggles with near identical adjacent labels, and it needs either its own labelling pass or a conversion to `AdminSettingRow` that changes its visual styling. Either way that is not an accessibility only diff and belongs in its own PR. All 12 touched files compile with the Svelte compiler with no new warnings and are Prettier clean. Severity: Serious. Admin Settings is unusable with a screen reader. ### Contributor License Agreement <!-- 🚨 DO NOT DELETE THE TEXT BELOW 🚨 Keep the "Contributor License Agreement" confirmation text intact. Deleting it will trigger the CLA-Bot to INVALIDATE your PR. Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA. --> - [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms. > [!NOTE] > Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
This commit is contained in:
1 parent
94b1b7e6b6
commit
7effaa05d1
12 files changed
+213
-72
No files matched your search
@@ -168,23 +168,29 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('New Sign Ups')}
|
||||
description={$i18n.t('Allow new users to create accounts.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
|
||||
<Switch bind:state={adminConfig.ENABLE_SIGNUP} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('API Keys')}
|
||||
description={$i18n.t('Allow users to create API keys for programmatic access.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEYS} />
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEYS} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEYS}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('API Key Endpoint Restrictions')}
|
||||
description={$i18n.t('Limit API keys to configured endpoints.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS} />
|
||||
<Switch
|
||||
bind:state={adminConfig.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS}
|
||||
@@ -238,8 +244,9 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Admin Details')}
|
||||
description={$i18n.t('Show admin contact details while an account waits for approval.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
|
||||
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if adminConfig.SHOW_ADMIN_DETAILS}
|
||||
@@ -288,8 +295,9 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('LDAP')}
|
||||
description={$i18n.t('Allow users to authenticate with an LDAP directory.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={ENABLE_LDAP} />
|
||||
<Switch bind:state={ENABLE_LDAP} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if ENABLE_LDAP}
|
||||
@@ -441,8 +449,9 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('TLS')}
|
||||
description={$i18n.t('Use TLS when connecting to the LDAP server.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={LDAP_SERVER.use_tls} />
|
||||
<Switch bind:state={LDAP_SERVER.use_tls} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if LDAP_SERVER.use_tls}
|
||||
@@ -460,8 +469,9 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Validate Certificate')}
|
||||
description={$i18n.t('Verify the LDAP server certificate when TLS is enabled.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={LDAP_SERVER.validate_cert} />
|
||||
<Switch bind:state={LDAP_SERVER.validate_cert} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingField
|
||||
@@ -481,16 +491,18 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Group Mapping')}
|
||||
description={$i18n.t('Map LDAP groups to Open WebUI groups.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={LDAP_SERVER.enable_group_management} />
|
||||
<Switch bind:state={LDAP_SERVER.enable_group_management} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if LDAP_SERVER.enable_group_management}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Auto-Create Groups')}
|
||||
description={$i18n.t('Create missing groups from LDAP groups.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={LDAP_SERVER.enable_group_creation} />
|
||||
<Switch bind:state={LDAP_SERVER.enable_group_creation} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingField
|
||||
@@ -631,22 +643,28 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('OAuth Signup')}
|
||||
description={$i18n.t('Allow users to create accounts through OAuth.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.ENABLE_OAUTH_SIGNUP} />
|
||||
<Switch bind:state={oauthConfig.ENABLE_OAUTH_SIGNUP} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Merge Accounts by Email')}
|
||||
description={$i18n.t('Link OAuth sign-ins to existing accounts with the same email.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.OAUTH_MERGE_ACCOUNTS_BY_EMAIL} />
|
||||
<Switch
|
||||
bind:state={oauthConfig.OAUTH_MERGE_ACCOUNTS_BY_EMAIL}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Auto Redirect')}
|
||||
description={$i18n.t('Send users directly to the OAuth provider from the sign-in page.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.OAUTH_AUTO_REDIRECT} />
|
||||
<Switch bind:state={oauthConfig.OAUTH_AUTO_REDIRECT} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingField
|
||||
@@ -663,8 +681,12 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Role Mapping')}
|
||||
description={$i18n.t('Map OAuth claims to Open WebUI roles.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT} />
|
||||
<Switch
|
||||
bind:state={oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT}
|
||||
@@ -707,16 +729,24 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Group Mapping')}
|
||||
description={$i18n.t('Map OAuth claims to Open WebUI groups.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT} />
|
||||
<Switch
|
||||
bind:state={oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Auto-Create Groups')}
|
||||
description={$i18n.t('Create missing groups from OAuth claims.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.ENABLE_OAUTH_GROUP_CREATION} />
|
||||
<Switch
|
||||
bind:state={oauthConfig.ENABLE_OAUTH_GROUP_CREATION}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
|
||||
@@ -747,22 +777,28 @@
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Update Email')}
|
||||
description={$i18n.t('Refresh the account email from OAuth on sign-in.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.OAUTH_UPDATE_EMAIL_ON_LOGIN} />
|
||||
<Switch bind:state={oauthConfig.OAUTH_UPDATE_EMAIL_ON_LOGIN} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Update Name')}
|
||||
description={$i18n.t('Refresh the account name from OAuth on sign-in.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.OAUTH_UPDATE_NAME_ON_LOGIN} />
|
||||
<Switch bind:state={oauthConfig.OAUTH_UPDATE_NAME_ON_LOGIN} ariaLabelledbyId={labelId} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Update Picture')}
|
||||
description={$i18n.t('Refresh the profile picture from OAuth on sign-in.')}
|
||||
let:labelId
|
||||
>
|
||||
<Switch bind:state={oauthConfig.OAUTH_UPDATE_PICTURE_ON_LOGIN} />
|
||||
<Switch
|
||||
bind:state={oauthConfig.OAUTH_UPDATE_PICTURE_ON_LOGIN}
|
||||
ariaLabelledbyId={labelId}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
|
||||
Reference in new issue
Block a user