Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Accessibility bug fix",
"packageName": "@fluentui/react-charts",
"email": "132879294+v-baambati@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,11 @@ export const GroupedVerticalBarChart: React.FC<GroupedVerticalBarChartProps> = R
return domainNRangeValue;
}

// Utility to determine ARIA role for interactive elements
function getAriaRole(onClick?: unknown): 'button' | 'img' {
return onClick ? 'button' : 'img';
}

// The maxOfYVal prop is only required for the primary y-axis, so yMax should be calculated
// using only the data points associated with the primary y-axis.

Expand Down Expand Up @@ -594,7 +599,7 @@ export const GroupedVerticalBarChart: React.FC<GroupedVerticalBarChartProps> = R
onClick={pointData.onClick}
aria-label={getAriaLabel(pointData, singleSet.xAxisPoint)}
tabIndex={_legendHighlighted(pointData.legend) || _noLegendHighlighted() ? 0 : undefined}
role="img"
role={getAriaRole(pointData.onClick)}
/>,
);

Expand Down Expand Up @@ -871,7 +876,7 @@ export const GroupedVerticalBarChart: React.FC<GroupedVerticalBarChartProps> = R
tabIndex={shouldHighlight ? 0 : undefined}
onFocus={e => _onLineFocus(e, series, seriesIdx, pointIdx)}
onBlur={_onBarLeave}
role="img"
role={getAriaRole(point.onClick)}
aria-label={getAriaLabel(
{
xAxisCalloutData: point.xAxisCalloutData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ export const HorizontalBarChart: React.FunctionComponent<HorizontalBarChartProps
const [selectedLegend, setSelectedLegend] = React.useState<string>('');
const [activeLegend, setActiveLegend] = React.useState<string>('');

// Utility to check if a bar is interactive
const _isBarInteractive = (point: ChartDataPoint): boolean => {
return Boolean(point.onClick) || (!props.hideTooltip && point.legend !== '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This condition looks inconsistent across charts.

props.hideTooltip does not clearly represent interactivity. It only prevents the popover/callout from being shown.

Checking point.legend !== '' works for placeholder bars, but it could fail when a user intentionally sets the legend to an empty value for a real data series. Also, since we do not accept onClick or similar interaction props for placeholder bars, this additional check may not even be necessary.

I think we have two possible approaches:

interactive = typeof point.onClick === 'function'

Or, if we want only highlighted bars to be clickable:

interactive =
  (_legendHighlighted(point.legend!) || _noLegendHighlighted()) &&
  typeof point.onClick === 'function'

The 2nd option seems more appropriate but adopting it would require updating all charts to maintain consistent behavior across the library.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@AtishayMsft your inputs on this?

};

// Utility to determine ARIA role for bar
const _getAriaRole = (point: ChartDataPoint): 'button' | 'img' => {
return _isBarInteractive(point) ? 'button' : 'img';
};

function _refCallback(element: SVGGElement, legendTitle: string | undefined): void {
_refArray.push({ index: legendTitle, refElement: element });
}
Expand Down Expand Up @@ -320,13 +330,15 @@ export const HorizontalBarChart: React.FunctionComponent<HorizontalBarChartProps
_showToolTipOnSegment && point.legend !== '' ? event => _hoverOn(event, xValue, point) : undefined
Copy link
Copy Markdown

@github-actions github-actions Bot May 5, 2026

Choose a reason for hiding this comment

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

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Charts-DonutChart 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - RTL.default.chromium.png 5570 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 732 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 300 Changed
vr-tests-react-components/ProgressBar converged 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 34 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 84 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 68 Changed

}
onFocus={_showToolTipOnSegment && point.legend !== '' ? event => _hoverOn(event, xValue, point) : undefined}
role="img"
role={_getAriaRole(point)}
aria-label={_getAriaLabel(point)}
onBlur={_hoverOff}
onMouseLeave={_hoverOff}
className={classes.barWrapper}
opacity={isLegendSelected ? 1 : 0.1}
tabIndex={_legendHighlighted(point.legend!) || _noLegendHighlighted() ? 0 : undefined}
tabIndex={
(_legendHighlighted(point.legend!) || _noLegendHighlighted()) && _isBarInteractive(point) ? 0 : undefined
}
/>
);
});
Expand Down
Loading
Loading