feat: make OAuth admin settings read-only when ENABLE_OAUTH_PERSISTENT_CONFIG is off (#28276)

When ENABLE_OAUTH_PERSISTENT_CONFIG is off (the default), oauth.* config is
never persisted and is read from environment variables, but the admin panel
still let admins edit the OAuth/OIDC fields and silently dropped every save on
restart, which kept confusing users who missed the docs warning
(open-webui/open-webui#28247).

The OAuth/OIDC section is now read-only in that case: the admin oauth config
endpoint reports the flag and the UI wraps the section in a disabled fieldset,
slightly dimmed with every control inert but all values still visible, plus a
note naming the env var. Saving skips the OAuth POST since nothing can change.
With the flag enabled the section behaves exactly as before.

Known limits: the guard is UI-side only (the POST endpoint keeps accepting
writes, unchanged), and disabled fields mean values cannot be selected and the
masked client secret cannot be revealed while read-only. Switch.svelte gains a
disabled:cursor-not-allowed style that applies to any disabled switch app-wide.

Co-authored-by: Tim Baek <tim@openwebui.com>
This commit is contained in:
Classic298
2026-08-10 21:41:07 -06:00
committed by GitHub
co-authored by Tim Baek
parent a3e5d0b362
commit a680f21e12
3 changed files with 291 additions and 268 deletions
+9 -3
View File
@@ -1428,11 +1428,13 @@ def _parse_oauth_update_value(field: str, value):
async def get_oauth_config_values() -> dict: async def get_oauth_config_values() -> dict:
values = await Config.get_many(*OAUTH_CONFIG_KEYS.values()) values = await Config.get_many(*OAUTH_CONFIG_KEYS.values())
return { form_values = {
field: _format_oauth_form_value(field, values[storage_key]) field: _format_oauth_form_value(field, values[storage_key])
for field, storage_key in OAUTH_CONFIG_KEYS.items() for field, storage_key in OAUTH_CONFIG_KEYS.items()
if storage_key in values if storage_key in values
} }
form_values['ENABLE_OAUTH_PERSISTENT_CONFIG'] = Config.OAUTH_PERSISTENT_ENABLED
return form_values
def oauth_config_updates(data: dict) -> dict: def oauth_config_updates(data: dict) -> dict:
@@ -1443,12 +1445,16 @@ def oauth_config_updates(data: dict) -> dict:
} }
@router.get('/admin/config/oauth', response_model=OAuthConfigForm) class OAuthConfigResponse(OAuthConfigForm):
ENABLE_OAUTH_PERSISTENT_CONFIG: bool
@router.get('/admin/config/oauth', response_model=OAuthConfigResponse)
async def get_oauth_config(request: Request, user=Depends(get_admin_user)): async def get_oauth_config(request: Request, user=Depends(get_admin_user)):
return await get_oauth_config_values() return await get_oauth_config_values()
@router.post('/admin/config/oauth', response_model=OAuthConfigForm) @router.post('/admin/config/oauth', response_model=OAuthConfigResponse)
async def update_oauth_config(request: Request, form_data: OAuthConfigForm, user=Depends(get_admin_user)): async def update_oauth_config(request: Request, form_data: OAuthConfigForm, user=Depends(get_admin_user)):
await Config.upsert(oauth_config_updates(form_data.model_dump(exclude_none=True))) await Config.upsert(oauth_config_updates(form_data.model_dump(exclude_none=True)))
return await get_oauth_config_values() return await get_oauth_config_values()
@@ -49,6 +49,7 @@
}; };
let oauthConfig: any = null; let oauthConfig: any = null;
$: oauthEditable = oauthConfig?.ENABLE_OAUTH_PERSISTENT_CONFIG ?? true;
const inputClass = const inputClass =
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500'; 'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const textareaClass = const textareaClass =
@@ -74,7 +75,7 @@
}; };
const updateOAuthHandler = async () => { const updateOAuthHandler = async () => {
if (!oauthConfig) return true; if (!oauthConfig || !oauthEditable) return true;
const res = await updateOAuthConfig(localStorage.token, oauthConfig).catch((error) => { const res = await updateOAuthConfig(localStorage.token, oauthConfig).catch((error) => {
toast.error(`${error}`); toast.error(`${error}`);
return null; return null;
@@ -526,311 +527,327 @@
{#if oauthConfig} {#if oauthConfig}
<AdminSettingSection title={$i18n.t('OAuth / OIDC')}> <AdminSettingSection title={$i18n.t('OAuth / OIDC')}>
<AdminSettingRow {#if !oauthEditable}
label={$i18n.t('OAuth / OIDC')} <div
description={$i18n.t('Allow users to authenticate with an OAuth / OIDC provider.')} class="rounded-lg bg-yellow-500/10 px-2 py-1.5 text-[0.6875rem] text-yellow-700 dark:text-yellow-200"
let:labelId
>
<Switch bind:state={oauthConfig.ENABLE_OAUTH} ariaLabelledbyId={labelId} />
</AdminSettingRow>
{#if oauthConfig.ENABLE_OAUTH}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Provider Name')}
description={$i18n.t('Display name shown for the OAuth provider.')}
>
<input
class={inputClass}
placeholder="SSO"
bind:value={oauthConfig.OAUTH_PROVIDER_NAME}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Provider URL')}
description={$i18n.t('OpenID discovery URL for this provider.')}
>
<input
class={inputClass}
placeholder="https://accounts.google.com/.well-known/openid-configuration"
bind:value={oauthConfig.OPENID_PROVIDER_URL}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Client ID')}
description={$i18n.t('OAuth client identifier from the provider.')}
>
<input
class={inputClass}
placeholder={$i18n.t('Enter Client ID')}
bind:value={oauthConfig.OAUTH_CLIENT_ID}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Client Secret')}
description={$i18n.t('OAuth client secret from the provider.')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Client Secret')}
required={false}
bind:value={oauthConfig.OAUTH_CLIENT_SECRET}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Redirect URI')}
description={$i18n.t('Callback URI registered with the provider.')}
>
<input
class={inputClass}
placeholder={$i18n.t('Enter Redirect URI')}
bind:value={oauthConfig.OPENID_REDIRECT_URI}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Scopes')}
description={$i18n.t('OAuth scopes requested during sign-in.')}
>
<input
class={inputClass}
placeholder="openid email profile"
bind:value={oauthConfig.OAUTH_SCOPES}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Email Claim')}
description={$i18n.t('Claim used as the user email address.')}
>
<input
class={inputClass}
placeholder="email"
bind:value={oauthConfig.OAUTH_EMAIL_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Username Claim')}
description={$i18n.t('Claim used as the display name.')}
>
<input
class={inputClass}
placeholder="name"
bind:value={oauthConfig.OAUTH_USERNAME_CLAIM}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Picture Claim')}
description={$i18n.t('Claim used as the profile picture URL.')}
>
<input
class={inputClass}
placeholder="picture"
bind:value={oauthConfig.OAUTH_PICTURE_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Sub Claim')}
description={$i18n.t('Claim used as the stable user identifier.')}
>
<input
class={inputClass}
placeholder="sub"
bind:value={oauthConfig.OAUTH_SUB_CLAIM}
/>
</AdminSettingField>
</div>
<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} ariaLabelledbyId={labelId} /> {$i18n.t(
</AdminSettingRow> 'These settings are read from environment variables and cannot be edited here while {{ENV_VAR}} is disabled.',
{ ENV_VAR: 'ENABLE_OAUTH_PERSISTENT_CONFIG' }
<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}
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 </div>
> {/if}
<Switch bind:state={oauthConfig.OAUTH_AUTO_REDIRECT} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingField <fieldset
label={$i18n.t('Allowed Domains')} class="flex min-w-0 flex-col gap-2.5 disabled:cursor-not-allowed disabled:opacity-75"
description={$i18n.t('Email domains allowed to sign in with OAuth.')} disabled={!oauthEditable}
> >
<input
class={inputClass}
placeholder="* (all domains)"
bind:value={oauthConfig.OAUTH_ALLOWED_DOMAINS}
/>
</AdminSettingField>
<!-- LICENSE covers this Open WebUI wordmark.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow <AdminSettingRow
label={$i18n.t('Role Mapping')} label={$i18n.t('OAuth / OIDC')}
description={$i18n.t('Map OAuth claims to Open WebUI roles.')} description={$i18n.t('Allow users to authenticate with an OAuth / OIDC provider.')}
let:labelId let:labelId
> >
<Switch <Switch bind:state={oauthConfig.ENABLE_OAUTH} ariaLabelledbyId={labelId} />
bind:state={oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT}
ariaLabelledbyId={labelId}
/>
</AdminSettingRow> </AdminSettingRow>
{#if oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT} {#if oauthConfig.ENABLE_OAUTH}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField <AdminSettingField
label={$i18n.t('Roles Claim')} label={$i18n.t('Provider Name')}
description={$i18n.t('Claim containing provider roles.')} description={$i18n.t('Display name shown for the OAuth provider.')}
> >
<input <input
class={inputClass} class={inputClass}
placeholder="roles" placeholder="SSO"
bind:value={oauthConfig.OAUTH_ROLES_CLAIM} bind:value={oauthConfig.OAUTH_PROVIDER_NAME}
/> />
</AdminSettingField> </AdminSettingField>
<AdminSettingField <AdminSettingField
label={$i18n.t('Admin Roles')} label={$i18n.t('Provider URL')}
description={$i18n.t('Provider roles that grant admin access.')} description={$i18n.t('OpenID discovery URL for this provider.')}
> >
<input <input
class={inputClass} class={inputClass}
placeholder="admin" placeholder="https://accounts.google.com/.well-known/openid-configuration"
bind:value={oauthConfig.OAUTH_ADMIN_ROLES} bind:value={oauthConfig.OPENID_PROVIDER_URL}
/> />
</AdminSettingField> </AdminSettingField>
</div> </div>
<AdminSettingField <div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
label={$i18n.t('Allowed Roles')} <AdminSettingField
description={$i18n.t('Provider roles allowed to sign in.')} label={$i18n.t('Client ID')}
> description={$i18n.t('OAuth client identifier from the provider.')}
<input >
class={inputClass} <input
placeholder="*" class={inputClass}
bind:value={oauthConfig.OAUTH_ALLOWED_ROLES} placeholder={$i18n.t('Enter Client ID')}
/> bind:value={oauthConfig.OAUTH_CLIENT_ID}
</AdminSettingField> />
{/if} </AdminSettingField>
<!-- LICENSE covers this Open WebUI wordmark. <AdminSettingField
Do not alter, remove, obscure, or replace it except as LICENSE permits: label={$i18n.t('Client Secret')}
https://docs.openwebui.com/license. --> description={$i18n.t('OAuth client secret from the provider.')}
<AdminSettingRow >
label={$i18n.t('Group Mapping')} <SensitiveInput
description={$i18n.t('Map OAuth claims to Open WebUI groups.')} variant="settings"
let:labelId placeholder={$i18n.t('Enter Client Secret')}
> required={false}
<Switch bind:value={oauthConfig.OAUTH_CLIENT_SECRET}
bind:state={oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT} />
ariaLabelledbyId={labelId} </AdminSettingField>
/> </div>
</AdminSettingRow>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Redirect URI')}
description={$i18n.t('Callback URI registered with the provider.')}
>
<input
class={inputClass}
placeholder={$i18n.t('Enter Redirect URI')}
bind:value={oauthConfig.OPENID_REDIRECT_URI}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Scopes')}
description={$i18n.t('OAuth scopes requested during sign-in.')}
>
<input
class={inputClass}
placeholder="openid email profile"
bind:value={oauthConfig.OAUTH_SCOPES}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Email Claim')}
description={$i18n.t('Claim used as the user email address.')}
>
<input
class={inputClass}
placeholder="email"
bind:value={oauthConfig.OAUTH_EMAIL_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Username Claim')}
description={$i18n.t('Claim used as the display name.')}
>
<input
class={inputClass}
placeholder="name"
bind:value={oauthConfig.OAUTH_USERNAME_CLAIM}
/>
</AdminSettingField>
</div>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Picture Claim')}
description={$i18n.t('Claim used as the profile picture URL.')}
>
<input
class={inputClass}
placeholder="picture"
bind:value={oauthConfig.OAUTH_PICTURE_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Sub Claim')}
description={$i18n.t('Claim used as the stable user identifier.')}
>
<input
class={inputClass}
placeholder="sub"
bind:value={oauthConfig.OAUTH_SUB_CLAIM}
/>
</AdminSettingField>
</div>
{#if oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT}
<AdminSettingRow <AdminSettingRow
label={$i18n.t('Auto-Create Groups')} label={$i18n.t('OAuth Signup')}
description={$i18n.t('Create missing groups from OAuth claims.')} description={$i18n.t('Allow users to create accounts through OAuth.')}
let:labelId
>
<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 let:labelId
> >
<Switch <Switch
bind:state={oauthConfig.ENABLE_OAUTH_GROUP_CREATION} bind:state={oauthConfig.OAUTH_MERGE_ACCOUNTS_BY_EMAIL}
ariaLabelledbyId={labelId} ariaLabelledbyId={labelId}
/> />
</AdminSettingRow> </AdminSettingRow>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2"> <AdminSettingRow
<AdminSettingField label={$i18n.t('Auto Redirect')}
label={$i18n.t('Group Claim')} description={$i18n.t(
description={$i18n.t('Claim containing provider groups.')} 'Send users directly to the OAuth provider from the sign-in page.'
> )}
<input let:labelId
class={inputClass} >
placeholder="groups" <Switch bind:state={oauthConfig.OAUTH_AUTO_REDIRECT} ariaLabelledbyId={labelId} />
bind:value={oauthConfig.OAUTH_GROUP_CLAIM} </AdminSettingRow>
/>
</AdminSettingField> <AdminSettingField
label={$i18n.t('Allowed Domains')}
description={$i18n.t('Email domains allowed to sign in with OAuth.')}
>
<input
class={inputClass}
placeholder="* (all domains)"
bind:value={oauthConfig.OAUTH_ALLOWED_DOMAINS}
/>
</AdminSettingField>
<!-- LICENSE covers this Open WebUI wordmark.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<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}
ariaLabelledbyId={labelId}
/>
</AdminSettingRow>
{#if oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Roles Claim')}
description={$i18n.t('Claim containing provider roles.')}
>
<input
class={inputClass}
placeholder="roles"
bind:value={oauthConfig.OAUTH_ROLES_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Admin Roles')}
description={$i18n.t('Provider roles that grant admin access.')}
>
<input
class={inputClass}
placeholder="admin"
bind:value={oauthConfig.OAUTH_ADMIN_ROLES}
/>
</AdminSettingField>
</div>
<AdminSettingField <AdminSettingField
label={$i18n.t('Blocked Groups')} label={$i18n.t('Allowed Roles')}
description={$i18n.t('Provider groups blocked from signing in.')} description={$i18n.t('Provider roles allowed to sign in.')}
> >
<input <input
class={inputClass} class={inputClass}
placeholder={$i18n.t('Comma-separated group names')} placeholder="*"
bind:value={oauthConfig.OAUTH_BLOCKED_GROUPS} bind:value={oauthConfig.OAUTH_ALLOWED_ROLES}
/> />
</AdminSettingField> </AdminSettingField>
</div> {/if}
<!-- LICENSE covers this Open WebUI wordmark.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<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}
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}
ariaLabelledbyId={labelId}
/>
</AdminSettingRow>
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Group Claim')}
description={$i18n.t('Claim containing provider groups.')}
>
<input
class={inputClass}
placeholder="groups"
bind:value={oauthConfig.OAUTH_GROUP_CLAIM}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Blocked Groups')}
description={$i18n.t('Provider groups blocked from signing in.')}
>
<input
class={inputClass}
placeholder={$i18n.t('Comma-separated group names')}
bind:value={oauthConfig.OAUTH_BLOCKED_GROUPS}
/>
</AdminSettingField>
</div>
{/if}
<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}
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}
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}
ariaLabelledbyId={labelId}
/>
</AdminSettingRow>
{/if} {/if}
</fieldset>
<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}
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}
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}
ariaLabelledbyId={labelId}
/>
</AdminSettingRow>
{/if}
</AdminSettingSection> </AdminSettingSection>
{/if} {/if}
</div> </div>
+1 -1
View File
@@ -36,7 +36,7 @@
{id} {id}
aria-labelledby={ariaLabelledbyId || undefined} aria-labelledby={ariaLabelledbyId || undefined}
aria-label={ariaLabel || undefined} aria-label={ariaLabel || undefined}
class="relative h-4 min-h-4 w-7 shrink-0 cursor-pointer rounded-full mx-[0.0625rem] transition-colors duration-150 {($settings?.highContrastMode ?? class="relative h-4 min-h-4 w-7 shrink-0 cursor-pointer rounded-full mx-[0.0625rem] transition-colors duration-150 disabled:cursor-not-allowed {($settings?.highContrastMode ??
false) false)
? 'focus:outline focus:outline-2 focus:outline-gray-800 focus:dark:outline-gray-200' ? 'focus:outline focus:outline-2 focus:outline-gray-800 focus:dark:outline-gray-200'
: 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400 dark:focus-visible:outline-gray-500'} {state : 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400 dark:focus-visible:outline-gray-500'} {state