diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md
index 589290a0e1ebb..0b92b9fd1ad46 100644
--- a/docs/pages/x/api/data-grid/grid-api.md
+++ b/docs/pages/x/api/data-grid/grid-api.md
@@ -113,9 +113,9 @@ import { GridApi } from '@mui/x-data-grid';
| setRowSelectionModel | (rowIds: GridRowId[]) => void | Updates the selected rows to be those passed to the `rowIds` argument.
Any row already selected will be unselected. |
| setSortModel | (model: GridSortModel) => void | Updates the sort model and triggers the sorting of rows. |
| showColumnMenu | (field: string) => void | Display the column menu under the `field` column. |
-| showFilterPanel | (targetColumnField?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. |
+| showFilterPanel | (targetColumnField?: string, panelId?: string, labelId?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. |
| showHeaderFilterMenu | (field: GridColDef['field']) => void | Opens the header filter menu for the given field. |
-| showPreferences | (newValue: GridPreferencePanelsValue) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. |
+| showPreferences | (newValue: GridPreferencePanelsValue, panelId?: string, labelId?: string) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. |
| sortColumn | (column: GridColDef, direction?: GridSortDirection, allowMultipleSorting?: boolean) => void | Sorts a column. |
| startCellEditMode | (params: GridStartCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into edit mode. |
| startHeaderFilterEditMode | (field: GridColDef['field']) => void | Puts the cell corresponding to the given row id and field into edit mode. |
diff --git a/docs/pages/x/api/data-grid/grid-filter-api.json b/docs/pages/x/api/data-grid/grid-filter-api.json
index b120ad763bf5b..08c8d1f9593d7 100644
--- a/docs/pages/x/api/data-grid/grid-filter-api.json
+++ b/docs/pages/x/api/data-grid/grid-filter-api.json
@@ -26,7 +26,7 @@
{
"name": "showFilterPanel",
"description": "Shows the filter panel. If targetColumnField
is given, a filter for this field is also added.",
- "type": "(targetColumnField?: string) => void"
+ "type": "(targetColumnField?: string, panelId?: string, labelId?: string) => void"
},
{
"name": "upsertFilterItem",
diff --git a/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
index b228fdfe3e723..bbf538399d30d 100644
--- a/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
@@ -71,6 +71,8 @@ const FULL_INITIAL_STATE: GridInitialState = {
preferencePanel: {
open: true,
openedPanelValue: GridPreferencePanelsValue.filters,
+ panelId: undefined,
+ labelId: undefined,
},
sorting: {
sortModel: [{ field: 'id', sort: 'desc' }],
diff --git a/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx
index 5eb94358b0d78..103f594dc95c1 100644
--- a/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx
+++ b/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx
@@ -199,9 +199,9 @@ function GridActionsCell(props: GridActionsCellProps) {
ref={buttonRef}
id={buttonId}
aria-label={apiRef.current.getLocaleText('actionsCellMore')}
- aria-controls={menuId}
- aria-expanded={open ? 'true' : undefined}
- aria-haspopup="true"
+ aria-haspopup="menu"
+ aria-expanded={open}
+ aria-controls={open ? menuId : undefined}
role="menuitem"
size="small"
onClick={showMenu}
diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx
index 1eb387b8e118b..16566f378d74e 100644
--- a/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx
+++ b/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx
@@ -59,9 +59,9 @@ export const ColumnHeaderMenuIcon = React.memo((props: ColumnHeaderMenuIconProps
aria-label={apiRef.current.getLocaleText('columnMenuLabel')}
size="small"
onClick={handleMenuIconClick}
- aria-expanded={open ? 'true' : undefined}
- aria-haspopup="true"
- aria-controls={columnMenuId}
+ aria-haspopup="menu"
+ aria-expanded={open}
+ aria-controls={open ? columnMenuId : undefined}
id={columnMenuButtonId}
{...rootProps.slotProps?.baseIconButton}
>
diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx
index e9c348f056725..d9e8e77041dbe 100644
--- a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx
+++ b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx
@@ -1,7 +1,8 @@
import * as React from 'react';
import PropTypes from 'prop-types';
-import { unstable_composeClasses as composeClasses } from '@mui/utils';
+import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils';
import Badge from '@mui/material/Badge';
+import { useGridSelector } from '../../hooks';
import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector';
import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue';
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
@@ -37,6 +38,9 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro
const rootProps = useGridRootProps();
const ownerState = { ...props, classes: rootProps.classes };
const classes = useUtilityClasses(ownerState);
+ const preferencePanel = useGridSelector(apiRef, gridPreferencePanelStateSelector);
+ const labelId = useId();
+ const panelId = useId();
const toggleFilter = React.useCallback(
(event: React.MouseEvent) => {
@@ -48,27 +52,33 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro
if (open && openedPanelValue === GridPreferencePanelsValue.filters) {
apiRef.current.hideFilterPanel();
} else {
- apiRef.current.showFilterPanel();
+ apiRef.current.showFilterPanel(undefined, panelId, labelId);
}
if (onClick) {
onClick(apiRef.current.getColumnHeaderParams(field), event);
}
},
- [apiRef, field, onClick],
+ [apiRef, field, onClick, panelId, labelId],
);
if (!counter) {
return null;
}
+ const open = preferencePanel.open && preferencePanel.labelId === labelId;
+
const iconButton = (
diff --git a/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx b/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx
index 90532465eb55e..fe3083aa87ac6 100644
--- a/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx
+++ b/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx
@@ -26,6 +26,8 @@ export const GridPreferencesPanel = React.forwardRef<
ref={ref}
as={rootProps.slots.basePopper}
open={columns.length > 0 && preferencePanelState.open}
+ id={preferencePanelState.panelId}
+ aria-labelledby={preferencePanelState.labelId}
{...rootProps.slotProps?.panel}
{...props}
{...rootProps.slotProps?.basePopper}
diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx
index b582717e904b3..e1fb78e7435c6 100644
--- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx
+++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import { ButtonProps } from '@mui/material/Button';
+import { unstable_useId as useId } from '@mui/material/utils';
import { useGridSelector } from '../../hooks/utils/useGridSelector';
import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector';
import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue';
@@ -9,15 +10,25 @@ import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
export const GridToolbarColumnsButton = React.forwardRef(
function GridToolbarColumnsButton(props, ref) {
const { onClick, ...other } = props;
+ const columnButtonId = useId();
+ const columnPanelId = useId();
+
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
- const { open, openedPanelValue } = useGridSelector(apiRef, gridPreferencePanelStateSelector);
+ const preferencePanel = useGridSelector(apiRef, gridPreferencePanelStateSelector);
const showColumns = (event: React.MouseEvent) => {
- if (open && openedPanelValue === GridPreferencePanelsValue.columns) {
+ if (
+ preferencePanel.open &&
+ preferencePanel.openedPanelValue === GridPreferencePanelsValue.columns
+ ) {
apiRef.current.hidePreferences();
} else {
- apiRef.current.showPreferences(GridPreferencePanelsValue.columns);
+ apiRef.current.showPreferences(
+ GridPreferencePanelsValue.columns,
+ columnPanelId,
+ columnButtonId,
+ );
}
onClick?.(event);
@@ -28,11 +39,17 @@ export const GridToolbarColumnsButton = React.forwardRef}
{...other}
onClick={showColumns}
diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx
index 2e54d7d626c10..61567ce612785 100644
--- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx
+++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx
@@ -107,9 +107,9 @@ export const GridToolbarDensitySelector = React.forwardRef(null);
@@ -58,11 +58,11 @@ export const GridToolbarExportContainer = React.forwardRef}
- aria-expanded={open ? 'true' : undefined}
+ aria-expanded={open}
aria-label={apiRef.current.getLocaleText('toolbarExportLabel')}
aria-haspopup="menu"
- aria-labelledby={menuId}
- id={buttonId}
+ aria-controls={open ? exportMenuId : undefined}
+ id={exportButtonId}
{...other}
onClick={handleMenuOpen}
{...rootProps.slotProps?.baseButton}
@@ -76,9 +76,9 @@ export const GridToolbarExportContainer = React.forwardRef