Skip to content

Commit bbbdd63

Browse files
feat(ui): Add appearance option to disable autofocus (#8521)
1 parent 3987851 commit bbbdd63

8 files changed

Lines changed: 29 additions & 8 deletions

File tree

.changeset/lazy-hawk-old-fish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/ui': minor
3+
---
4+
5+
Add `autoFocus` appearance option to disable automatic input focusing

packages/ui/src/components/UserProfile/MfaBackupCodeCreateForm.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import { FormContainer } from '@/ui/elements/FormContainer';
1010
import { FullHeightLoader } from '@/ui/elements/FullHeightLoader';
1111
import { handleError } from '@/ui/utils/errorHandler';
1212

13-
import { Button, descriptors, localizationKeys, Text } from '../../customizables';
13+
import { Button, descriptors, localizationKeys, Text, useAppearance } from '../../customizables';
1414
import { MfaBackupCodeList } from './MfaBackupCodeList';
1515

1616
type MfaBackupCodeCreateFormProps = FormProps;
1717
export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCodeCreateFormProps) => {
1818
const { onSuccess, onReset } = props;
1919
const { user } = useUser();
2020
const card = useCardState();
21+
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
2122
const createBackupCode = useReverification(() => user?.createBackupCode());
2223
const [backupCode, setBackupCode] = React.useState<BackupCodeResource | undefined>(undefined);
2324

@@ -56,7 +57,7 @@ export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCo
5657

5758
<FormButtonContainer>
5859
<Button
59-
autoFocus
60+
autoFocus={optionAutoFocus}
6061
onClick={onSuccess}
6162
localizationKey={localizationKeys('userProfile.formButtonPrimary__finish')}
6263
elementDescriptor={descriptors.formButtonPrimary}

packages/ui/src/customizables/parseAppearance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const defaultOptions: ParsedOptions = {
5151
shimmer: true,
5252
animations: true,
5353
unsafe_disableDevelopmentModeWarnings: false,
54+
autoFocus: true,
5455
};
5556

5657
const defaultCaptchaOptions: ParsedCaptcha = {

packages/ui/src/elements/CodeControl.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { PropsWithChildren } from 'react';
55
import React, { useCallback } from 'react';
66

77
import type { LocalizationKey } from '../customizables';
8-
import { Box, descriptors, Flex, OTPInputSegment } from '../customizables';
8+
import { Box, descriptors, Flex, OTPInputSegment, useAppearance } from '../customizables';
99
import { useCardState } from '../elements/contexts';
1010
import { useLoadingStatus } from '../hooks';
1111
import type { PropsOfComponent } from '../styledSystem';
@@ -166,6 +166,7 @@ export const OTPResendButton = () => {
166166
export const OTPCodeControl = () => {
167167
const [disabled, setDisabled] = React.useState(false);
168168
const { otpControl, isLoading, isDisabled } = useOTPInputContext();
169+
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
169170
const { feedback, values, setValues, feedbackType, length } = otpControl.otpInputProps;
170171

171172
React.useEffect(() => {
@@ -180,7 +181,7 @@ export const OTPCodeControl = () => {
180181
sx={{ position: 'relative' }}
181182
>
182183
<OTPInput
183-
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
184+
autoFocus={optionAutoFocus} // eslint-disable-line jsx-a11y/no-autofocus
184185
aria-label='Enter verification code'
185186
aria-required
186187
maxLength={length}

packages/ui/src/elements/FieldControl.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Link,
1414
localizationKeys,
1515
Text,
16+
useAppearance,
1617
useLocalizations,
1718
} from '../customizables';
1819
import type { ElementDescriptor, ElementId } from '../customizables/elementDescriptors';
@@ -33,13 +34,15 @@ type FormControlProps = Omit<PropsOfComponent<typeof Input>, 'label' | 'placehol
3334

3435
const Root = (props: PropsWithChildren<FormControlProps>) => {
3536
const card = useCardState();
37+
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
3638
const { children, isDisabled: isDisabledProp, ...restProps } = props;
3739

3840
const isDisabled = isDisabledProp || card.isLoading;
3941

4042
const ctxProps = {
4143
...restProps,
4244
isDisabled,
45+
autoFocus: optionAutoFocus && restProps.autoFocus,
4346
};
4447

4548
return <FormFieldContextProvider {...ctxProps}>{children}</FormFieldContextProvider>;

packages/ui/src/elements/SuccessPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import type { Flex } from '../customizables';
4-
import { Button, Col, descriptors, Text } from '../customizables';
4+
import { Button, Col, descriptors, Text, useAppearance } from '../customizables';
55
import { useNavigateToFlowStart } from '../hooks';
66
import type { LocalizationKey } from '../localization';
77
import { localizationKeys } from '../localization';
@@ -23,6 +23,7 @@ type SuccessPageProps = Omit<PropsOfComponent<typeof Flex>, 'headerTitle' | 'tit
2323
export const SuccessPage = (props: SuccessPageProps) => {
2424
const { text, title, subtitle, finishLabel, onFinish, contents, finishButtonProps, headerBadgeText, ...rest } = props;
2525
const { navigateToFlowStart } = useNavigateToFlowStart();
26+
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
2627

2728
return (
2829
<Col
@@ -63,7 +64,7 @@ export const SuccessPage = (props: SuccessPageProps) => {
6364
{contents}
6465
<FormButtonContainer>
6566
<Button
66-
autoFocus
67+
autoFocus={optionAutoFocus}
6768
//Do we need a separate key here?
6869
localizationKey={finishLabel || localizationKeys('userProfile.formButtonPrimary__finish')}
6970
elementDescriptor={descriptors.formButtonPrimary}

packages/ui/src/elements/TagInput.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import type { LocalizationKey } from '../customizables';
4-
import { descriptors, Flex, Icon, Input, Text, useLocalizations } from '../customizables';
4+
import { descriptors, Flex, Icon, Input, Text, useAppearance, useLocalizations } from '../customizables';
55
import { Plus } from '../icons';
66
import type { PropsOfComponent } from '../styledSystem';
77
import { common } from '../styledSystem';
@@ -21,16 +21,18 @@ type TagInputProps = Pick<PropsOfComponent<typeof Flex>, 'sx'> & {
2121

2222
export const TagInput = (props: TagInputProps) => {
2323
const { t } = useLocalizations();
24+
const { autoFocus: optionAutoFocus } = useAppearance().parsedOptions;
2425
const {
2526
sx,
2627
placeholder,
2728
validate = () => true,
2829
value: valueProp,
2930
onChange: onChangeProp,
30-
autoFocus,
31+
autoFocus: autoFocusProp,
3132
validateUnsubmittedEmail = () => null,
3233
...rest
3334
} = props;
35+
const autoFocus = optionAutoFocus && autoFocusProp;
3436
const tags = valueProp.split(',').map(sanitize).filter(Boolean);
3537
const tagsSet = new Set(tags);
3638
const keyReleasedRef = React.useRef(true);

packages/ui/src/internal/appearance.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,13 @@ export type Options = {
995995
* @default false
996996
*/
997997
unsafe_disableDevelopmentModeWarnings?: boolean;
998+
/**
999+
* Controls whether inputs will automatically receive focus when a component is mounted.
1000+
* Set to false to prevent the components from auto-focusing any input fields.
1001+
*
1002+
* @default true
1003+
*/
1004+
autoFocus?: boolean;
9981005
};
9991006

10001007
export type CaptchaAppearanceOptions = {

0 commit comments

Comments
 (0)