From bb06b670eee80e04b275d507990eb2eb6a8dedf9 Mon Sep 17 00:00:00 2001 From: mahsa Date: Fri, 14 Aug 2026 20:07:51 +0330 Subject: [PATCH] fix(a11y): add button semantics to clickable datagrid/datatable rows (#11108) --- .../src/list/datagrid/DatagridRow.tsx | 29 +++++++++++++++++-- .../src/list/datatable/DataTableRow.tsx | 17 +++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx index bc3e05f62d7..893c88504e4 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/DatagridRow.tsx @@ -165,6 +165,26 @@ const DatagridRow: React.ForwardRefExoticComponent< getPathForRecord, ] ); + const isRowClickable = Boolean(rowClick ?? hasDetailView); + + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleClick(event as any); + } + }, + [handleClick] + ); + + // Accessible name for screen readers: without this, the row is announced + // as a bare "button"/"link" with no indication of what it does. + const rowAccessibleLabel = isRowClickable + ? translate('ra.action.open', { + _: `Open ${resource} #${record.id}`, + recordRepresentation: `${resource} #${record.id}`, + }) + : undefined; return ( <> @@ -173,12 +193,15 @@ const DatagridRow: React.ForwardRefExoticComponent< className={clsx(className, { [DatagridClasses.expandable]: expandable, [DatagridClasses.selectable]: selectable, - [DatagridClasses.clickableRow]: rowClick ?? hasDetailView, + [DatagridClasses.clickableRow]: isRowClickable, })} - key={id} - style={style} + key={record.id} hover={hover} onClick={handleClick} + onKeyDown={isRowClickable ? handleKeyDown : undefined} + role={isRowClickable ? 'button' : undefined} + tabIndex={isRowClickable ? 0 : undefined} + aria-label={rowAccessibleLabel} {...rest} > {expand && ( diff --git a/packages/ra-ui-materialui/src/list/datatable/DataTableRow.tsx b/packages/ra-ui-materialui/src/list/datatable/DataTableRow.tsx index cf55b9bfc97..4545bb4f430 100644 --- a/packages/ra-ui-materialui/src/list/datatable/DataTableRow.tsx +++ b/packages/ra-ui-materialui/src/list/datatable/DataTableRow.tsx @@ -159,6 +159,17 @@ export const DataTableRow = React.memo( getPathForRecord, ] ); + const isRowClickable = Boolean(rowClick ?? hasDetailView); + + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleClick(event as any); + } + }, + [handleClick] + ); return ( <> @@ -167,12 +178,14 @@ export const DataTableRow = React.memo( className={clsx(className, { [DataTableClasses.expandable]: expandable, [DataTableClasses.selectable]: selectable, - [DataTableClasses.clickableRow]: - rowClick ?? hasDetailView, + [DataTableClasses.clickableRow]: isRowClickable, })} key={record.id} hover={hover} onClick={handleClick} + onKeyDown={isRowClickable ? handleKeyDown : undefined} + role={isRowClickable ? 'button' : undefined} + tabIndex={isRowClickable ? 0 : undefined} {...rest} > {expand && (