[core] Allow opt-in data-* attributes on slotProps#22128
Conversation
Previously, passing `data-testid` or any `data-*` / `aria-*` attribute through `slotProps` required an `as any` cast in TypeScript, even though the attributes are forwarded at runtime. Users hit this any time they wanted to attach a test id to a slot. - Introduce `DataAttributes` in `@mui/x-internals/types` combining `React.AriaAttributes` with a `data-*` index signature and an augmentable `DataAttributesOverrides` interface for strongly-typed custom keys. - Generalize `SlotComponentPropsFromProps` to accept either a props type or a React element type, and widen its output with `DataAttributes` via a union variant (so existing `as CustomProps` casts keep working). - Drop `SlotComponentProps` from `@mui/utils/types` in favor of the unified helper across pickers, tree-view, chat, chat-headless. - Consolidate Charts' ad-hoc `data-*` / `aria-*` index signatures on `ChartBaseCommonProps` to reuse `DataAttributes`. - Extend `describeSlotsConformance` to assert `data-*` / `aria-*` forwarding per slot, and clean up the `as any` workarounds in PickerDay, tree-view, and chat-headless tests. Closes mui#22126
Deploy previewBundle size
PerformanceTotal duration: 1,514.59 ms +56.03 ms(+3.8%) | Renders: 63 (+0) | Paint: 2,118.04 ms +116.75 ms(+5.8%)
…and 2 more (+19 within noise) — details Metric alarms
Check out the code infra dashboard for more information about this PR. |
Resolves the OOM/assignability regressions caused by directly intersecting `DataAttributes` into slot props. `WithDataAttributes<T> = T | (T & DataAttributes)` keeps pre-typed values assignable to the original branch while object literals can pick up the widened branch for `data-*` / `aria-*` keys. Applied across pickers, charts, data-grid, chat, and tree-view slot types. Added `slotDataAttributes.spec.ts` files using the new `AssertAllSlotsAcceptDataAttributes` helper so every package compile-time verifies every slot accepts `data-*` / `aria-*`. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Removes the `as any` data-*/aria-* casts from tests that the type widening now makes unnecessary, and widens the remaining nested `htmlInput` / `input` / `inputLabel` slot types inside `gridBaseSlots.ts` so Data Grid consumers can reach the same attributes without a cast. Also drops the `(other as any)['data-testid']` cast in `DateRangePickerDay.tsx` and replaces the `props: any` parameter in `describeSlotsConformance` with a documented loose interface. Per-slot type-level coverage continues to live in the `slotDataAttributes.spec.ts` files, since dynamic `[slotName]` keys cannot be statically matched to a component's typed `slotProps` shape inside the helper. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Two review findings + one latent bug surfaced by fixing the second:
1. SlotComponentPropsFromProps: widen the callback branch via
WithDataAttributes (union preserving T) instead of an unconditional
`& DataAttributes` intersection. Consumers that return a cast custom
type from the callback stay assignable without having to declare a
data-* index signature on that type.
2. AssertAllSlotsAcceptDataAttributes: probe both data-* and aria-label
as marker keys, so the assertion actually enforces what its name and
error message claim. The previous `V extends { [k: data-...]: any }`
structural check was trivially satisfied by any object without
conflicting keys, so unwidened slots silently passed.
3. Fixing (2) surfaced slots that had `Partial<SomeProps>` without
widening. Wrapped each with `WithDataAttributes<>`:
- charts: mark, tooltip (ChartsTooltipSlotProps), scatter,
ScatterChartSlotProps.tooltip, pieArc, pieArcLabel
- charts-pro: HeatmapSlotProps.tooltip, HeatmapItemSlotProps.cell,
HeatmapTooltipSlotProps.tooltip, ChartsToolbarProSlotProps.toolbar,
ScatterChartProSlotProps.tooltip
- date-pickers: PickersModalDialogSlotProps.{dialog, mobilePaper,
mobileTransition}
Distribution mechanics in the aria check: the outer `V extends any`
forces distribution over union variants, so a `T | (T & DataAttributes)`
shape passes as soon as the widened branch exposes both keys. Without
that, `keyof (A | B)` collapses to `keyof A & keyof B` and neither marker
is visible.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
…mui-x into claude/trusting-ishizaka-77d81a
| * } | ||
| * } | ||
| */ | ||
| export interface DataAttributesOverrides {} |
There was a problem hiding this comment.
but why does user needs module-augmentation if L:25 already there.
I have another proposal, how about removing "[k: data-${string}]: DataAttributeValue" and provide only DataAttributesOverrides? Meaning user needs to augment DataAttributesOverrides to pass the data-*
This will give them type-safe through out their app, I don't think data-* will be customized over 10 values for a project.
There was a problem hiding this comment.
Are you suggesting to support data-* attributes only through augmentation?
That's not a bad idea, but still one that would cause way too much pain, especially given that the need for this is a result of the API we chose.
With the data-* TS accepts data attributes without needing to cast or add @ts-expect-error.
The Overrides allow specific typing (IntelliSense).
There was a problem hiding this comment.
Are you suggesting to support data-* attributes only through augmentation?
Yes, users get type-safe benefits and autocompletion.
With the data-* TS accepts data attributes without needing to cast or add @ts-expect-error.
The Overrides allow specific typing (IntelliSense).
True, but if you type data-testiid, I believe that this will not throw a type error.
My argument is that we should let users decide on the level of type safety they want to have because they can also do:
declare module "…" {
interface DataAttributesOverrides {
[k: `data-${string}`]: DataAttributeValue
}
}There was a problem hiding this comment.
@siriwatknp, your proposal makes sense.
My only gripe is the fact that some packages (Data Grid and Charts) already have explicit data-* definitions, while others lack these explicit definitions.
Going with your proposal would keep the existing inconsistency between X libraries. 🤔
Do you still agree that an opt-in approach is the best compromise?
There was a problem hiding this comment.
My only gripe is the fact that some packages (Data Grid and Charts) already have explicit data-* definitions, while others lack these explicit definitions.
I think that's our internal implementation and those works because it's directly used on JSX.
The opt-in approach is for consumer, which I still prefer this as starting point. If there is a push from users to make it data-{string}, it can still be done.
There was a problem hiding this comment.
Anyway, it's your call. I don't have strong preference, just thinking that the opt-in is a good first step.
There was a problem hiding this comment.
Well, I was leaning towards doing this PR as an experiment and then apply the changes in the material ui repo (mui/material-ui#33175) so that it trickles down to this repo.
The only gripe is that is gonna be optional based on the material package version you use, but we can clarify it in the docs.
WDYT, should I try tackling that issue at the root location?
If possible, I would like to avoid the need to change the module declaration type from declare module '@mui/x-internals/types' to declare module '@mui/utils/types'.
There was a problem hiding this comment.
yes, that makes sense. If fixing in Material UI benefits both, then better.
There was a problem hiding this comment.
The PR has been updated and is ready for a re-review. 👌
It has become so much smaller due to the type widening from the SlotComponentProps from @mui/utils/types.
There is one missing piece in X docs - a doc section.
I haven't added it, because there is no single TypeScript page, and I didn't want to duplicate it in so many sub-product TypeScript sections.
If you feel like it is essential, I can add a small section also linking to material-ui docs.
It might be worth noting that to fully benefit from this, you need to use >=9.2.0 of material package.
- AssertSlotDataAttributes.spec.ts: replace commented-out negative case with a real `interface BadSlotProps` + `@ts-expect-error` directive so the sanity check is now self-verifying (if the helper stops catching bad slots, the unused directive fails the typecheck). - AssertSlotDataAttributes.ts: swap "iff" for plain "if" / "only when" in the two doc comments that used it. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Every real slot inner type in MUI X already inherits React.AriaAttributes
through its element or component base type (HTMLAttributes, SVGAttributes,
ButtonBaseProps, PopperProps, etc.), so including aria-* in the shared
DataAttributes widener was redundant. Narrow the widener to only add the
data-* index signature that React's built-in types don't otherwise expose,
so the helper does exactly what its name says.
- DataAttributes no longer intersects React.AriaAttributes; only the
`[k: `data-${string}`]` index signature plus user overrides remain.
- AssertAllSlotsAcceptDataAttributes now checks a single marker (`data-x`)
instead of both data-x and aria-label, and its failure message is
updated accordingly.
- ChartBaseCommonProps uses React.DOMAttributes (not HTMLAttributes), so
aria is re-added explicitly there to preserve current behavior.
- Per-package slotDataAttributes.spec.ts comments updated.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
|
Follow-up: tightened Why
A repo-wide grep for narrow inline types ( So including
What changed
Verification
The runtime test in Performance noteThe existing perf numbers in the PR body represent the upper bound. Dropping |
data-* and aria-* attributes on slotPropsdata-* attributes on slotProps
data-* attributes on slotPropsslotProps to allow data-* attributes
|
@siriwatknp I have noticed that the majority of the slots extend some React HTML element attributes, which downstream extend the |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…shizaka-77d81a # Conflicts: # packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx
Per team agreement, MUI X slot props should not include arbitrary `data-*`
keys by default. The single supported way for consumers to type those keys
is to augment the `DataAttributesOverrides` interface, which they can shape
to whichever level of strictness they want.
- DataAttributes is now equivalent to DataAttributesOverrides (empty by
default). The default `[k: \`data-${string}\`]` index signature is
removed; WithDataAttributes becomes a dormant widener that activates only
when a consumer augments DataAttributesOverrides.
- Drop the AssertAllSlotsAcceptDataAttributes helper and its 13 per-package
`slotDataAttributes.spec.ts` files: there's no default contract to assert
anymore.
- Restore the `(other as any)['data-testid']` source compat casts in
PickerDay and DateRangePickerDay (they were removed on the assumption
data-* would be typed by default; reverting that assumption needs the
casts back).
- Restore the `as any` casts in tests that pass `data-*` via slotProps.
Tests still verify runtime forwarding; the cast just re-acknowledges
that the type-level contract is opt-in.
- Revert chartsBaseSlotProps and gridSlotsComponentsProps RootProps/MainProps
to their master shapes — those public types already had ad-hoc `data-*`
index signatures; the new direction is to leave per-component decisions
alone, not to centralize them.
- DesktopDateRangePicker popper test uses `getByRole('tooltip')` instead
of `data-testid` to find the popper element.
- Update describeSlotsConformance comment to drop the reference to the
deleted spec files; the runtime `data-*` / `aria-*` forwarding test on
the helper itself stays — it's a runtime check, independent of the type
surface.
Kept: WithDataAttributes wrapper, SlotComponentPropsFromProps unification,
all per-slot widenings across charts, pickers, tree-view, chat, and the
data-grid slot map. With empty DataAttributes these are no-ops, but they
form the load-bearing surface that activates as soon as a consumer
augments DataAttributesOverrides.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
slotProps to allow data-* attributesDataAttributesOverrides augmentation for data-* on slot props
The runtime event-forwarding test added on top of the latent slot
coverage relied on `screen.getByRole('tooltip')` to find the popper,
but in `<DesktopDateRangePicker open>` rendered without user
interaction the popper does not expose the `tooltip` role. The test
failed 100% on all 4 CI pipelines (regular and React 18). The check
isn't load-bearing for the rest of the slot type-coverage work.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Claude Opus 4.8 replyI verified all four against the code (tracing the runtime paths, not just the types); all four are real. Addressed in three commits: 8af03a1 (exclude non-forwarding slots), c6254be (messageActions), 445b54b (docs).
For the excluded slots, the per-package specs now pass an explicit exclusion list rather than silently dropping them, so the exclusion is documented and a future regression on the remaining slots still surfaces. On the aria sub-claim: correct that On the broader recommendation (make runtime forwarding a prerequisite, audit + runtime-test every slot): I'd keep that as the agreed follow-up rather than a merge gate. Tracing the widened slots, nearly all forward at runtime, and the specific non-forwarding ones you found are now excluded on the same principle as Data Grid. A full per-slot runtime-forwarding audit with runtime tests across Charts / Pickers / Chat (alongside the Data Grid work) is the tracked follow-up. |
GPT 5.6 Sol Follow-up finding[P1]
Consequently this type-checks but produces no locator: <ScatterChart
slotProps={{
scatter: { 'data-testid': 'scatter-series' },
}}
/>This directly contradicts the response’s statement that the checked plot leaves, including Recommended resolution: either remove Previous findingsThe four original findings are otherwise addressed correctly:
Design assessmentThe central design rule should be: Two non-blocking concerns remain:
I reviewed exact head |
A follow-up audit traced every widened charts slot to the DOM. `scatter` was the one remaining mismatch: ScatterPlot spreads slotProps.scatter onto ScatterItems, but all three default renderers (svg-single Scatter, svg-batch BatchScatter, progressive ScatterAsync) destructure named props with no `...other`, so `data-*` never reaches an element. Un-widen it and exclude it from the ScatterChart/ScatterChartPro assertions, same as xAxis/yAxis. The other traced slots (marker, mark, pieArc, pieArcLabel, funnelSection, funnelSectionLabel, cell, tooltip, root, and the four axis leaves) do forward `...other` onto an intrinsic element and stay widened. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The comment claimed every slot inner type inherits React.AriaAttributes, which MuiTrapFocusProps disproves. Reword to the accurate reason aria isn't checked: this widener only adds data-* keys (DataAttributesOverrides carries no aria keys), so aria is simply out of its scope. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adding a direct dependency alone doesn't guarantee a single resolved version (an existing lockfile or a future major can keep two copies). Recommend the `^9.2.0` range and tell consumers to verify the lockfile resolves @mui/utils to one copy, running their package manager's dedupe if needed. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The previous wording implied dedupe collapses everything to a single @mui/utils copy. It can't: an older Material UI (v7) pins @mui/utils@^7, which is disjoint from MUI X's @mui/utils@^9.2, so two copies always coexist. Clarify that a direct @mui/utils@^9.2 dependency helps only by making your augmentation resolve to the same v9.2 copy MUI X uses (the v7 copy stays separate), that this depends on the package manager, and that upgrading Material UI to 9.2+ is the reliable fix. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Empirically checked the Material UI 7 + MUI X case (real @mui/[email protected] pulling @mui/[email protected], a package consuming @mui/[email protected], and tsc): without a direct @mui/[email protected] dependency the augmentation targets Material's v7 copy and data-* is rejected; adding @mui/utils@^9.2.0 as a direct dependency makes it resolve to MUI X's v9.2 copy and it type-checks (confirmed under both pnpm and npm). The two copies still coexist -- the fix dedupes the v9.2 copies, not the v7 one. Reword from the earlier speculative "best-effort" phrasing. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
All points from both reviews are addressed. Summary with commits: Original findings
Follow-up finding
Non-blocking
On the design rule (widen only where the default slot has a DOM root that forwards): agreed, and the scatter miss is a fair hit against a compile-time-only check. I've now applied that rule across the full charts surface by tracing each slot; a broad per-slot runtime-forwarding test sweep across Charts / Pickers / Chat, plus Data Grid, stays the tracked follow-up rather than a merge gate. |
GPT 5.6 Sol Findings[P1]
|
ScatterChartPremium re-declared the tooltip slot without WithDataAttributes (dropping the widening it inherits from the community ScatterChart), yet the tooltip forwards data-* to the DOM at runtime -- so it should be widened. Wrap it like community/pro. The gap hid because the premium spec asserted only 5 of the 14 exported *SlotProps. Add the rest (ScatterChartPremium, RadialBarChart, RadialLineChart, the geo/radial providers, and the bar/scatter/radial plot types), excluding xAxis/yAxis/scatter where they appear -- same as the other charts specs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The comment claimed "every exported SlotProps type", but the specs assert the top-level component and provider *SlotProps -- the surfaces a consumer passes slotProps to. Reword to say that, and note that nested-component slot props (plot elements, use* hooks) are exercised transitively through their parent. Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Add `import type {} from '@mui/material/utils'` to the snippet: in a
standalone declaration file the block is otherwise an ambient module
declaration, not augmentation, and data-* stays rejected (verified).
- Note the same marker for the `@mui/utils/types` fallback.
- Material UI v7 keeps using its own @mui/utils copy; the workaround only
enables data-* on MUI X slots, not Material UI v7 slots.
- Soften "always resolves one shared copy" to "normally", with the lockfile
caveat for overrides/resolutions.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude Opus 4.8 replyAll three addressed (224af3e, 2bb1459, 6be5f68): [P1] ScatterChartPremium.tooltip -- fixed. It re-declared the slot without [P2] standalone declaration file -- fixed, and confirmed with a repro: augmenting the re-export [P2] wording -- fixed. Material UI v7 keeps using its own On coverage scope: the specs assert every top-level component and provider |
ChatMessage is a public component with its own ChatMessageSlotProps that
declared raw Partial<...> fields, so data-* was rejected on <ChatMessage
slotProps={{ avatar: { 'data-testid' } }} /> even though the props forward at
runtime. ChatBoxSlotProps was widened but does not transitively cover this
independent declaration. Widen the object fields and both actions branches,
matching ChatBox.messageActions.
Add spec coverage via NonNullable<ChatMessageProps['slotProps']> (the slot
props type is not itself exported), plus ChatMessageList/ChatMessageGroup and a
callback-return guard for ChatMessage.actions, so future independent
declarations cannot escape coverage.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extend the compile-time specs to cover every consumer-reachable *SlotProps, not just the top-level charts, so an independently-declared surface (as ChatMessage was) cannot escape coverage: - x-charts: the plot/element components exported from the root (Bar/Line/Pie/ Scatter plots and elements, ChartsTooltip), imported via their chart subpaths. - x-charts-pro: FunnelPlot, HeatmapPlot, HeatmapTooltip, SankeyTooltip and the pro container/provider/toolbar surfaces. - x-chat-headless: MessageError, StreamingIndicator. - x-date-pickers-pro: MultiInputDateRangeField. Internal use*-hook slot-prop types and non-exported base interfaces are left to their parent's coverage. Non-forwarding slots (xAxis/yAxis/scatter) stay excluded where those surfaces inherit them. Co-Authored-By: Claude Opus 4.8 <[email protected]>
On Material UI 7 the @mui/material/utils path does not work at all: v7 has no DataAttributesOverrides to re-export, and augmenting it targets Material's own v7 @mui/utils copy, not the v9.2 copy MUI X imports (verified). Adding a direct @mui/utils@^9.2 dependency alone is not enough -- the augmentation target must also switch to @mui/utils/types. Spell that out. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ource The per-package assertions accept a slot as long as one union arm exposes data-*, so a regression that widened only the object arm of the shared SlotComponentPropsFromProps utility (used across charts, pickers, tree view) would keep every package-wide assertion green. Add an x-internals spec that extracts the callback return independently and asserts it accepts data-*. Verified it fails if the callback return is un-widened. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Final GPT 5.6 Sol follow-up reviewNo further findings. I verified the two follow-up commits on the exact PR head, Verified fixes
Final assessment
Verdict: looks good to merge once the remaining CI checks finish successfully. |
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in, type-safe mechanism for allowing data-* attributes in MUI X slotProps by adopting WithDataAttributes / DataAttributesOverrides from @mui/utils (and the @mui/material/utils re-export path), and backs it with broad compile-time coverage plus an initial runtime forwarding conformance check.
Changes:
- Widen slot prop types across X packages using
WithDataAttributes(including callback-form slotProps viaSlotComponentPropsFromProps). - Add reusable TS-only assertion utilities and per-package
slotDataAttributes.spec.tsfiles to verify every exported*SlotPropsacceptsdata-*once augmented (with explicit exclusions). - Extend
describeSlotsConformancewith a runtime check thatdata-*andaria-*passed viaslotPropsreach the slot’s DOM element; document the consumer augmentation setup in the “Custom components” guide.
Reviewed changes
Copilot reviewed 47 out of 47 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/utils/slotDataAttributes.ts | Adds type-level utilities to assert data-* support across slot prop unions/callbacks. |
| test/utils/describeSlotsConformance.tsx | Adds runtime conformance coverage for forwarding data-*/aria-* through slotProps. |
| packages/x-tree-view/src/tests/slotDataAttributes.spec.ts | Compile-time coverage ensuring Tree View slot props accept data-* after augmentation. |
| packages/x-tree-view-pro/src/tests/slotDataAttributes.spec.ts | Compile-time coverage ensuring Tree View Pro slot props accept data-* after augmentation. |
| packages/x-internals/src/types/SlotComponentPropsFromProps.ts | Widens the callback-capable slot prop helper with WithDataAttributes for object + callback return. |
| packages/x-internals/src/tests/slotDataAttributes.spec.ts | Guards that callback-return widening is applied correctly at the source helper type. |
| packages/x-date-pickers/src/TimePicker/shared.tsx | Widens toolbar slot props to accept data-* (opt-in). |
| packages/x-date-pickers/src/tests/slotDataAttributes.spec.ts | Adds broad pickers compile-time slot coverage, with explicit excluded slots. |
| packages/x-date-pickers/src/PickersTextField/PickersTextField.types.ts | Widens PickersTextField slot prop types using WithDataAttributes. |
| packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts | Widens layout-related slot props using WithDataAttributes. |
| packages/x-date-pickers/src/PickerDay/PickerDay.types.ts | Attempts to add data-* support to PickerDay props (see review comments re: approach). |
| packages/x-date-pickers/src/internals/components/PickersModalDialog.tsx | Widens modal dialog slot props using WithDataAttributes. |
| packages/x-date-pickers/src/internals/components/PickerPopper/PickerPopper.tsx | Widens desktop transition slot props; documents excluded FocusTrap slot. |
| packages/x-date-pickers/src/DateTimePicker/shared.tsx | Widens tabs/toolbar slot props using WithDataAttributes. |
| packages/x-date-pickers/src/DatePicker/shared.tsx | Widens toolbar slot props using WithDataAttributes. |
| packages/x-date-pickers-pro/src/TimeRangePicker/shared.tsx | Widens tabs/toolbar slot props using WithDataAttributes. |
| packages/x-date-pickers-pro/src/tests/slotDataAttributes.spec.ts | Adds broad pickers-pro compile-time slot coverage with exclusions. |
| packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.types.ts | Widens toolbar slot props in static range picker hook types. |
| packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx | Removes an explicit cast in merged toolbar slot props (relies on widened typing). |
| packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx | Widens tabs/toolbar slot props using WithDataAttributes. |
| packages/x-date-pickers-pro/src/DateRangePicker/shared.tsx | Widens toolbar slot props using WithDataAttributes. |
| packages/x-chat/src/tests/slotDataAttributes.spec.ts | Adds compile-time slot coverage for Chat exported components, including callback-return guards. |
| packages/x-chat/src/ChatMessage/ChatMessage.tsx | Widens multiple ChatMessage slot props (including callback form) with WithDataAttributes. |
| packages/x-chat/src/ChatBox/ChatBox.types.ts | Widens ChatBox slot props with WithDataAttributes and documents callback-return behavior. |
| packages/x-chat-headless/src/tests/slotDataAttributes.spec.ts | Adds extensive compile-time slot coverage for Chat Headless slot props. |
| packages/x-charts/src/Toolbar/Toolbar.types.ts | Widens Charts toolbar slot props using WithDataAttributes. |
| packages/x-charts/src/tests/slotDataAttributes.spec.ts | Adds broad charts compile-time slot coverage with explicit excluded slots. |
| packages/x-charts/src/ScatterChart/ScatterPlot.tsx | Documents intentional exclusion of scatter slot from widening due to no DOM prop spreading. |
| packages/x-charts/src/ScatterChart/ScatterMarker.types.ts | Widens marker slot props using WithDataAttributes. |
| packages/x-charts/src/ScatterChart/ScatterChart.tsx | Widens tooltip slot props using WithDataAttributes. |
| packages/x-charts/src/PieChart/PieArcPlot.tsx | Widens pieArc slot props using WithDataAttributes. |
| packages/x-charts/src/PieChart/PieArcLabelPlot.tsx | Widens pieArcLabel slot props using WithDataAttributes. |
| packages/x-charts/src/models/axis.ts | Widens axis-related SVG/text slot props; documents excluded whole-axis replacement slots. |
| packages/x-charts/src/LineChart/MarkPlot.tsx | Widens mark slot props using WithDataAttributes. |
| packages/x-charts/src/ChartsTooltip/ChartTooltip.types.ts | Widens tooltip slot props using WithDataAttributes. |
| packages/x-charts/src/ChartsTooltip/ChartsTooltipContainer.tsx | Widens Popper root slot props using WithDataAttributes while preserving other slot props. |
| packages/x-charts/src/ChartsLegend/chartsLegend.types.ts | Widens legend slot props using WithDataAttributes. |
| packages/x-charts-pro/src/tests/slotDataAttributes.spec.ts | Adds charts-pro compile-time slot coverage with explicit excluded slots. |
| packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx | Widens tooltip slot props using WithDataAttributes. |
| packages/x-charts-pro/src/Heatmap/HeatmapTooltip/HeatmapTooltip.types.ts | Widens heatmap tooltip slot props using WithDataAttributes. |
| packages/x-charts-pro/src/Heatmap/HeatmapItem.tsx | Widens heatmap cell slot props using WithDataAttributes. |
| packages/x-charts-pro/src/Heatmap/Heatmap.tsx | Widens heatmap tooltip slot props using WithDataAttributes. |
| packages/x-charts-pro/src/FunnelChart/funnelPlotSlots.types.ts | Widens funnel section/label slot props using WithDataAttributes. |
| packages/x-charts-pro/src/ChartsToolbarPro/Toolbar.types.ts | Widens pro toolbar slot props using WithDataAttributes. |
| packages/x-charts-premium/src/tests/slotDataAttributes.spec.ts | Adds charts-premium compile-time slot coverage with explicit excluded slots. |
| packages/x-charts-premium/src/ScatterChartPremium/ScatterChartPremium.tsx | Widens tooltip slot props using WithDataAttributes. |
| docs/data/common-concepts/custom-components/custom-components.md | Documents the consumer-facing augmentation approach and version-dependent guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Leftover from the PR's first iteration. The `day` slot is already widened via `SlotComponentPropsFromProps<PickerDayProps>`, so `data-*` on `slotProps.day` type-checks without it. Extending the base props type with the augmentable index signature is redundant and reintroduces the assignability friction the `WithDataAttributes<T> = T | (T & Overrides)` union is designed to avoid. Removing it restores PickerDay.types.ts to master (per Copilot review). Co-Authored-By: Claude Opus 4.8 <[email protected]>
…ta-* The three default tab components (DateTimePickerTabs, DateTimeRangePickerTabs, TimeRangePickerTabs) destructure a fixed prop list with no `...other`, so a `data-*` attribute passed via slotProps.tabs never reaches the DOM. It was widened in PickersLayout.types.ts and the DateTime/TimeRange shared.tsx files, producing a green typecheck for an attribute that silently doesn't render -- the same over-promise that got Data Grid excluded. Un-widen `tabs` (like scatter/xAxis/desktopTrapFocus) and exclude it from the affected assertions. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The MUI docs render `--` as literal double hyphens (the convention is the em-dash, used in 225 files vs 2). Replace the one remaining `--` sentence separator in the data-* section. Co-Authored-By: Claude Opus 4.8 <[email protected]>
The three default tab components (DateTimePickerTabs, DateTimeRangePickerTabs, TimeRangePickerTabs) now rest-spread `...other` onto their styled root, so `data-*` passed via slotProps.tabs reaches the DOM. Re-widen `tabs` and drop its assertion exclusions. Adds a runtime test (StaticDateTimePicker) asserting a data-* attribute on the tabs slot lands on the DOM root -- the DOM-forwarding coverage the type promise was missing. (aria-* isn't asserted there because MUI Tabs routes aria-label to its inner tablist, not the root.) Co-Authored-By: Claude Opus 4.8 <[email protected]>
siriwatknp
left a comment
There was a problem hiding this comment.
👍 Huge thanks for the effort
Fixes #22126.
Fixes #21817.
Summary
Allows
data-*attributes on MUI XslotPropswithout anas anycast. It is opt-in and typed: MUI X inherits the augmentableDataAttributesOverrideshook that ships in@mui/material/@mui/utilsv9.2 (mui/material-ui#48554, merged), so a consumer augments one module and it applies to both Material UI and every supported MUI X slot. Without augmentation nothing changes --data-*onslotPropsstays a TS error, matching today's behavior.aria-*is untouched by this PR -- the augmentation adds onlydata-*keys. It already type-checks on the DOM-backed slots, whose inner type inheritsReact.AriaAttributesvia an element or component base (HTMLAttributes,SVGAttributes,ButtonBaseProps,PopperProps, ...). A few non-DOM controller slots (for example the desktopFocusTrap) do not inherit it, but those are excluded from the widening anyway (see Coverage).What consumers see
Augment
@mui/material/utilsonce, anywhere reachable from the project's tsconfig:Then
data-*type-checks on any supported MUI X slot, including slots that rejected it before:The same single augmentation covers Material UI slots too. Full docs: the Material UI TypeScript guide (https://mui.com/material-ui/guides/typescript/#allowing-data-attributes-on-slotprops).
How it works
WithDataAttributesfrom@mui/utils/types(shipped in 9.2) and widens each supported slot prop type with it.WithDataAttributes<T> = T | (T & DataAttributesOverrides)is a union, so pre-typed values stay assignable and the default (emptyDataAttributesOverrides) makes the widening a no-op until a consumer augments.@mui/utils'sSlotComponentPropsdirectly, which now applies the sameWithDataAttributeswidening. MUI X's ownSlotComponentPropsFromPropsis used only where a slot is typed from a bare props type (PopperProps,TransitionProps, etc.), whichSlotComponentPropscannot express since its first parameter must be aReact.ElementType.@mui/utilsDataAttributesOverridesinterface directly, so there is a single augmentation surface rather than a competing MUI-X-only one.Composition with slot module augmentation (#22519)
master shipped per-slot
*PropsOverridesinterfaces (#22519). This PR composes with them rather than replacing: a slot that carries a per-slot overrides interface becomesWithDataAttributes<Partial<X> & XxxPropsOverrides>, so per-slot overrides and the globaldata-*switch both work (slots without an overrides interface are simplyWithDataAttributes<Partial<X>>). Whether to keep both mechanisms long-term is a follow-up decision (see below).Requirements
Requires
@mui/material/@mui/utils>= 9.2 for the@mui/material/utilsaugmentation path (already on master via the catalog bump). Consumers on older@mui/material, or packages that do not depend on Material UI at all (such as Chat Headless), can augment@mui/utils/typesdirectly instead; that reaches MUI X slots only. MUI X ships@mui/utils>= 9.2 as its own dependency, so the slot types always resolve. On that fallback path a Material UI older than 9.2 (e.g. v7) has noDataAttributesOverridesto re-export and ships its own@mui/utilsthat never dedupes with MUI X's v9.2, so on v7 the@mui/material/utilspath does not work at all. Instead, augment@mui/utils/typesdirectly AND add@mui/utils(^9.2.0) as a direct dependency, so your augmentation resolves to the same v9.2 copy MUI X imports (confirmed with a Material 7 repro under pnpm and npm); upgrading Material UI to 9.2+ avoids the split entirely.Coverage
Every slot-bearing package outside Data Grid is widened:
x-charts,x-charts-pro,x-charts-premium,x-date-pickers,x-date-pickers-pro,x-tree-view,x-tree-view-pro,x-chat,x-chat-headless. Several carry no source change of their own --x-tree-view,x-tree-view-pro, andx-chat-headlessinherit the widening transitively (through their widened base*SlotProps, or through@mui/utils'sSlotComponentPropsfor element-type slots) and are verified by their specs.A few individual slots are deliberately left un-widened because their default component never forwards arbitrary props to the DOM:
xAxis/yAxisandscatterin Charts (ChartsAxisnever spreads the axis slots, and no default scatter renderer spreads unknown props onto an element) anddesktopTrapFocusin the Pickers (FocusTraprenders no element and rejects unknown props viaexactProp). This is the same type/runtime-forwarding reason Data Grid is excluded; the per-package specs mark these exclusions explicitly.Data Grid is intentionally excluded. An earlier revision of this PR widened every grid slot as well, but that over-promised: many default grid slots do not forward arbitrary props at runtime, so a blanket type widening would have type-checked
data-*that silently never renders. After review (@MBilalShafi), the grid widening was reverted and deferred to a follow-up (see below). Data Grid keeps its existingdata-*support in the meantime (root/main index signatures, per-slot*PropsOverrides).Two layers of test coverage back this:
src/tests/slotDataAttributes.spec.ts) asserting every slot of every exported component and provider*SlotPropsa consumer can passslotPropsto acceptsdata-*onceDataAttributesOverridesis augmented -- including nested/plot-level and standalone components (e.g.ChatMessage) that declare their own slot props -- so a regression names the offending slot. Callback-form slots additionally assert that the callback return is widened independently -- both the sharedSlotComponentPropsFromPropshelper (inx-internals) and the hand-written Chat callbacks (messageActions,ChatMessage.actions) -- so wrapping the whole object-or-callback union cannot slip through.describeSlotsConformancegains a check thatdata-*andaria-*passed viaslotPropsactually reach the slot's DOM element (wired forTreeItem); the Pickerstabsslot additionally has a focuseddata-*forwarding test.The
data-*augmentation is documented in the shared "Custom slots" TypeScript guide, with per-package examples and the v9.2 /@mui/utils/typesfallback callout.Follow-ups
data-*: re-introduce grid-widedata-*backed by real runtime forwarding plus tests, rather than a blanket type widening. This is the deferred half of this work, agreed with @MBilalShafi.describeSlotsConformancedata-*/aria-*check beyondTreeItemto the other packages' slots, so runtime forwarding is verified alongside the compile-time acceptance specs. Wiringdata-*forwarding for the currently-excluded slots (xAxis/yAxis) can then re-enable their widening.WithDataAttributesswitch and the per-slot*PropsOverridesinterfaces ([charts] Support module augmentation for slots #22519) long-term, or converge on one -- a call for the Charts team.Verification
pnpm typescriptpasses across all affected packages.DataAttributesOverridesmakesdata-*assignable on every slot of every supported package's*SlotProps(enforced by the per-package compile-time specs); without the augmentation slots reject it.pnpm eslintandpnpm prettierare clean on the changed files.Related
DataAttributesOverridesaugmentation for slot props material-ui#48554 (merged) -- adds theDataAttributesOverrides/WithDataAttributeshook to@mui/utils, re-exports it from@mui/material/utils, and documents it in the Material UI TypeScript guide.