Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/data/charts/accessibility/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
productId: x-charts
title: Charts - Accessibility
packageName: '@mui/x-charts'
---

# Accessibility

<p class="description">Learn how the Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p class="description">Learn how the Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>
<p class="description">Learn how Charts implement accessibility features and guidelines, including keyboard navigation that follows international standards.</p>


:::info
A common misconception about accessibility is to only consider blind people and the screen reader.
But there are other disability to consider, like:

- **Color blindness**, making it hard to distinguish different series, or low contrast elements.
- **Motion disability**, making it hard to open the tooltip on a given item.
- **Cognitive disability**, making it hard to focus your attention on some details.
- **Vestibular dysfunction**, making you unconformable with animations.

:::

## Guidelines

Common conformance guidelines for accessibility include:

- Globally accepted standard: [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/)
- US:
- [ADA](https://www.ada.gov/) - US Department of Justice

Check warning on line 28 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'US'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'US'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 28, "column": 35}}}, "severity": "WARNING"}
- [Section 508](https://www.section508.gov/) - US federal agencies

Check warning on line 29 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'US'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'US'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 29, "column": 50}}}, "severity": "WARNING"}
- Europe: [EAA](https://employment-social-affairs.ec.europa.eu/policies-and-activities/social-protection-social-inclusion/persons-disabilities/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en) (European Accessibility Act)

WCAG 2.1 has three levels of conformance: A, AA, and AAA.
Level AA exceeds the basic criteria for accessibility and is a common target for most organizations, so this is what we aim to support.

Check warning on line 33 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'we'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'we'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 33, "column": 118}}}, "severity": "WARNING"}

The WAI-ARIA Authoring Practices includes examples on [Tooltip](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).

<!-- ### Dialog considerations
Both `Desktop` and `Mobile` Date and Time Pickers are using `role="dialog"` to display their interactive view parts and thus they should follow [Modal accessibility guidelines](/material-ui/react-modal/#accessibility).
This behavior is automated as much as possible, ensuring that the Date and Time Pickers are accessible in most cases.
A correct `aria-labelledby` value is assigned to the dialog component based on the following rules:
- Use `toolbar` id if the toolbar is visible;
- Use the id of the input label if the toolbar is hidden;
:::info
Make sure to provide an `aria-labelledby` prop to `popper` and/or `mobilePaper` slots in case you are using Date and Time Pickers component with **hidden toolbar** and **without a label**.
::: -->

## Animation

Most of the charts have an animation when rendering or when data update.
For users with vestibular motion disorders those animations can be problematic.
By default we skip animation based on the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media feature.

Check warning on line 54 in docs/data/charts/accessibility/accessibility.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.We] Try to avoid using first-person plural like 'we'. Raw Output: {"message": "[Google.We] Try to avoid using first-person plural like 'we'.", "location": {"path": "docs/data/charts/accessibility/accessibility.md", "range": {"start": {"line": 54, "column": 12}}}, "severity": "WARNING"}

<!--
## Screen reader compatibility
Date and Time Pickers use ARIA roles and robust focus management across the interactive elements to convey the necessary information to users, being optimized for use with assistive technologies.
-->

## Keyboard support

Set `enableKeyboardNavigation` to `true` to enable the keyboard navigation on your charts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a global enable/disable toggle for this? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a sentence about how to use theme provider to do so

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a demo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

This feature is currently supported by line, bar, pie, and scatter charts.

This makes the SVG component focusable thanks to [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex).
When focused, the charts highlight a value item that can be modified with arrow navigation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When focused, the charts highlight a value item that can be modified with arrow navigation.
When focused, the chart highlights a value item that can be modified with arrow navigation.


