diff --git a/docs/pages/material-ui/api/autocomplete.json b/docs/pages/material-ui/api/autocomplete.json
index 5e12636af35030..d0922e78f8ff63 100644
--- a/docs/pages/material-ui/api/autocomplete.json
+++ b/docs/pages/material-ui/api/autocomplete.json
@@ -161,14 +161,14 @@
"slotProps": {
"type": {
"name": "shape",
- "description": "{ chip?: func
| object, clearIndicator?: func
| object, listbox?: func
| object, paper?: func
| object, popper?: func
| object, popupIndicator?: func
| object, root?: func
| object }"
+ "description": "{ chip?: func
| object, clearIndicator?: func
| object, listbox?: func
| object, noOptionsContainer?: func
| object, paper?: func
| object, popper?: func
| object, popupIndicator?: func
| object, root?: func
| object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
- "description": "{ clearIndicator?: elementType, listbox?: elementType, paper?: elementType, popper?: elementType, popupIndicator?: elementType, root?: elementType }"
+ "description": "{ clearIndicator?: elementType, listbox?: elementType, noOptionsContainer?: elementType, paper?: elementType, popper?: elementType, popupIndicator?: elementType, root?: elementType }"
},
"default": "{}"
},
@@ -211,6 +211,12 @@
"default": "'ul'",
"class": "MuiAutocomplete-listbox"
},
+ {
+ "name": "noOptionsContainer",
+ "description": "The component used to render the \"no options\" container.",
+ "default": "'div'",
+ "class": "MuiAutocomplete-noOptionsContainer"
+ },
{
"name": "paper",
"description": "The component used to render the body of the popup.",
diff --git a/docs/translations/api-docs/autocomplete/autocomplete.json b/docs/translations/api-docs/autocomplete/autocomplete.json
index 7eb570fb34d844..46016b6f5ac057 100644
--- a/docs/translations/api-docs/autocomplete/autocomplete.json
+++ b/docs/translations/api-docs/autocomplete/autocomplete.json
@@ -319,6 +319,7 @@
"slotDescriptions": {
"clearIndicator": "The component used to render the clear indicator element.",
"listbox": "The component used to render the listbox.",
+ "noOptionsContainer": "The component used to render the "no options" container.",
"paper": "The component used to render the body of the popup.",
"popper": "The component used to position the popup.",
"popupIndicator": "The component used to render the popup indicator element.",
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts
index bbd514a792a6d0..4bddc19567a1fe 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.d.ts
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.d.ts
@@ -23,6 +23,7 @@ import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
export interface AutocompletePaperSlotPropsOverrides {}
export interface AutocompletePopperSlotPropsOverrides {}
+export interface AutocompleteNoOptionsContainerSlotPropsOverrides {}
export {
AutocompleteChangeDetails,
@@ -136,6 +137,11 @@ export interface AutocompleteSlots {
* @default 'ul'
*/
listbox: React.JSXElementConstructor>;
+ /**
+ * The component used to render the "no options" container.
+ * @default 'div'
+ */
+ noOptionsContainer: React.ElementType;
/**
* The component used to render the body of the popup.
* @default Paper
@@ -185,6 +191,11 @@ export type AutocompleteSlotsAndSlotProps<
{},
AutocompleteOwnerState
>;
+ noOptionsContainer: SlotProps<
+ 'div',
+ AutocompleteNoOptionsContainerSlotPropsOverrides,
+ AutocompleteOwnerState
+ >;
paper: SlotProps<
React.ElementType>,
AutocompletePaperSlotPropsOverrides,
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.js b/packages/mui-material/src/Autocomplete/Autocomplete.js
index 8470ab477a273c..c3135e29314a98 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.js
@@ -59,6 +59,7 @@ const useUtilityClasses = (ownerState) => {
listbox: ['listbox'],
loading: ['loading'],
noOptions: ['noOptions'],
+ noOptionsContainer: ['noOptionsContainer'],
option: ['option'],
groupLabel: ['groupLabel'],
groupUl: ['groupUl'],
@@ -604,6 +605,18 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
className: classes.paper,
});
+ const [NoOptionsSlot, noOptionsProps] = useSlot('noOptionsContainer', {
+ elementType: 'div',
+ externalForwardedProps,
+ ownerState,
+ className: classes.noOptionsContainer,
+ additionalProps: {
+ role: 'status',
+ 'aria-live': 'polite',
+ 'aria-atomic': 'true',
+ },
+ });
+
const [PopperSlot, popperProps] = useSlot('popper', {
elementType: Popper,
externalForwardedProps,
@@ -796,19 +809,20 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
{loadingText}
) : null}
- {renderedOptions.length === 0 && !freeSolo && !loading ? (
- {
- // Prevent input blur when interacting with the "no options" content
- event.preventDefault();
- }}
- >
- {noOptionsText}
-
- ) : null}
+
+ {renderedOptions.length === 0 && !freeSolo && !loading ? (
+ {
+ // Prevent input blur when interacting with the "no options" content
+ event.preventDefault();
+ }}
+ >
+ {noOptionsText}
+
+ ) : null}
+
{renderedOptions.length > 0 ? (
{renderedOptions.map((option, index) => {
@@ -1232,6 +1246,7 @@ Autocomplete.propTypes /* remove-proptypes */ = {
chip: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
clearIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+ noOptionsContainer: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
popper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
popupIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
@@ -1244,6 +1259,7 @@ Autocomplete.propTypes /* remove-proptypes */ = {
slots: PropTypes.shape({
clearIndicator: PropTypes.elementType,
listbox: PropTypes.elementType,
+ noOptionsContainer: PropTypes.elementType,
paper: PropTypes.elementType,
popper: PropTypes.elementType,
popupIndicator: PropTypes.elementType,
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx
index cf9baa121a2e52..cd037cc40d891e 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx
@@ -151,6 +151,7 @@ function AutocompleteComponentsProps() {
renderInput={(params) => }
slotProps={{
clearIndicator: { size: 'large' },
+ noOptionsContainer: { 'aria-label': 'no results' },
paper: { elevation: 2 },
popper: { placement: 'bottom-end' },
popupIndicator: { size: 'large' },
@@ -170,6 +171,18 @@ function CustomListboxRef() {
);
}
+function CustomNoOptionsSlot() {
+ const ref = React.useRef(null);
+ return (
+ }
+ options={['one', 'two', 'three']}
+ slots={{ noOptionsContainer: 'div' }}
+ slotProps={{ noOptionsContainer: { ref } }}
+ />
+ );
+}
+
// Tests presence of defaultMuiPrevented in event
}
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
index c64005ad1c64da..79788d5feecfbb 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
@@ -93,6 +93,7 @@ describe('', () => {
slots: {
clearIndicator: { expectedClassName: classes.clearIndicator },
popupIndicator: { expectedClassName: classes.popupIndicator },
+ noOptionsContainer: { expectedClassName: classes.noOptionsContainer },
},
only: [
'slotsProp',
@@ -4970,6 +4971,78 @@ describe('', () => {
expect(screen.getByTestId('label')).to.have.attribute('data-shrink', 'false');
});
+ describe('prop: noOptionsText', () => {
+ it('should render the no options text when there are no options', () => {
+ render(
+ }
+ />,
+ );
+
+ expect(screen.getByText('No options')).not.to.equal(null);
+ });
+
+ it('should render the custom no options text when there are no options', () => {
+ render(
+ }
+ />,
+ );
+
+ expect(screen.getByText('No results')).not.to.equal(null);
+ });
+
+ it('should not render the no options text when loading and there are no options', () => {
+ render(
+ }
+ />,
+ );
+
+ expect(screen.queryByText('No options')).to.equal(null);
+ });
+
+ it('should not render the no options text when freeSolo is true and there are no options', () => {
+ render(
+ }
+ />,
+ );
+
+ expect(screen.queryByText('No options')).to.equal(null);
+ });
+
+ it('should always render a status message container for no options', async () => {
+ const { user } = render(
+ }
+ />,
+ );
+
+ const status = screen.getByRole('status');
+ expect(status).to.have.attribute('aria-live', 'polite');
+ expect(status).to.have.attribute('aria-atomic', 'true');
+ expect(status.children).to.have.length(0);
+
+ await user.type(screen.getByRole('combobox'), 'three');
+
+ expect(status.children).to.have.length(1);
+ });
+ });
+
// https://github.com/mui/material-ui/issues/47203
it.skipIf(isJsdom())(
'should not scroll the listbox to the top when listbox is scrolled down and one of the end option is clicked',
diff --git a/packages/mui-material/src/Autocomplete/autocompleteClasses.ts b/packages/mui-material/src/Autocomplete/autocompleteClasses.ts
index 863a1719e46490..2910da37a3164c 100644
--- a/packages/mui-material/src/Autocomplete/autocompleteClasses.ts
+++ b/packages/mui-material/src/Autocomplete/autocompleteClasses.ts
@@ -48,6 +48,8 @@ export interface AutocompleteClasses {
loading: string;
/** Styles applied to the no option wrapper. */
noOptions: string;
+ /** Styles applied to the no option container. */
+ noOptionsContainer: string;
/** Styles applied to the option elements. */
option: string;
/** Styles applied to the group's label elements. */
@@ -86,6 +88,7 @@ const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('MuiAuto
'listbox',
'loading',
'noOptions',
+ 'noOptionsContainer',
'option',
'groupLabel',
'groupUl',