#25320·highcharts

Accessibility module's forced markers make the legend symbol transparent on redraw

Author: frogdevvnCreated Sep 9, 2026Updated Sep 16, 2026
LabelsType: BugProduct: Highchartsa11y

Legend swatch turns invisible after any redraw when the accessibility module forces markers on a series

Expected behaviour

A series with marker: { enabled: false } and legendSymbol: 'rectangle' keeps its legend swatch visible for the life of the chart.

Actual behaviour

The swatch is painted correctly on first render, then becomes fully transparent (opacity="0" on the symbol element) after the first redraw that re-renders the legend — a window resize is the usual trigger. The label stays, so the legend renders as text with no colour key.

Loading the accessibility module is enough to trigger it; no accessibility options need to be set. legend — a window resize is the usual trigger. The label stays, so the legend renders as text with no colour key.

Loading the accessibility module is enough to trigger it; no accessibility options need to be set.

Reproduction

javascript
// modules/accessibility.js loaded, no accessibility options set
const chart = Highcharts.chart('container', {
  series: [{
    type: 'line',
    name: 'Series 1',
    data: [1, 2, 3, 4, 5],
    marker: { enabled: false },
    legendSymbol: 'rectangle'
  }]
});

const opacity = () =>
  chart.series[0].legendItem.symbol.element.getAttribute('opacity');

console.log('first render:', opacity());   // "1"  — correct
chart.setSize(500, 300, false);            // or just resize the window
console.log('after redraw:', opacity());   // "0"  — swatch is gone

console.log('after redraw:', opacity()); // "0" — swatch is gone


Controls, same page and same Highcharts instance:

| config | swatch opacity, first render → after redraw |
| --- | --- |
| `marker.enabled: false`, accessibility loaded | `1` → **`0`** |
| `marker.enabled: false`, `accessibility: { enabled: false }` | `1` → `1` |
| markers left enabled, accessibility loaded | `1` → `1` |

### Cause

`ForcedMarkers` turns markers back on for any series that disables them, and
hides them by merging a presentation override into the **live series options**
(`Accessibility/Components/SeriesComponent/ForcedMarkers.js`, `seriesOnRender` →
`forceZeroOpacityMarkerOptions`):

```js
merge(true, options, {
  marker: { enabled: true, states: { normal: { opacity: 0 } } }
});

Series#pointAttribs (Core/Series/Series.js) resolves state 'normal' and so returns opacity: 0:

javascript
opacity = pointStateOptions.opacity ?? seriesStateOptions.opacity ?? opacity;

Legend#colorizeItem (Core/Legend/Legend.js) then applies that whole attribute hash — opacity included — to the legend swatch:

javascript
symbol?.attr(colorizeHidden(item.series ?
  item.series.pointAttribs?.(item) :
  item.pointAttribs?.() || { fill: item.color }));
```js
symbol?.attr(colorizeHidden(item.series ?
item.series.pointAttribs?.(item) :
item.pointAttribs?.() || { fill: item.color }));

The first render escapes it because of paint order: Legend#renderItem draws the symbol and colorizes it before the series.render that rewrites the option. renderItem creates the symbol only once (if (!label)), but colorizeItem runs on every legend render, so every subsequent render picks up the zero.

That paint-order dependence is what makes this look unintended rather than by design — and the zero opacity is meant to keep the forced markers invisible in the plot, so leaking it to the legend defeats the module's own goal of being visually neutral.

Series whose legendSymbol resolves to lineMarker are unaffected in practice: drawLegendSymbol runs once, before the mutation, so no marker element is ever created for the legend to fade out.

Suggested fix

Either:

  1. Legend#colorizeItem should not take opacity from pointAttribs() when painting a swatch (the hidden/visible state is already handled separately by colorizeHidden); or
  2. ForcedMarkers should not write a presentation-only override into the shared series.options tree — applying the zero opacity to the marker group or the point graphics would keep it scoped to the plot.

Environment

  • Highcharts 13.0.1; ForcedMarkers.js and Legend#colorizeItem are byte-identical in 13.0.2
  • Accessibility module loaded
  • Chrome 141, macOS 15

Workaround

Restore the swatches on chart.events.render:

javascript
chart: {
  events: {
    render(event) {
      for (const series of event.target.series) {
        series.legendItem?.symbol?.attr({ opacity: 1 });
      }
    }
  }
}