| Keys | Description |
| --------------------------------------------------------------------: | :---------------------------- |
| <kbd class="key">Arrow Left</kbd>, <kbd class="key">Arrow Right</kbd> | Moves focus inside the series |
| <kbd class="key">Arrow Up</kbd>, <kbd class="key">Arrow Down</kbd> | Move focus between series |
2 changes: 1 addition & 1 deletion docs/data/charts/lines/lines.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: React Line chart
productId: x-charts
components: LineChart, LineChartPro, LineElement, LineHighlightElement, LineHighlightPlot, LinePlot, MarkElement, MarkPlot, AreaElement, AreaPlot, AnimatedLine, AnimatedArea, ChartsGrid
components: LineChart, LineChartPro, LineElement, LineHighlightElement, LineHighlightPlot, LinePlot, MarkElement, MarkPlot, AreaElement, AreaPlot, AnimatedLine, AnimatedArea, ChartsGrid, FocusedMark
---

# Charts - Lines
Expand Down
7 changes: 7 additions & 0 deletions docs/pages/x/react-charts/accessibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import * as pageProps from 'docsx/data/charts/accessibility/accessibility.md?muiMarkdown';

export default function Page() {
return <MarkdownDocs {...pageProps} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
UseChartInteractionSignature,
useChartHighlight,
UseChartHighlightSignature,
useChartKeyboardNavigation,
UseChartKeyboardNavigationSignature,
ConvertSignaturesIntoPlugins,
} from '@mui/x-charts/internals';
import {
Expand All @@ -20,6 +22,7 @@ export type BarChartProPluginsSignatures = [
UseChartCartesianAxisSignature<'bar'>,
UseChartInteractionSignature,
UseChartHighlightSignature,
UseChartKeyboardNavigationSignature,
UseChartProZoomSignature,
UseChartProExportSignature,
];
Expand All @@ -29,6 +32,7 @@ export const BAR_CHART_PRO_PLUGINS: ConvertSignaturesIntoPlugins<BarChartProPlug
useChartCartesianAxis,
useChartInteraction,
useChartHighlight,
useChartKeyboardNavigation,
useChartProZoom,
useChartProExport,
];
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ BarChartPro.propTypes = {
* @default false
*/
disableAxisListener: PropTypes.bool,
enableKeyboardNavigation: PropTypes.bool,
/**
* Option to display a cartesian grid in the background.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
UseChartInteractionSignature,
useChartHighlight,
UseChartHighlightSignature,
useChartKeyboardNavigation,
UseChartKeyboardNavigationSignature,
ConvertSignaturesIntoPlugins,
} from '@mui/x-charts/internals';
import {
Expand All @@ -20,6 +22,7 @@ export type LineChartProPluginsSignatures = [
UseChartCartesianAxisSignature<'line'>,
UseChartInteractionSignature,
UseChartHighlightSignature,
UseChartKeyboardNavigationSignature,
UseChartProZoomSignature,
UseChartProExportSignature,
];
Expand All @@ -29,6 +32,7 @@ export const LINE_CHART_PRO_PLUGINS: ConvertSignaturesIntoPlugins<LineChartProPl
useChartCartesianAxis,
useChartInteraction,
useChartHighlight,
useChartKeyboardNavigation,
useChartProZoom,
useChartProExport,
];
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ LineChartPro.propTypes = {
* If `true`, render the line highlight item.
*/
disableLineItemHighlight: PropTypes.bool,
enableKeyboardNavigation: PropTypes.bool,
/**
* Options to enable features planned for the next major.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
UseChartHighlightSignature,
UseChartInteractionSignature,
PIE_CHART_PLUGINS,
UseChartKeyboardNavigationSignature,
} from '@mui/x-charts/internals';
import {
useChartProExport,
Expand All @@ -12,6 +13,7 @@ import {
export type PieChartProPluginSignatures = [
UseChartInteractionSignature,
UseChartHighlightSignature,
UseChartKeyboardNavigationSignature,
UseChartProExportSignature,
];

Expand Down
1 change: 1 addition & 0 deletions packages/x-charts-pro/src/PieChartPro/PieChartPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ PieChartPro.propTypes = {
*/
dataset: PropTypes.arrayOf(PropTypes.object),
desc: PropTypes.string,
enableKeyboardNavigation: PropTypes.bool,
/**
* The height of the chart in px. If not defined, it takes the height of the parent element.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
UseChartHighlightSignature,
useChartVoronoi,
UseChartVoronoiSignature,
useChartKeyboardNavigation,
UseChartKeyboardNavigationSignature,
ConvertSignaturesIntoPlugins,
} from '@mui/x-charts/internals';
import {
Expand All @@ -23,6 +25,7 @@ export type ScatterChartProPluginsSignatures = [
UseChartInteractionSignature,
UseChartHighlightSignature,
UseChartVoronoiSignature,
UseChartKeyboardNavigationSignature,
UseChartProZoomSignature,
UseChartProExportSignature,
];
Expand All @@ -34,6 +37,7 @@ export const SCATTER_CHART_PRO_PLUGINS: ConvertSignaturesIntoPlugins<ScatterChar
useChartInteraction,
useChartHighlight,
useChartVoronoi,
useChartKeyboardNavigation,
useChartProZoom,
useChartProExport,
];
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ ScatterChartPro.propTypes = {
* @default false
*/
disableVoronoi: PropTypes.bool,
enableKeyboardNavigation: PropTypes.bool,
/**
* Option to display a cartesian grid in the background.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/x-charts/src/BarChart/BarChart.plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,24 @@ import {
useChartHighlight,
UseChartHighlightSignature,
} from '../internals/plugins/featurePlugins/useChartHighlight';
import {
useChartKeyboardNavigation,
UseChartKeyboardNavigationSignature,
} from '../internals/plugins/featurePlugins/useChartKeyboardNavigation';
import { ConvertSignaturesIntoPlugins } from '../internals/plugins/models/helpers';

export type BarChartPluginsSignatures = [
UseChartZAxisSignature,
UseChartCartesianAxisSignature<'bar'>,
UseChartInteractionSignature,
UseChartHighlightSignature,
UseChartKeyboardNavigationSignature,
];

export const BAR_CHART_PLUGINS: ConvertSignaturesIntoPlugins<BarChartPluginsSignatures> = [
useChartZAxis,
useChartCartesianAxis,
useChartInteraction,
useChartHighlight,
useChartKeyboardNavigation,
];
1 change: 1 addition & 0 deletions packages/x-charts/src/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ BarChart.propTypes = {
* @default false
*/
disableAxisListener: PropTypes.bool,
enableKeyboardNavigation: PropTypes.bool,
/**
* Option to display a cartesian grid in the background.
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/x-charts/src/BarChart/BarElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BarElementOwnerState, useUtilityClasses } from './barElementClasses';
import { useInteractionItemProps } from '../hooks/useInteractionItemProps';
import { useItemHighlighted } from '../hooks/useItemHighlighted';
import { AnimatedBarElement, BarProps } from './AnimatedBarElement';
import { useIsItemFocused } from '../hooks/useIsItemFocused';

export interface BarElementSlots {
/**
Expand Down Expand Up @@ -67,6 +68,11 @@ function BarElement(props: BarElementProps) {
seriesId: id,
dataIndex,
});
const isFocused = useIsItemFocused({
seriesType: 'bar',
seriesId: id,
dataIndex,
});

const ownerState = {
id,
Expand All @@ -75,6 +81,7 @@ function BarElement(props: BarElementProps) {
color,
isFaded,
isHighlighted,
isFocused,
};

const classes = useUtilityClasses(ownerState);
Expand Down Expand Up @@ -103,6 +110,7 @@ function BarElement(props: BarElementProps) {
fill: color,
skipAnimation,
layout,
'data-focused': isFocused || undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base UI uses data-highlighted, but that might conflict with our current highlighted logic.

Image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data-highlighted could clearly conflict. But maybe data-active to use similar wording as aria-activedescendent

},
className: classes.root,
ownerState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const useChartContainerProps = <
slots,
slotProps,
experimentalFeatures,
enableKeyboardNavigation,
...other
} = props as ChartContainerProps<TSeries, AllPluginSignatures>;

Expand Down Expand Up @@ -91,6 +92,7 @@ export const useChartContainerProps = <
localeText,
seriesConfig,
experimentalFeatures,
enableKeyboardNavigation,
plugins: plugins ?? DEFAULT_PLUGINS,
slots,
slotProps,
Expand Down
15 changes: 15 additions & 0 deletions packages/x-charts/src/ChartsSurface/ChartsSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
selectorChartContainerSize,
selectorChartPropsSize,
} from '../internals/plugins/corePlugins/useChartDimensions/useChartDimensions.selectors';
import {
selectorChartsHasFocusedItem,
selectorChartsIsKeyboardNavigationEnabled,
} from '../internals/plugins/featurePlugins/useChartKeyboardNavigation';

export interface ChartsSurfaceProps
extends Omit<
Expand Down Expand Up @@ -40,6 +44,13 @@ const ChartsSurfaceStyles = styled('svg', {
// For example, prevent page scroll & zoom.
touchAction: 'pan-y',
userSelect: 'none',
'&[data-has-focused-item=true]': {
// Move the focus outline responsibility to children
outline: 'none',
},
'& [data-focused=true]': {
outline: 'auto',
},
}));

/**
Expand All @@ -63,6 +74,8 @@ const ChartsSurface = React.forwardRef<SVGSVGElement, ChartsSurfaceProps>(functi
const store = useStore();
const { width: svgWidth, height: svgHeight } = useSelector(store, selectorChartContainerSize);
const { width: propsWidth, height: propsHeight } = useSelector(store, selectorChartPropsSize);
const isKeyboardNavigationEnabled = useSelector(store, selectorChartsIsKeyboardNavigationEnabled);
const hasFocusedItem = useSelector(store, selectorChartsHasFocusedItem);
const svgRef = useSvgRef();
const handleRef = useForkRef(svgRef, ref);
const themeProps = useThemeProps({ props: inProps, name: 'MuiChartsSurface' });
Expand All @@ -76,6 +89,8 @@ const ChartsSurface = React.forwardRef<SVGSVGElement, ChartsSurfaceProps>(functi
ownerState={{ width: propsWidth, height: propsHeight }}
viewBox={`${0} ${0} ${svgWidth} ${svgHeight}`}
className={className}
tabIndex={isKeyboardNavigationEnabled ? 0 : undefined}
data-has-focused-item={hasFocusedItem || undefined}
{...other}
ref={handleRef}
>
Expand Down
38 changes: 38 additions & 0 deletions packages/x-charts/src/LineChart/FocusedMark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import { useFocusedItem } from '../hooks/useFocusedItem';
import { useLineSeriesContext, useXAxes, useYAxes } from '../hooks';

const RADIUS = 6;
export function FocusedMark() {
const theme = useTheme();
const focusedItem = useFocusedItem();

const lineSeries = useLineSeriesContext();
const { xAxis, xAxisIds } = useXAxes();
const { yAxis, yAxisIds } = useYAxes();

if (focusedItem === null || focusedItem.seriesType !== 'line' || !lineSeries) {
return null;
}

const series = lineSeries?.series[focusedItem.seriesId];

const xAxisId = series.xAxisId ?? xAxisIds[0];
const yAxisId = series.yAxisId ?? yAxisIds[0];

return (
<rect
fill="none"
stroke={(theme.vars ?? theme).palette.text.primary}
strokeWidth={2}
x={xAxis[xAxisId].scale(xAxis[xAxisId].data![focusedItem.dataIndex])! - RADIUS}
y={yAxis[yAxisId].scale(series.stackedData[focusedItem.dataIndex][1])! - RADIUS}
width={2 * RADIUS}
height={2 * RADIUS}
rx={3}
ry={3}
/>
);
}
Loading
Loading