diff --git a/docs/pages/experiments/joy/switch.tsx b/docs/pages/experiments/joy/switch.tsx
new file mode 100644
index 00000000000000..49e7a349fb18e2
--- /dev/null
+++ b/docs/pages/experiments/joy/switch.tsx
@@ -0,0 +1,138 @@
+import * as React from 'react';
+// @ts-ignore
+import { jsx as _jsx } from 'react/jsx-runtime';
+import Box from '@mui/joy/Box';
+import Button from '@mui/joy/Button';
+import Switch from '@mui/joy/Switch';
+import {
+ CssVarsProvider,
+ styled,
+ useColorScheme,
+ ColorPaletteProp,
+ TypographySystem,
+ FontSize,
+} from '@mui/joy/styles';
+
+export const SvgIcon = styled('svg', {
+ shouldForwardProp: (prop) => prop !== 'fontSize' && prop !== 'sx',
+})<{
+ fontSize: keyof FontSize | 'inherit';
+}>(({ theme, fontSize }) => ({
+ userSelect: 'none',
+ width: '1em',
+ height: '1em',
+ display: 'inline-block',
+ fill: 'currentColor',
+ flexShrink: 0,
+ ...(fontSize && {
+ fontSize: fontSize === 'inherit' ? 'inherit' : theme.vars.fontSize[fontSize],
+ }),
+}));
+
+function createSvgIcon(path: any, displayName: any, initialProps?: any) {
+ const Component = (props: any, ref: any) =>
+ (
+
+ {path}
+
+ ) as unknown as typeof SvgIcon;
+
+ // @ts-ignore
+ return React.memo(React.forwardRef(Component));
+}
+
+const Typography = styled('p', {
+ shouldForwardProp: (prop) => prop !== 'color' && prop !== 'level' && prop !== 'sx',
+})<{ color?: ColorPaletteProp; level?: keyof TypographySystem }>(
+ ({ theme, level = 'body1', color }) => [
+ { margin: 0 },
+ theme.typography[level],
+ color && { color: `var(--joy-palette-${color}-textColor)` },
+ ],
+);
+
+export const Moon = createSvgIcon(
+ _jsx('path', {
+ d: 'M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z',
+ }),
+ 'DarkMode',
+);
+
+export const Sun = createSvgIcon(
+ _jsx('path', {
+ d: 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z',
+ }),
+ 'LightMode',
+);
+
+const ColorSchemePicker = () => {
+ const { mode, setMode } = useColorScheme();
+ const [mounted, setMounted] = React.useState(false);
+ React.useEffect(() => {
+ setMounted(true);
+ }, []);
+ if (!mounted) {
+ return null;
+ }
+
+ return (
+
+ );
+};
+
+const props = {
+ size: ['sm', 'md', 'lg'],
+ color: ['primary', 'danger', 'info', 'success', 'warning'],
+} as const;
+
+export default function JoySwitch() {
+ return (
+
+
+
+
+
+
+ {Object.entries(props).map(([propName, propValue]) => (
+
+ {propName}
+ {propValue.map((value) => (
+
+
+ {value && (
+
+ {value}
+
+ )}
+
+ ))}
+
+ ))}
+
+
+
+ );
+}
diff --git a/packages/mui-joy/src/Switch/Switch.spec.tsx b/packages/mui-joy/src/Switch/Switch.spec.tsx
new file mode 100644
index 00000000000000..131bd1329907a3
--- /dev/null
+++ b/packages/mui-joy/src/Switch/Switch.spec.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+import Switch from '@mui/joy/Switch';
+
+;
+
+;
+
+;
+
+// common HTML attributes
+ {}} />;
+
+;
+
+;
+
+ {
+ const checked = event.target.checked;
+ }}
+/>;
+
+;
+;
+;
+;
+;
+// @ts-expect-error there is no neutral switch
+;
+
+;
+;
+;
+
+;
diff --git a/packages/mui-joy/src/Switch/Switch.test.js b/packages/mui-joy/src/Switch/Switch.test.js
new file mode 100644
index 00000000000000..53478b34da225c
--- /dev/null
+++ b/packages/mui-joy/src/Switch/Switch.test.js
@@ -0,0 +1,98 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { describeConformance, act, createRenderer, fireEvent, screen } from 'test/utils';
+import Switch, { switchClasses as classes } from '@mui/joy/Switch';
+import { ThemeProvider } from '@mui/joy/styles';
+
+describe('', () => {
+ const { render } = createRenderer();
+
+ describeConformance(, () => ({
+ classes,
+ render,
+ ThemeProvider,
+ muiName: 'MuiSwitch',
+ testDeepOverrides: [
+ { slotName: 'track', slotClassName: classes.track },
+ { slotName: 'input', slotClassName: classes.input },
+ ],
+ refInstanceof: window.HTMLSpanElement,
+ skip: [
+ 'componentProp',
+ 'componentsProp',
+ 'classesRoot',
+ 'propsSpread',
+ 'themeDefaultProps',
+ 'themeVariants',
+ ],
+ }));
+
+ it('should pass componentProps down to slots', () => {
+ const {
+ container: { firstChild: root },
+ } = render(
+ ,
+ );
+
+ expect(screen.getByTestId('root-switch')).toBeVisible();
+ expect(root.childNodes[0]).to.have.class(/custom-(thumb|track|input)/);
+ expect(root.childNodes[1]).to.have.class(/custom-(thumb|track|input)/);
+ expect(root.childNodes[2]).to.have.class(/custom-(thumb|track|input)/);
+ });
+
+ it('should have the classes required for Switch', () => {
+ expect(classes).to.include.all.keys(['root', 'checked', 'disabled']);
+ });
+
+ it('should render the track as the first child of the Switch', () => {
+ const {
+ container: { firstChild: root },
+ } = render();
+
+ expect(root.childNodes[0]).to.have.property('tagName', 'SPAN');
+ expect(root.childNodes[0]).to.have.class(classes.track);
+ });
+
+ it('renders a `role="checkbox"` with the Unchecked state by default', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('checkbox')).to.have.property('checked', false);
+ });
+
+ it('renders a checkbox with the Checked state when checked', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('checkbox')).to.have.property('checked', true);
+ });
+
+ it('the switch can be disabled', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('checkbox')).to.have.property('disabled', true);
+ });
+
+ it('the switch can be readonly', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('checkbox')).to.have.property('readOnly', true);
+ });
+
+ it('the Checked state changes after change events', () => {
+ const { getByRole } = render();
+
+ // how a user would trigger it
+ act(() => {
+ getByRole('checkbox').click();
+ fireEvent.change(getByRole('checkbox'), { target: { checked: '' } });
+ });
+
+ expect(getByRole('checkbox')).to.have.property('checked', false);
+ });
+});
diff --git a/packages/mui-joy/src/Switch/Switch.tsx b/packages/mui-joy/src/Switch/Switch.tsx
new file mode 100644
index 00000000000000..a0b0b09756d1f5
--- /dev/null
+++ b/packages/mui-joy/src/Switch/Switch.tsx
@@ -0,0 +1,280 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { unstable_composeClasses as composeClasses } from '@mui/base';
+import { useSwitch } from '@mui/base/SwitchUnstyled';
+import { styled } from '../styles';
+import switchClasses, { getSwitchUtilityClass } from './switchClasses';
+import { SwitchProps } from './SwitchProps';
+
+const useUtilityClasses = (ownerState: SwitchProps & { focusVisible: boolean }) => {
+ const { classes, checked, disabled, focusVisible, readOnly } = ownerState;
+
+ const slots = {
+ root: [
+ 'root',
+ checked && 'checked',
+ disabled && 'disabled',
+ focusVisible && 'focusVisible',
+ readOnly && 'readOnly',
+ ],
+ thumb: ['thumb', checked && 'checked'],
+ track: ['track', checked && 'checked'],
+ input: ['input'],
+ };
+
+ return composeClasses(slots, getSwitchUtilityClass, classes);
+};
+
+const SwitchRoot = styled('span', {
+ name: 'MuiSwitch',
+ slot: 'Root',
+ overridesResolver: (props, styles) => styles.root,
+})<{ ownerState: SwitchProps }>(({ theme, ownerState }) => {
+ return {
+ '--Switch-track-radius': theme.vars.radius.lg,
+ '--Switch-track-width': '48px',
+ '--Switch-track-height': '24px',
+ '--Switch-thumb-size': '16px',
+ ...(ownerState.size === 'sm' && {
+ '--Switch-track-width': '40px',
+ '--Switch-track-height': '20px',
+ '--Switch-thumb-size': '12px',
+ }),
+ ...(ownerState.size === 'lg' && {
+ '--Switch-track-width': '64px',
+ '--Switch-track-height': '32px',
+ '--Switch-thumb-size': '24px',
+ }),
+ '--Switch-thumb-radius': 'calc(var(--Switch-track-radius) - 2px)',
+ '--Switch-thumb-width': 'var(--Switch-thumb-size)',
+ '--Switch-thumb-offset':
+ 'max((var(--Switch-track-height) - var(--Switch-thumb-size)) / 2, 0px)',
+ display: 'inline-block',
+ width: 'var(--Switch-track-width)', // should have the same width as track because flex parent can stretch SwitchRoot.
+ borderRadius: 'var(--Switch-track-radius)',
+ position: 'relative',
+ padding:
+ 'calc((var(--Switch-thumb-size) / 2) - (var(--Switch-track-height) / 2)) calc(-1 * var(--Switch-thumb-offset))',
+ color: theme.vars.palette.neutral.containedBg,
+ '&:hover': {
+ color: theme.vars.palette.neutral.containedBg,
+ },
+ [`&.${switchClasses.checked}`]: {
+ color: theme.vars.palette[ownerState.color!].containedBg,
+ '&:hover': {
+ color: theme.vars.palette[ownerState.color!].containedHoverBg,
+ },
+ },
+ [`&.${switchClasses.disabled}`]: {
+ pointerEvents: 'none',
+ cursor: 'default',
+ opacity: 0.6,
+ },
+ [`&.${switchClasses.focusVisible}`]: theme.focus.default,
+ };
+});
+
+const SwitchInput = styled('input', {
+ name: 'MuiSwitch',
+ slot: 'Input',
+ overridesResolver: (props, styles) => styles.input,
+})<{ ownerState: SwitchProps }>(() => ({
+ margin: 0,
+ height: '100%',
+ width: '100%',
+ opacity: 0,
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ cursor: 'pointer',
+}));
+
+const SwitchTrack = styled('span', {
+ name: 'MuiSwitch',
+ slot: 'Track',
+ overridesResolver: (props, styles) => styles.track,
+})<{ ownerState: SwitchProps & { focusVisible: boolean } }>(() => ({
+ position: 'relative',
+ color: 'inherit',
+ height: 'var(--Switch-track-height)',
+ width: 'var(--Switch-track-width)',
+ display: 'block',
+ backgroundColor: 'currentColor',
+ borderRadius: 'var(--Switch-track-radius)',
+}));
+
+const SwitchThumb = styled('span', {
+ name: 'MuiSwitch',
+ slot: 'Thumb',
+ overridesResolver: (props, styles) => styles.thumb,
+})<{ ownerState: SwitchProps }>(() => ({
+ transition: 'left 0.2s',
+ position: 'absolute',
+ top: '50%',
+ left: 'calc(50% - var(--Switch-track-width) / 2 + var(--Switch-thumb-width) / 2 + var(--Switch-thumb-offset))',
+ transform: 'translate(-50%, -50%)',
+ width: 'var(--Switch-thumb-width)',
+ height: 'var(--Switch-thumb-size)',
+ borderRadius: 'var(--Switch-thumb-radius)',
+ backgroundColor: '#fff',
+ [`&.${switchClasses.checked}`]: {
+ left: 'calc(50% + var(--Switch-track-width) / 2 - var(--Switch-thumb-width) / 2 - var(--Switch-thumb-offset))',
+ },
+}));
+
+const Switch = React.forwardRef(function Switch(inProps, ref) {
+ const props = inProps;
+ const {
+ checked: checkedProp,
+ className,
+ component,
+ componentsProps = {},
+ defaultChecked,
+ disabled: disabledProp,
+ onBlur,
+ onChange,
+ onFocus,
+ onFocusVisible,
+ readOnly: readOnlyProp,
+ required,
+ color = 'primary',
+ size,
+ ...otherProps
+ } = props;
+
+ const useSwitchProps = {
+ checked: checkedProp,
+ defaultChecked,
+ disabled: disabledProp,
+ onBlur,
+ onChange,
+ onFocus,
+ onFocusVisible,
+ readOnly: readOnlyProp,
+ };
+
+ const { getInputProps, checked, disabled, focusVisible, readOnly } = useSwitch(useSwitchProps);
+
+ const ownerState = {
+ ...props,
+ checked,
+ disabled,
+ focusVisible,
+ readOnly,
+ color,
+ size,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+
+
+
+
+ );
+});
+
+Switch.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * If `true`, the component is checked.
+ */
+ checked: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Class name applied to the root element.
+ */
+ className: PropTypes.string,
+ /**
+ * The color of the component. It supports those theme colors that make sense for this component.
+ * @default 'primary'
+ */
+ color: PropTypes.oneOf(['danger', 'info', 'primary', 'success', 'warning']),
+ /**
+ * The component used for the Root slot.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * The props used for each slot inside the Switch.
+ * @default {}
+ */
+ componentsProps: PropTypes.object,
+ /**
+ * The default checked state. Use when the component is not controlled.
+ */
+ defaultChecked: PropTypes.bool,
+ /**
+ * If `true`, the component is disabled.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ id: PropTypes.string,
+ /**
+ * @ignore
+ */
+ onBlur: PropTypes.func,
+ /**
+ * Callback fired when the state is changed.
+ *
+ * @param {React.ChangeEvent} event The event source of the callback.
+ * You can pull out the new value by accessing `event.target.value` (string).
+ * You can pull out the new checked state by accessing `event.target.checked` (boolean).
+ */
+ onChange: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onFocus: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onFocusVisible: PropTypes.func,
+ /**
+ * If `true`, the component is read only.
+ */
+ readOnly: PropTypes.bool,
+ /**
+ * If `true`, the `input` element is required.
+ */
+ required: PropTypes.bool,
+ /**
+ * The size of the component.
+ * @default 'md'
+ */
+ size: PropTypes.oneOf(['sm', 'md', 'lg']),
+} as any;
+
+export default Switch;
diff --git a/packages/mui-joy/src/Switch/SwitchProps.ts b/packages/mui-joy/src/Switch/SwitchProps.ts
new file mode 100644
index 00000000000000..e4298111914736
--- /dev/null
+++ b/packages/mui-joy/src/Switch/SwitchProps.ts
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import { OverridableStringUnion } from '@mui/types';
+import { UseSwitchProps } from '@mui/base/SwitchUnstyled';
+import { SwitchClasses } from './switchClasses';
+import { SxProps } from '../styles/defaultTheme';
+import { ColorPaletteProp } from '../styles/types';
+
+export interface SwitchPropsColorOverrides {}
+
+export interface SwitchPropsSizeOverrides {}
+
+export interface SwitchProps
+ extends UseSwitchProps,
+ Omit, keyof UseSwitchProps> {
+ /**
+ * Class name applied to the root element.
+ */
+ className?: string;
+ /**
+ * The component used for the Root slot.
+ * Either a string to use a HTML element or a component.
+ */
+ component?: React.ElementType;
+ /**
+ * The props used for each slot inside the Switch.
+ * @default {}
+ */
+ componentsProps?: {
+ thumb?: React.HTMLAttributes;
+ input?: React.InputHTMLAttributes;
+ track?: React.HTMLAttributes;
+ };
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+ /**
+ * The color of the component. It supports those theme colors that make sense for this component.
+ * @default 'primary'
+ */
+ color?: OverridableStringUnion<
+ Exclude,
+ SwitchPropsColorOverrides
+ >;
+ /**
+ * The size of the component.
+ * @default 'md'
+ */
+ size?: OverridableStringUnion<'sm' | 'md' | 'lg', SwitchPropsSizeOverrides>;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+}
diff --git a/packages/mui-joy/src/Switch/index.ts b/packages/mui-joy/src/Switch/index.ts
new file mode 100644
index 00000000000000..2c190ccd5f69b4
--- /dev/null
+++ b/packages/mui-joy/src/Switch/index.ts
@@ -0,0 +1,4 @@
+export { default } from './Switch';
+export { default as switchClasses } from './switchClasses';
+export * from './switchClasses';
+export * from './SwitchProps';
diff --git a/packages/mui-joy/src/Switch/switchClasses.ts b/packages/mui-joy/src/Switch/switchClasses.ts
new file mode 100644
index 00000000000000..cceac6965d6213
--- /dev/null
+++ b/packages/mui-joy/src/Switch/switchClasses.ts
@@ -0,0 +1,63 @@
+import { generateUtilityClass, generateUtilityClasses } from '@mui/base';
+
+export interface SwitchClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** State class applied to the internal `SwitchBase` component's `checked` class. */
+ checked: string;
+ /** State class applied to the internal SwitchBase component's disabled class. */
+ disabled: string;
+ /** Styles applied to the internal SwitchBase component's input element. */
+ input: string;
+ /** Styles used to create the thumb passed to the internal `SwitchBase` component `icon` prop. */
+ thumb: string;
+ /** Styles applied to the track element. */
+ track: string;
+ /** Class applied to the root element if the switch has visible focus */
+ focusVisible: string;
+ /** Class applied to the root element if the switch is read-only */
+ readOnly: string;
+ /** Styles applied to the root element if `color="primary"`. */
+ colorPrimary: string;
+ /** Styles applied to the root element if `color="danger"`. */
+ colorDanger: string;
+ /** Styles applied to the root element if `color="info"`. */
+ colorInfo: string;
+ /** Styles applied to the root element if `color="success"`. */
+ colorSuccess: string;
+ /** Styles applied to the root element if `color="warning"`. */
+ colorWarning: string;
+ /** Styles applied to the root element if `size="sm"`. */
+ sizeSm: string;
+ /** Styles applied to the root element if `size="md"`. */
+ sizeMd: string;
+ /** Styles applied to the root element if `size="lg"`. */
+ sizeLg: string;
+}
+
+export type SwitchClassKey = keyof SwitchClasses;
+
+export function getSwitchUtilityClass(slot: string): string {
+ return generateUtilityClass('MuiSwitch', slot);
+}
+
+const switchClasses: SwitchClasses = generateUtilityClasses('MuiSwitch', [
+ 'root',
+ 'checked',
+ 'disabled',
+ 'input',
+ 'thumb',
+ 'track',
+ 'focusVisible',
+ 'readOnly',
+ 'colorPrimary',
+ 'colorDanger',
+ 'colorInfo',
+ 'colorSuccess',
+ 'colorWarning',
+ 'sizeSm',
+ 'sizeMd',
+ 'sizeLg',
+]);
+
+export default switchClasses;
diff --git a/packages/mui-joy/src/index.ts b/packages/mui-joy/src/index.ts
index 3a8ed6e62c440f..7072cb249343de 100644
--- a/packages/mui-joy/src/index.ts
+++ b/packages/mui-joy/src/index.ts
@@ -3,3 +3,6 @@ export * from './styles';
export { default as Button } from './Button';
export * from './Button';
+
+export { default as Switch } from './Switch';
+export * from './Switch';
diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts
index ea60820f1ff1c6..b0d2231051b87a 100644
--- a/packages/mui-joy/src/styles/components.d.ts
+++ b/packages/mui-joy/src/styles/components.d.ts
@@ -2,6 +2,8 @@ import { CSSInterpolation } from '@mui/system';
import { GlobalStateSlot } from '@mui/base';
import { ButtonProps } from '../Button/ButtonProps';
import { ButtonClassKey } from '../Button/buttonClasses';
+import { SwitchProps } from '../Switch/SwitchProps';
+import { SwitchClassKey } from '../Switch/switchClasses';
export type OverridesStyleRules = Record<
ClassKey,
@@ -12,9 +14,9 @@ export interface Components {
MuiButton?: {
defaultProps?: Partial;
styleOverrides?: Partial>>;
- variants?: Array<{
- props: Partial;
- style: CSSInterpolation;
- }>;
+ };
+ MuiSwitch?: {
+ defaultProps?: Partial;
+ styleOverrides?: Partial>>;
};
}
diff --git a/packages/mui-system/src/cssVars/createGetThemeVar.ts b/packages/mui-system/src/cssVars/createGetThemeVar.ts
index 419fc2cffe93a3..564b4a326edefb 100644
--- a/packages/mui-system/src/cssVars/createGetThemeVar.ts
+++ b/packages/mui-system/src/cssVars/createGetThemeVar.ts
@@ -6,7 +6,10 @@ export default function createGetThemeVar(prefix: str
return `, var(--${prefix ? `${prefix}-` : ''}${vars[0]}${appendVar(...vars.slice(1))})`;
}
- const getThemeVar = (field: T, ...vars: T[]) => {
+ const getThemeVar = (
+ field: T | AdditionalVars,
+ ...vars: (T | AdditionalVars)[]
+ ) => {
return `var(--${prefix ? `${prefix}-` : ''}${field}${appendVar(...vars)})`;
};
return getThemeVar;
diff --git a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.d.ts b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.d.ts
index 084a4c47829ee7..7fd21123f81e9f 100644
--- a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.d.ts
+++ b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.d.ts
@@ -49,6 +49,7 @@ export type SystemStyleObject =
| SystemCssProperties
| CSSPseudoSelectorProps
| CSSSelectorObject
+ | { [cssVariable: string]: string | number }
| null;
/**
diff --git a/test/regressions/fixtures/SwitchJoy/SwitchJoy.js b/test/regressions/fixtures/SwitchJoy/SwitchJoy.js
new file mode 100644
index 00000000000000..1586c8dd08a47a
--- /dev/null
+++ b/test/regressions/fixtures/SwitchJoy/SwitchJoy.js
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import { CssVarsProvider } from '@mui/joy/styles';
+import Box from '@mui/joy/Box';
+import Switch from '@mui/joy/Switch';
+
+export default function SwitchJoy() {
+ return (
+
+
+ {['primary', 'danger', 'info', 'success', 'warning'].map((color) => (
+
+ ))}
+ {['sm', 'md', 'lg'].map((size) => (
+
+ ))}
+
+
+ );
+}