diff --git a/package-lock.json b/package-lock.json
index 1d6333ad719e..91c47de80672 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -96,7 +96,7 @@
         "react-native-pdf": "^6.7.3",
         "react-native-performance": "^5.1.0",
         "react-native-permissions": "^3.9.3",
-        "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
+        "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8",
         "react-native-plaid-link-sdk": "10.8.0",
         "react-native-qrcode-svg": "^6.2.0",
         "react-native-quick-sqlite": "^8.0.0-beta.2",
@@ -44514,9 +44514,9 @@
       }
     },
     "node_modules/react-native-picker-select": {
-      "version": "8.0.4",
-      "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
-      "integrity": "sha512-3U/mtHN/pKC5yXtJnqj5rre8+4YPSqoXCn/3qKjb5u8BMIiuc5H3KJ0ZbKlZEg/8Uh4j0cvrtcNasdPgMqRgCQ==",
+      "version": "8.1.0",
+      "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8",
+      "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==",
       "license": "MIT",
       "dependencies": {
         "lodash.isequal": "^4.5.0"
@@ -84854,9 +84854,9 @@
       "requires": {}
     },
     "react-native-picker-select": {
-      "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
-      "integrity": "sha512-3U/mtHN/pKC5yXtJnqj5rre8+4YPSqoXCn/3qKjb5u8BMIiuc5H3KJ0ZbKlZEg/8Uh4j0cvrtcNasdPgMqRgCQ==",
-      "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
+      "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8",
+      "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==",
+      "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8",
       "requires": {
         "lodash.isequal": "^4.5.0"
       }
diff --git a/package.json b/package.json
index 1453e85fef53..366f9cf5bac4 100644
--- a/package.json
+++ b/package.json
@@ -143,7 +143,7 @@
     "react-native-pdf": "^6.7.3",
     "react-native-performance": "^5.1.0",
     "react-native-permissions": "^3.9.3",
-    "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2",
+    "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8",
     "react-native-plaid-link-sdk": "10.8.0",
     "react-native-qrcode-svg": "^6.2.0",
     "react-native-quick-sqlite": "^8.0.0-beta.2",
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 97f24a2710aa..1bc06e231448 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -398,7 +398,7 @@ type OnyxValues = {
     [ONYXKEYS.IS_PLAID_DISABLED]: boolean;
     [ONYXKEYS.PLAID_LINK_TOKEN]: string;
     [ONYXKEYS.ONFIDO_TOKEN]: string;
-    [ONYXKEYS.NVP_PREFERRED_LOCALE]: ValueOf<typeof CONST.LOCALES>;
+    [ONYXKEYS.NVP_PREFERRED_LOCALE]: OnyxTypes.Locale;
     [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet;
     [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido;
     [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails;
diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js
deleted file mode 100644
index 83723541b16f..000000000000
--- a/src/components/LocalePicker.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import compose from '@libs/compose';
-import useTheme from '@styles/themes/useTheme';
-import useThemeStyles from '@styles/useThemeStyles';
-import * as App from '@userActions/App';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import Picker from './Picker';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
-
-const propTypes = {
-    /** Indicates which locale the user currently has selected */
-    preferredLocale: PropTypes.string,
-
-    /** Indicates size of a picker component and whether to render the label or not */
-    size: PropTypes.oneOf(['normal', 'small']),
-
-    ...withLocalizePropTypes,
-};
-
-const defaultProps = {
-    preferredLocale: CONST.LOCALES.DEFAULT,
-    size: 'normal',
-};
-
-function LocalePicker(props) {
-    const theme = useTheme();
-    const styles = useThemeStyles();
-    const localesToLanguages = _.map(CONST.LANGUAGES, (language) => ({
-        value: language,
-        label: props.translate(`languagePage.languages.${language}.label`),
-        keyForList: language,
-        isSelected: props.preferredLocale === language,
-    }));
-    return (
-        <Picker
-            label={props.size === 'normal' ? props.translate('languagePage.language') : null}
-            onInputChange={(locale) => {
-                if (locale === props.preferredLocale) {
-                    return;
-                }
-
-                App.setLocale(locale);
-            }}
-            items={localesToLanguages}
-            size={props.size}
-            value={props.preferredLocale}
-            containerStyles={props.size === 'small' ? [styles.pickerContainerSmall] : []}
-            backgroundColor={theme.signInPage}
-        />
-    );
-}
-
-LocalePicker.defaultProps = defaultProps;
-LocalePicker.propTypes = propTypes;
-LocalePicker.displayName = 'LocalePicker';
-
-export default compose(
-    withLocalize,
-    withOnyx({
-        preferredLocale: {
-            key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-        },
-    }),
-)(LocalePicker);
diff --git a/src/components/LocalePicker.tsx b/src/components/LocalePicker.tsx
new file mode 100644
index 000000000000..c04b0131744f
--- /dev/null
+++ b/src/components/LocalePicker.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import {OnyxEntry, withOnyx} from 'react-native-onyx';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
+import * as App from '@userActions/App';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Locale} from '@src/types/onyx';
+import Picker from './Picker';
+import type {PickerSize} from './Picker/types';
+
+type LocalePickerOnyxProps = {
+    /** Indicates which locale the user currently has selected */
+    preferredLocale: OnyxEntry<Locale>;
+};
+
+type LocalePickerProps = LocalePickerOnyxProps & {
+    /** Indicates size of a picker component and whether to render the label or not */
+    size?: PickerSize;
+};
+
+function LocalePicker({preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}: LocalePickerProps) {
+    const theme = useTheme();
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+    const localesToLanguages = CONST.LANGUAGES.map((language) => ({
+        value: language,
+        label: translate(`languagePage.languages.${language}.label`),
+        keyForList: language,
+        isSelected: preferredLocale === language,
+    }));
+
+    return (
+        <Picker
+            label={size === 'normal' ? translate('languagePage.language') : null}
+            onInputChange={(locale) => {
+                if (locale === preferredLocale) {
+                    return;
+                }
+
+                App.setLocale(locale);
+            }}
+            items={localesToLanguages}
+            size={size}
+            value={preferredLocale}
+            containerStyles={size === 'small' ? styles.pickerContainerSmall : {}}
+            backgroundColor={theme.signInPage}
+        />
+    );
+}
+
+LocalePicker.displayName = 'LocalePicker';
+
+export default withOnyx<LocalePickerProps, LocalePickerOnyxProps>({
+    preferredLocale: {
+        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+    },
+})(LocalePicker);
diff --git a/src/components/Picker/BasePicker.js b/src/components/Picker/BasePicker.js
deleted file mode 100644
index 8eaf33ab7423..000000000000
--- a/src/components/Picker/BasePicker.js
+++ /dev/null
@@ -1,311 +0,0 @@
-import PropTypes from 'prop-types';
-import React, {useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
-import RNPickerSelect from 'react-native-picker-select';
-import _ from 'underscore';
-import FormHelpMessage from '@components/FormHelpMessage';
-import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
-import refPropTypes from '@components/refPropTypes';
-import {ScrollContext} from '@components/ScrollViewWithContext';
-import Text from '@components/Text';
-import useTheme from '@styles/themes/useTheme';
-import useThemeStyles from '@styles/useThemeStyles';
-
-const propTypes = {
-    /** A forwarded ref */
-    forwardedRef: refPropTypes,
-
-    /** BasePicker label */
-    label: PropTypes.string,
-
-    /** Should the picker appear disabled? */
-    isDisabled: PropTypes.bool,
-
-    /** Input value */
-    value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-
-    /** The items to display in the list of selections */
-    items: PropTypes.arrayOf(
-        PropTypes.shape({
-            /** The value of the item that is being selected */
-            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
-
-            /** The text to display for the item */
-            label: PropTypes.string.isRequired,
-        }),
-    ).isRequired,
-
-    /** Something to show as the placeholder before something is selected */
-    placeholder: PropTypes.shape({
-        /** The value of the placeholder item, usually an empty string */
-        value: PropTypes.string,
-
-        /** The text to be displayed as the placeholder */
-        label: PropTypes.string,
-    }),
-
-    /** Error text to display */
-    errorText: PropTypes.string,
-
-    /** Customize the BasePicker container */
-    // eslint-disable-next-line react/forbid-prop-types
-    containerStyles: PropTypes.arrayOf(PropTypes.object),
-
-    /** Customize the BasePicker background color */
-    backgroundColor: PropTypes.string,
-
-    /** The ID used to uniquely identify the input in a Form */
-    inputID: PropTypes.string,
-
-    /** Saves a draft of the input value when used in a form */
-    // eslint-disable-next-line react/no-unused-prop-types
-    shouldSaveDraft: PropTypes.bool,
-
-    /** A callback method that is called when the value changes and it receives the selected value as an argument */
-    onInputChange: PropTypes.func.isRequired,
-
-    /** Size of a picker component */
-    size: PropTypes.oneOf(['normal', 'small']),
-
-    /** An icon to display with the picker */
-    icon: PropTypes.func,
-
-    /** Whether we should forward the focus/blur calls to the inner picker * */
-    shouldFocusPicker: PropTypes.bool,
-
-    /** Callback called when click or tap out of BasePicker */
-    onBlur: PropTypes.func,
-
-    /** Additional events passed to the core BasePicker for specific platforms such as web */
-    additionalPickerEvents: PropTypes.func,
-
-    /** Hint text that appears below the picker */
-    hintText: PropTypes.string,
-};
-
-const defaultProps = {
-    forwardedRef: undefined,
-    label: '',
-    isDisabled: false,
-    errorText: '',
-    hintText: '',
-    containerStyles: [],
-    backgroundColor: undefined,
-    inputID: undefined,
-    shouldSaveDraft: false,
-    value: undefined,
-    placeholder: {},
-    size: 'normal',
-    icon: undefined,
-    shouldFocusPicker: false,
-    onBlur: () => {},
-    additionalPickerEvents: () => {},
-};
-
-function BasePicker(props) {
-    const theme = useTheme();
-    const styles = useThemeStyles();
-
-    const [isHighlighted, setIsHighlighted] = useState(false);
-
-    // reference to the root View
-    const root = useRef(null);
-
-    // reference to @react-native-picker/picker
-    const picker = useRef(null);
-
-    // Windows will reuse the text color of the select for each one of the options
-    // so we might need to color accordingly so it doesn't blend with the background.
-    const placeholder = _.isEmpty(props.placeholder)
-        ? {}
-        : {
-              ...props.placeholder,
-              color: theme.pickerOptionsTextColor,
-          };
-
-    useEffect(() => {
-        if (props.value || !props.items || props.items.length !== 1 || !props.onInputChange) {
-            return;
-        }
-
-        // When there is only 1 element in the selector, we do the user a favor and automatically select it for them
-        // so they don't have to spend extra time selecting the only possible value.
-        props.onInputChange(props.items[0].value, 0);
-
-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [props.items]);
-
-    const context = useContext(ScrollContext);
-
-    /**
-     * Forms use inputID to set values. But BasePicker passes an index as the second parameter to onInputChange
-     * We are overriding this behavior to make BasePicker work with Form
-     * @param {String} value
-     * @param {Number} index
-     */
-    const onInputChange = (value, index) => {
-        if (props.inputID) {
-            props.onInputChange(value);
-            return;
-        }
-
-        props.onInputChange(value, index);
-    };
-
-    const enableHighlight = () => {
-        setIsHighlighted(true);
-    };
-
-    const disableHighlight = () => {
-        setIsHighlighted(false);
-    };
-
-    const {icon, size} = props;
-
-    const iconToRender = useMemo(() => {
-        if (icon) {
-            return () => icon(size);
-        }
-
-        // eslint-disable-next-line react/display-name
-        return () => (
-            <Icon
-                src={Expensicons.DownArrow}
-                // eslint-disable-next-line react/jsx-props-no-spreading
-                {...(size === 'small'
-                    ? {
-                          width: styles.pickerSmall().icon.width,
-                          height: styles.pickerSmall().icon.height,
-                      }
-                    : {})}
-            />
-        );
-    }, [icon, size, styles]);
-
-    useImperativeHandle(props.forwardedRef, () => ({
-        /**
-         * Focuses the picker (if configured to do so)
-         *
-         * This method is used by Form
-         */
-        focus() {
-            if (!props.shouldFocusPicker) {
-                return;
-            }
-
-            // Defer the focusing to work around a bug on Mobile Safari, where focusing the `select` element in the
-            // same task when we scrolled to it left that element in a glitched state, where the dropdown list can't
-            // be opened until the element gets re-focused
-            _.defer(() => {
-                picker.current.focus();
-            });
-        },
-
-        /**
-         * Like measure(), but measures the view relative to an ancestor
-         *
-         * This method is used by Form when scrolling to the input
-         *
-         * @param {Object} relativeToNativeComponentRef - reference to an ancestor
-         * @param {function(x: number, y: number, width: number, height: number): void} onSuccess - callback called on success
-         * @param {function(): void} onFail - callback called on failure
-         */
-        measureLayout(relativeToNativeComponentRef, onSuccess, onFail) {
-            if (!root.current) {
-                return;
-            }
-
-            root.current.measureLayout(relativeToNativeComponentRef, onSuccess, onFail);
-        },
-    }));
-
-    const hasError = !_.isEmpty(props.errorText);
-
-    if (props.isDisabled) {
-        return (
-            <View>
-                {Boolean(props.label) && (
-                    <Text
-                        style={[styles.textLabelSupporting, styles.mb1]}
-                        numberOfLines={1}
-                    >
-                        {props.label}
-                    </Text>
-                )}
-                <Text numberOfLines={1}>{props.value}</Text>
-                {Boolean(props.hintText) && <Text style={[styles.textLabel, styles.colorMuted, styles.mt2]}>{props.hintText}</Text>}
-            </View>
-        );
-    }
-
-    return (
-        <>
-            <View
-                ref={root}
-                style={[
-                    styles.pickerContainer,
-                    props.isDisabled && styles.inputDisabled,
-                    ...props.containerStyles,
-                    isHighlighted && styles.borderColorFocus,
-                    hasError && styles.borderColorDanger,
-                ]}
-            >
-                {props.label && <Text style={[styles.pickerLabel, styles.textLabelSupporting, styles.pointerEventsNone]}>{props.label}</Text>}
-                <RNPickerSelect
-                    onValueChange={onInputChange}
-                    // We add a text color to prevent white text on white background dropdown items on Windows
-                    items={_.map(props.items, (item) => ({...item, color: theme.pickerOptionsTextColor}))}
-                    style={size === 'normal' ? styles.picker(props.isDisabled, props.backgroundColor) : styles.pickerSmall(props.backgroundColor)}
-                    useNativeAndroidPickerStyle={false}
-                    placeholder={placeholder}
-                    value={props.value}
-                    Icon={iconToRender}
-                    disabled={props.isDisabled}
-                    fixAndroidTouchableBug
-                    onOpen={enableHighlight}
-                    onClose={disableHighlight}
-                    textInputProps={{
-                        allowFontScaling: false,
-                    }}
-                    pickerProps={{
-                        ref: picker,
-                        tabIndex: -1,
-                        onFocus: enableHighlight,
-                        onBlur: () => {
-                            disableHighlight();
-                            props.onBlur();
-                        },
-                        ...props.additionalPickerEvents(enableHighlight, (value, index) => {
-                            onInputChange(value, index);
-                            disableHighlight();
-                        }),
-                    }}
-                    scrollViewRef={context && context.scrollViewRef}
-                    scrollViewContentOffsetY={context && context.contentOffsetY}
-                />
-            </View>
-            <FormHelpMessage message={props.errorText} />
-            {Boolean(props.hintText) && <Text style={[styles.textLabel, styles.colorMuted, styles.mt2]}>{props.hintText}</Text>}
-        </>
-    );
-}
-
-BasePicker.propTypes = propTypes;
-BasePicker.defaultProps = defaultProps;
-BasePicker.displayName = 'BasePicker';
-
-const BasePickerWithRef = React.forwardRef((props, ref) => (
-    <BasePicker
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        // Forward the ref to BasePicker, as we implement imperative methods there
-        forwardedRef={ref}
-        // eslint-disable-next-line react/prop-types
-        key={props.inputID}
-    />
-));
-
-BasePickerWithRef.displayName = 'BasePickerWithRef';
-
-export default BasePickerWithRef;
diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx
new file mode 100644
index 000000000000..dfb2d6332da5
--- /dev/null
+++ b/src/components/Picker/BasePicker.tsx
@@ -0,0 +1,206 @@
+import lodashDefer from 'lodash/defer';
+import React, {ForwardedRef, forwardRef, ReactElement, ReactNode, RefObject, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
+import {ScrollView, View} from 'react-native';
+import RNPickerSelect from 'react-native-picker-select';
+import FormHelpMessage from '@components/FormHelpMessage';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import Text from '@components/Text';
+import useScrollContext from '@hooks/useScrollContext';
+import useTheme from '@styles/themes/useTheme';
+import useThemeStyles from '@styles/useThemeStyles';
+import type {BasePickerHandle, BasePickerProps} from './types';
+
+type IconToRender = () => ReactElement;
+
+function BasePicker<TPickerValue>(
+    {
+        items,
+        backgroundColor,
+        inputID,
+        value,
+        onInputChange,
+        icon,
+        label = '',
+        isDisabled = false,
+        errorText = '',
+        hintText = '',
+        containerStyles,
+        placeholder = {},
+        size = 'normal',
+        shouldFocusPicker = false,
+        onBlur = () => {},
+        additionalPickerEvents = () => {},
+    }: BasePickerProps<TPickerValue>,
+    ref: ForwardedRef<BasePickerHandle>,
+) {
+    const theme = useTheme();
+    const styles = useThemeStyles();
+
+    const [isHighlighted, setIsHighlighted] = useState(false);
+
+    // reference to the root View
+    const root = useRef<View>(null);
+
+    // reference to @react-native-picker/picker
+    const picker = useRef<RNPickerSelect>(null);
+
+    // Windows will reuse the text color of the select for each one of the options
+    // so we might need to color accordingly so it doesn't blend with the background.
+    const pickerPlaceholder = Object.keys(placeholder).length > 0 ? {...placeholder, color: theme.pickerOptionsTextColor} : {};
+
+    useEffect(() => {
+        if (!!value || !items || items.length !== 1 || !onInputChange) {
+            return;
+        }
+
+        // When there is only 1 element in the selector, we do the user a favor and automatically select it for them
+        // so they don't have to spend extra time selecting the only possible value.
+        onInputChange(items[0].value, 0);
+
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [items]);
+
+    const context = useScrollContext();
+
+    /**
+     * Forms use inputID to set values. But BasePicker passes an index as the second parameter to onValueChange
+     * We are overriding this behavior to make BasePicker work with Form
+     */
+    const onValueChange = (inputValue: TPickerValue, index: number) => {
+        if (inputID) {
+            onInputChange(inputValue);
+            return;
+        }
+
+        onInputChange(inputValue, index);
+    };
+
+    const enableHighlight = () => {
+        setIsHighlighted(true);
+    };
+
+    const disableHighlight = () => {
+        setIsHighlighted(false);
+    };
+
+    const iconToRender = useMemo((): IconToRender => {
+        if (icon) {
+            return () => icon(size);
+        }
+
+        // eslint-disable-next-line react/display-name
+        return () => (
+            <Icon
+                src={Expensicons.DownArrow}
+                // eslint-disable-next-line react/jsx-props-no-spreading
+                {...(size === 'small' ? {width: styles.pickerSmall().icon.width, height: styles.pickerSmall().icon.height} : {})}
+            />
+        );
+    }, [icon, size, styles]);
+
+    useImperativeHandle(ref, () => ({
+        /**
+         * Focuses the picker (if configured to do so)
+         *
+         * This method is used by Form
+         */
+        focus() {
+            if (!shouldFocusPicker) {
+                return;
+            }
+
+            // Defer the focusing to work around a bug on Mobile Safari, where focusing the `select` element in the
+            // same task when we scrolled to it left that element in a glitched state, where the dropdown list can't
+            // be opened until the element gets re-focused
+            lodashDefer(() => {
+                picker.current?.focus();
+            });
+        },
+
+        /**
+         * Like measure(), but measures the view relative to an ancestor
+         *
+         * This method is used by Form when scrolling to the input
+         *
+         * @param relativeToNativeComponentRef - reference to an ancestor
+         * @param onSuccess - callback called on success
+         * @param onFail - callback called on failure
+         */
+        measureLayout(relativeToNativeComponentRef, onSuccess, onFail) {
+            if (!root.current) {
+                return;
+            }
+
+            root.current.measureLayout(relativeToNativeComponentRef, onSuccess, onFail);
+        },
+    }));
+
+    const hasError = !!errorText;
+
+    if (isDisabled) {
+        return (
+            <View>
+                {!!label && (
+                    <Text
+                        style={[styles.textLabelSupporting, styles.mb1]}
+                        numberOfLines={1}
+                    >
+                        {label}
+                    </Text>
+                )}
+                <Text numberOfLines={1}>{value as ReactNode}</Text>
+                {!!hintText && <Text style={[styles.textLabel, styles.colorMuted, styles.mt2]}>{hintText}</Text>}
+            </View>
+        );
+    }
+
+    return (
+        <>
+            <View
+                ref={root}
+                style={[styles.pickerContainer, isDisabled && styles.inputDisabled, containerStyles, isHighlighted && styles.borderColorFocus, hasError && styles.borderColorDanger]}
+            >
+                {label && <Text style={[styles.pickerLabel, styles.textLabelSupporting, styles.pointerEventsNone]}>{label}</Text>}
+                <RNPickerSelect
+                    onValueChange={onValueChange}
+                    // We add a text color to prevent white text on white background dropdown items on Windows
+                    items={items.map((item) => ({...item, color: theme.pickerOptionsTextColor}))}
+                    style={size === 'normal' ? styles.picker(isDisabled, backgroundColor) : styles.pickerSmall(backgroundColor)}
+                    useNativeAndroidPickerStyle={false}
+                    placeholder={pickerPlaceholder}
+                    value={value}
+                    Icon={iconToRender}
+                    disabled={isDisabled}
+                    fixAndroidTouchableBug
+                    onOpen={enableHighlight}
+                    onClose={disableHighlight}
+                    textInputProps={{
+                        allowFontScaling: false,
+                    }}
+                    pickerProps={{
+                        ref: picker,
+                        tabIndex: -1,
+                        onFocus: enableHighlight,
+                        onBlur: () => {
+                            disableHighlight();
+                            onBlur();
+                        },
+                        ...additionalPickerEvents(enableHighlight, (inputValue, index) => {
+                            onValueChange(inputValue, index);
+                            disableHighlight();
+                        }),
+                    }}
+                    scrollViewRef={context?.scrollViewRef as RefObject<ScrollView>}
+                    scrollViewContentOffsetY={context?.contentOffsetY}
+                />
+            </View>
+            <FormHelpMessage message={errorText} />
+            {!!hintText && <Text style={[styles.textLabel, styles.colorMuted, styles.mt2]}>{hintText}</Text>}
+        </>
+    );
+}
+
+BasePicker.displayName = 'BasePicker';
+
+export default forwardRef(BasePicker);
diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js
deleted file mode 100644
index 8e49a42e8932..000000000000
--- a/src/components/Picker/index.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, {forwardRef} from 'react';
-import BasePicker from './BasePicker';
-
-const additionalPickerEvents = (onMouseDown, onChange) => ({
-    onMouseDown,
-    onChange: (e) => {
-        if (e.target.selectedIndex === undefined) {
-            return;
-        }
-        const index = e.target.selectedIndex;
-        const value = e.target.options[index].value;
-        onChange(value, index);
-    },
-});
-
-const BasePickerWithRef = forwardRef((props, ref) => (
-    <BasePicker
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        // Forward the ref to Picker, as we implement imperative methods there
-        ref={ref}
-        // On the Web, focusing the inner picker improves the accessibility,
-        // but doesn't open the picker (which we don't want), like it does on
-        // Native.
-        shouldFocusPicker
-        additionalPickerEvents={additionalPickerEvents}
-    />
-));
-
-BasePickerWithRef.displayName = 'BasePickerWithRef';
-
-export default BasePickerWithRef;
diff --git a/src/components/Picker/index.native.js b/src/components/Picker/index.native.js
deleted file mode 100644
index f441609fd4d0..000000000000
--- a/src/components/Picker/index.native.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React, {forwardRef} from 'react';
-import BasePicker from './BasePicker';
-
-const BasePickerWithRef = forwardRef((props, ref) => (
-    <BasePicker
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        ref={ref}
-    />
-));
-
-BasePickerWithRef.displayName = 'BasePickerWithRef';
-
-export default BasePickerWithRef;
diff --git a/src/components/Picker/index.native.tsx b/src/components/Picker/index.native.tsx
new file mode 100644
index 000000000000..7373f5a6f280
--- /dev/null
+++ b/src/components/Picker/index.native.tsx
@@ -0,0 +1,16 @@
+import React, {ForwardedRef, forwardRef} from 'react';
+import BasePicker from './BasePicker';
+import {BasePickerHandle, BasePickerProps} from './types';
+
+function Picker<TPickerValue>(props: BasePickerProps<TPickerValue>, ref: ForwardedRef<BasePickerHandle>) {
+    return (
+        <BasePicker<TPickerValue>
+            // eslint-disable-next-line react/jsx-props-no-spreading
+            {...props}
+            key={props.inputID}
+            ref={ref}
+        />
+    );
+}
+
+export default forwardRef(Picker);
diff --git a/src/components/Picker/index.tsx b/src/components/Picker/index.tsx
new file mode 100644
index 000000000000..18184b130bba
--- /dev/null
+++ b/src/components/Picker/index.tsx
@@ -0,0 +1,34 @@
+import React, {ForwardedRef, forwardRef} from 'react';
+import BasePicker from './BasePicker';
+import type {AdditionalPickerEvents, BasePickerHandle, BasePickerProps, OnChange, OnMouseDown} from './types';
+
+function Picker<TPickerValue>(props: BasePickerProps<TPickerValue>, ref: ForwardedRef<BasePickerHandle>) {
+    const additionalPickerEvents = (onMouseDown: OnMouseDown, onChange: OnChange<TPickerValue>): AdditionalPickerEvents => ({
+        onMouseDown,
+        onChange: (e) => {
+            if (e.target.selectedIndex === undefined) {
+                return;
+            }
+            const index = e.target.selectedIndex;
+            const value = e.target.options[index].value;
+            onChange(value as TPickerValue, index);
+        },
+    });
+
+    return (
+        <BasePicker<TPickerValue>
+            // eslint-disable-next-line react/jsx-props-no-spreading
+            {...props}
+            // Forward the ref to Picker, as we implement imperative methods there
+            ref={ref}
+            // On the Web, focusing the inner picker improves the accessibility,
+            // but doesn't open the picker (which we don't want), like it does on
+            // Native.
+            shouldFocusPicker
+            key={props.inputID}
+            additionalPickerEvents={additionalPickerEvents}
+        />
+    );
+}
+
+export default forwardRef(Picker);
diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts
new file mode 100644
index 000000000000..58eed0371893
--- /dev/null
+++ b/src/components/Picker/types.ts
@@ -0,0 +1,97 @@
+import {ChangeEvent, Component, ReactElement} from 'react';
+import {MeasureLayoutOnSuccessCallback, NativeMethods, StyleProp, ViewStyle} from 'react-native';
+
+type MeasureLayoutOnFailCallback = () => void;
+
+type RelativeToNativeComponentRef = (Component<unknown> & Readonly<NativeMethods>) | number;
+
+type BasePickerHandle = {
+    focus: () => void;
+    measureLayout: (relativeToNativeComponentRef: RelativeToNativeComponentRef, onSuccess: MeasureLayoutOnSuccessCallback, onFail: MeasureLayoutOnFailCallback) => void;
+};
+
+type OnMouseDown = () => void;
+
+type OnChange<TPickerValue> = (value: TPickerValue, index: number) => void;
+
+type AdditionalPickerEvents = {
+    onMouseDown?: OnMouseDown;
+    onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;
+};
+
+type AdditionalPickerEventsCallback<TPickerValue> = (onMouseDown: OnMouseDown, onChange: OnChange<TPickerValue>) => AdditionalPickerEvents;
+
+type DefaultPickerEventsCallback = () => void;
+
+type PickerSize = 'normal' | 'small';
+
+type PickerItem<TPickerValue> = {
+    /** The value of the item that is being selected */
+    value: TPickerValue;
+
+    /** The text to display for the item */
+    label: string;
+};
+
+type PickerPlaceholder = {
+    /** The value of the placeholder item, usually an empty string */
+    value?: string;
+
+    /** The text to be displayed as the placeholder */
+    label?: string;
+};
+
+type BasePickerProps<TPickerValue> = {
+    /** BasePicker label */
+    label?: string | null;
+
+    /** Should the picker appear disabled? */
+    isDisabled?: boolean;
+
+    /** Input value */
+    value?: TPickerValue | null;
+
+    /** The items to display in the list of selections */
+    items: Array<PickerItem<TPickerValue>>;
+
+    /** Something to show as the placeholder before something is selected */
+    placeholder?: PickerPlaceholder;
+
+    /** Error text to display */
+    errorText?: string;
+
+    /** Customize the BasePicker container */
+    containerStyles?: StyleProp<ViewStyle>;
+
+    /** Customize the BasePicker background color */
+    backgroundColor?: string;
+
+    /** The ID used to uniquely identify the input in a Form */
+    inputID?: string;
+
+    /** Saves a draft of the input value when used in a form */
+    shouldSaveDraft?: boolean;
+
+    /** A callback method that is called when the value changes and it receives the selected value as an argument */
+    onInputChange: (value: TPickerValue, index?: number) => void;
+
+    /** Size of a picker component */
+    size?: PickerSize;
+
+    /** An icon to display with the picker */
+    icon?: (size: PickerSize) => ReactElement;
+
+    /** Whether we should forward the focus/blur calls to the inner picker * */
+    shouldFocusPicker?: boolean;
+
+    /** Callback called when click or tap out of BasePicker */
+    onBlur?: () => void;
+
+    /** Additional events passed to the core BasePicker for specific platforms such as web */
+    additionalPickerEvents?: AdditionalPickerEventsCallback<TPickerValue> | DefaultPickerEventsCallback;
+
+    /** Hint text that appears below the picker */
+    hintText?: string;
+};
+
+export type {BasePickerHandle, BasePickerProps, AdditionalPickerEventsCallback, PickerSize, AdditionalPickerEvents, OnMouseDown, OnChange};
diff --git a/src/components/ScrollViewWithContext.tsx b/src/components/ScrollViewWithContext.tsx
index 6122d5508a38..7c75ae2f71b2 100644
--- a/src/components/ScrollViewWithContext.tsx
+++ b/src/components/ScrollViewWithContext.tsx
@@ -63,3 +63,4 @@ ScrollViewWithContextWithRef.displayName = 'ScrollViewWithContextWithRef';
 
 export default React.forwardRef(ScrollViewWithContextWithRef);
 export {ScrollContext};
+export type {ScrollContextValue};
diff --git a/src/components/Text.tsx b/src/components/Text.tsx
index 58a5cf300699..96a6f535877a 100644
--- a/src/components/Text.tsx
+++ b/src/components/Text.tsx
@@ -12,8 +12,10 @@ type TextProps = RNTextProps & {
 
     /** The size of the text */
     fontSize?: number;
+
     /** The alignment of the text */
     textAlign?: 'left' | 'right' | 'auto' | 'center' | 'justify';
+
     /** Any children to display */
     children: React.ReactNode;
 
diff --git a/src/hooks/useScrollContext.ts b/src/hooks/useScrollContext.ts
new file mode 100644
index 000000000000..711c8326bdff
--- /dev/null
+++ b/src/hooks/useScrollContext.ts
@@ -0,0 +1,6 @@
+import {useContext} from 'react';
+import {ScrollContext, ScrollContextValue} from '@components/ScrollViewWithContext';
+
+export default function useScrollContext(): ScrollContextValue {
+    return useContext(ScrollContext);
+}
diff --git a/src/types/onyx/Locale.ts b/src/types/onyx/Locale.ts
new file mode 100644
index 000000000000..1a5124684995
--- /dev/null
+++ b/src/types/onyx/Locale.ts
@@ -0,0 +1,6 @@
+import {ValueOf} from 'type-fest';
+import CONST from '@src/CONST';
+
+type Locale = ValueOf<typeof CONST.LOCALES>;
+
+export default Locale;
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index 4d4f45442d55..8329b56dc4b8 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -12,6 +12,7 @@ import Form, {AddDebitCardForm, DateOfBirthForm} from './Form';
 import FrequentlyUsedEmoji from './FrequentlyUsedEmoji';
 import Fund from './Fund';
 import IOU from './IOU';
+import Locale from './Locale';
 import Login from './Login';
 import MapboxAccessToken from './MapboxAccessToken';
 import Modal from './Modal';
@@ -70,6 +71,7 @@ export type {
     FrequentlyUsedEmoji,
     Fund,
     IOU,
+    Locale,
     Login,
     MapboxAccessToken,
     Modal,
