diff --git a/docs/pages/material-ui/api/autocomplete.json b/docs/pages/material-ui/api/autocomplete.json
index 5e12636af35030..162c7c504cb332 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, noOptions?: 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, noOptions?: elementType, paper?: elementType, popper?: elementType, popupIndicator?: elementType, root?: elementType }"
},
"default": "{}"
},
@@ -211,6 +211,12 @@
"default": "'ul'",
"class": "MuiAutocomplete-listbox"
},
+ {
+ "name": "noOptions",
+ "description": "The component used to render the \"no options\" container.",
+ "default": "'div'",
+ "class": "MuiAutocomplete-noOptions"
+ },
{
"name": "paper",
"description": "The component used to render the body of the popup.",
@@ -303,12 +309,6 @@
"description": "Styles applied to the loading wrapper.",
"isGlobal": false
},
- {
- "key": "noOptions",
- "className": "MuiAutocomplete-noOptions",
- "description": "Styles applied to the no option wrapper.",
- "isGlobal": false
- },
{
"key": "option",
"className": "MuiAutocomplete-option",
diff --git a/docs/translations/api-docs/autocomplete/autocomplete.json b/docs/translations/api-docs/autocomplete/autocomplete.json
index 7eb570fb34d844..3eb8d8d3b5fd94 100644
--- a/docs/translations/api-docs/autocomplete/autocomplete.json
+++ b/docs/translations/api-docs/autocomplete/autocomplete.json
@@ -282,10 +282,6 @@
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the loading wrapper"
},
- "noOptions": {
- "description": "Styles applied to {{nodeName}}.",
- "nodeName": "the no option wrapper"
- },
"option": {
"description": "Styles applied to {{nodeName}}.",
"nodeName": "the option elements"
@@ -319,6 +315,7 @@
"slotDescriptions": {
"clearIndicator": "The component used to render the clear indicator element.",
"listbox": "The component used to render the listbox.",
+ "noOptions": "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..0e43d4c979807f 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 AutocompleteNoOptionsSlotPropsOverrides {}
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'
+ */
+ noOptions: React.ElementType;
/**
* The component used to render the body of the popup.
* @default Paper
@@ -185,6 +191,11 @@ export type AutocompleteSlotsAndSlotProps<
{},
AutocompleteOwnerState
>;
+ noOptions: SlotProps<
+ 'div',
+ AutocompleteNoOptionsSlotPropsOverrides,
+ 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..577433a2b46e13 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.js
@@ -604,6 +604,17 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
className: classes.paper,
});
+ const [NoOptionsSlot, noOptionsProps] = useSlot('noOptions', {
+ elementType: 'div',
+ externalForwardedProps,
+ ownerState,
+ additionalProps: {
+ role: 'status',
+ 'aria-live': 'polite',
+ 'aria-atomic': 'true',
+ },
+ });
+
const [PopperSlot, popperProps] = useSlot('popper', {
elementType: Popper,
externalForwardedProps,
@@ -796,19 +807,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 +1244,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]),
+ noOptions: 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 +1257,7 @@ Autocomplete.propTypes /* remove-proptypes */ = {
slots: PropTypes.shape({
clearIndicator: PropTypes.elementType,
listbox: PropTypes.elementType,
+ noOptions: 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..4ea9c518f58655 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' },
+ noOptions: { '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={{ noOptions: 'div' }}
+ slotProps={{ noOptions: { 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..709ffc05a4458f 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
@@ -4970,6 +4970,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',