Problem

MUI X Charts (LineChart, BarChart, etc.) positions its tooltip via a Popper component that uses absolute/fixed positioning internally. When the chart is rendered inside a Paper, Card, or Dialog ancestor, those components create a new CSS stacking context (due to box-shadow + z-index). This causes the tooltip to be clipped or rendered behind the ancestor — invisible or in the wrong location on screen.

v8 Fix (superseded)

The original fix portalled the Popper to document.body:

slotProps={{ tooltip: { container: document.body } }}

This worked in v8 because the container prop was respected and placed the tooltip outside all ancestor stacking contexts.

v9 Behaviour Change

@mui/x-charts@9.3.0 silently overrides container — it always uses chartsLayerContainerRef.current (the chart’s own root <div>) regardless of what is passed. The container prop is a no-op in v9.

v9 also switches the Popper to popperOptions: { strategy: 'fixed' }, which makes the tooltip viewport-relative and partially addresses the stacking-context issue. However, the tooltip’s z-index defaults to theme.zIndex.modal (1300), which conflicts with Dialog (also 1300). Inside a Dialog the tooltip can still be obscured.

Current Fix (v9)

Both wrappers apply a fixedTooltipSx that raises the tooltip’s z-index to theme.zIndex.tooltip (1500), placing it above all modal-level components:

const fixedTooltipSx = (theme: Theme) => ({ zIndex: theme.zIndex.tooltip });
 
function mergeTooltipSx(callerSx: SxProps<Theme> | undefined): SxProps<Theme> {
  const normalized = Array.isArray(callerSx)
    ? callerSx
    : callerSx
      ? [callerSx]
      : [];
  return [...normalized, fixedTooltipSx];
}
 
export const AppLineChart = ({ slotProps, ...rest }: LineChartProps) => {
  const { tooltip: callerTooltip, ...otherSlotProps } = slotProps ?? {};
  return (
    <LineChart
      {...rest}
      slotProps={{
        ...otherSlotProps,
        tooltip: { ...callerTooltip, sx: mergeTooltipSx(callerTooltip?.sx) },
      }}
    />
  );
};

Key decisions:

PointDetail
container removedWas a no-op in v9 — removed to avoid confusion
sx array mergeCaller’s sx is normalised to an array and fixedTooltipSx is appended last, so it always wins
slotProps deep-mergecallerTooltip spread before the sx override — callers can pass their own tooltip slotProps without losing the z-index fix
z-index valuetheme.zIndex.tooltip = 1500, above modal (1300) and dialog (1300)

AppBarChart follows the identical pattern with BarChart / BarChartProps.

Wrapper Components — Always Use These

Project-wide wrappers live in frontend/shared/components/:

WrapperReplaces
AppLineChart@mui/x-charts/LineChart
AppBarChart@mui/x-charts/BarChart

Never import LineChart or BarChart directly from @mui/x-charts. Always use the app wrappers — they carry the tooltip fix and forward all props.

// ✅ Correct
import { AppLineChart } from '@frontend/shared/components/AppLineChart';
 
// ❌ Wrong — tooltip will break inside Paper/Card/Dialog
import { LineChart } from '@mui/x-charts/LineChart';

If a new chart type is needed (e.g. ScatterChart), create a matching AppScatterChart.tsx wrapper using the same pattern.