Skip to content

fix(react-provider): validate serialized theme tokens - #36713

Draft
Paul Mardling (PaulGMardling) wants to merge 3 commits into
microsoft:masterfrom
PaulGMardling:fix/theme-css-hardening
Draft

fix(react-provider): validate serialized theme tokens#36713
Paul Mardling (PaulGMardling) wants to merge 3 commits into
microsoft:masterfrom
PaulGMardling:fix/theme-css-hardening

Conversation

@PaulGMardling

@PaulGMardling Paul Mardling (PaulGMardling) commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Previous Behavior

createCSSRuleFromTheme serialized theme entries without verifying that each token name and value remained within one CSS custom-property declaration. A malformed entry could invalidate declarations that followed it, and the theming documentation did not describe the trust boundary for dynamic theme data.

New Behavior

createCSSRuleFromTheme now accepts CSS escape-aware custom-property names and structurally contains each token value before serialization. Valid CSS syntax remains unchanged, including colors, font-family fallbacks, quoted strings, comments, nested functions, gradients, calculations, case-insensitive escaped URL forms, other escapes, and multiline whitespace. Angle-bracket canonicalization consumes backslash runs linearly while preserving CSS escape parity. Malformed lexical contexts are repaired at the token boundary so later declarations remain independently parseable; unsupported token names are omitted with a development-only warning that identifies the name without including its value.

Regression coverage exercises exported themes, escaped custom-token names, compatibility syntax, long backslash runs, malformed-entry isolation, client stylesheet insertion, and server-rendered style output. The theming documentation clarifies that applications must validate dynamic data against an application-specific schema before constructing a theme.

Validation

  • yarn nx run react-provider:test (109 tests, 14 snapshots)
  • yarn nx run react-provider:lint
  • yarn nx run react-provider:type-check
  • yarn nx run react-provider:format:check
  • yarn nx run react-provider:build
  • yarn beachball check
  • FluentProvider bundle comparison: 21.913 kB minified / 8.415 kB gzip, +1.827 kB / +714 B (+9% / +9%) against master; below the 10% threshold

Related Issue(s)

  • None.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Pull request demo site: URL

@@ -112,6 +112,10 @@ export const customDarkTheme = createDarkTheme(customBrandRamp);

@github-actions github-actions Bot Sep 8, 2026

Copy link
Copy Markdown

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/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 404 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 617 Changed
vr-tests-react-components/Positioning.Positioning end.chromium.png 887 Changed
vr-tests-react-components/ProgressBar converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 54 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 2 Changed
vr-tests-react-components/TagPicker 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.chromium.png 658 Changed
vr-tests-react-components/TagPicker.disabled - High Contrast.disabled input hover.chromium.png 1319 Changed
vr-tests-react-components/TagPicker.disabled - RTL.chromium.png 635 Changed

There were 3 duplicate changes discarded. Check the build logs for more information.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-components
react-components: Button, FluentProvider & webLightTheme
67.471 kB
19.465 kB
69.298 kB
20.233 kB
1.827 kB
768 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
227.136 kB
68.41 kB
228.963 kB
69.159 kB
1.827 kB
749 B
react-components
react-components: FluentProvider & webLightTheme
40.694 kB
13.549 kB
42.521 kB
14.273 kB
1.827 kB
724 B
react-components
react-components: entire library
1.283 MB
322.086 kB
1.285 MB
322.75 kB
1.831 kB
664 B
react-provider
FluentProvider
20.086 kB
7.701 kB
21.913 kB
8.415 kB
1.827 kB
714 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-avatar
Avatar
48.256 kB
15.276 kB
react-avatar
AvatarGroup
17.269 kB
6.89 kB
react-avatar
AvatarGroupItem
61.24 kB
19.139 kB
react-breadcrumb
@fluentui/react-breadcrumb - package
103.667 kB
29.079 kB
react-card
Card - All
94.104 kB
27.352 kB
react-card
Card
86.7 kB
25.469 kB
react-card
CardFooter
12.663 kB
5.028 kB
react-card
CardHeader
15.21 kB
5.877 kB
react-card
CardPreview
12.74 kB
5.142 kB
react-charts
AreaChart
406.902 kB
127.183 kB
react-charts
DeclarativeChart
758.446 kB
222.689 kB
react-charts
DonutChart
317.733 kB
97.977 kB
react-charts
FunnelChart
309.133 kB
94.775 kB
react-charts
GanttChart
390.032 kB
121.663 kB
react-charts
GaugeChart
317.083 kB
97.402 kB
react-charts
GroupedVerticalBarChart
398.115 kB
124.454 kB
react-charts
HeatMapChart
392.172 kB
122.702 kB
react-charts
HorizontalBarChart
297.409 kB
90.473 kB
react-charts
HorizontalBarChartWithAxis
63 B
83 B
react-charts
Legends
236.596 kB
71.053 kB
react-charts
LineChart
418.251 kB
130.314 kB
react-charts
PolarChart
345.588 kB
108.005 kB
react-charts
SankeyChart
213.797 kB
68.722 kB
react-charts
ScatterChart
397.575 kB
124.369 kB
react-charts
Sparkline
80.888 kB
26.864 kB
react-charts
VerticalBarChart
434.566 kB
129.339 kB
react-charts
VerticalStackedBarChart
404.13 kB
125.845 kB
react-checkbox
Checkbox
30.781 kB
10.914 kB
react-color-picker
ColorArea
31.032 kB
11.706 kB
react-color-picker
ColorPicker
16.049 kB
6.438 kB
react-color-picker
ColorSlider
26.093 kB
10.232 kB
react-combobox
Combobox (including child components)
139.318 kB
45.067 kB
react-combobox
Dropdown (including child components)
139.083 kB
44.818 kB
react-components
react-components: all base hooks
217.893 kB
68.338 kB
react-datepicker-compat
DatePicker Compat
217.447 kB
64.407 kB
react-dialog
Dialog (including children components)
90.984 kB
28.254 kB
react-field
Field
22.311 kB
8.312 kB
react-headless-components-preview
react-headless-components-preview: entire library
241.736 kB
68.14 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
54.012 kB
17.756 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
36.073 kB
12.006 kB
react-input
Input
26.24 kB
8.643 kB
react-list
List
75.545 kB
23.408 kB
react-list
ListItem
99.06 kB
30.273 kB
react-menu
Menu (including children components)
160.788 kB
51.11 kB
react-menu
Menu (including selectable components)
163.954 kB
51.739 kB
react-message-bar
MessageBar (all components)
25.293 kB
8.946 kB
react-overflow
hooks only
10.203 kB
3.694 kB
react-persona
Persona
55.209 kB
17.22 kB
react-popover
Popover
126.6 kB
40.873 kB
react-portal
Portal
13.844 kB
5.392 kB
react-portal-compat
PortalCompatProvider
5.341 kB
2.146 kB
react-positioning
usePositioning
28.985 kB
10.198 kB
react-positioning
useSafeZoneArea
13.398 kB
5.365 kB
react-progress
ProgressBar
20.217 kB
7.836 kB
react-radio
Radio
28.166 kB
9.157 kB
react-radio
RadioGroup
13.931 kB
5.613 kB
react-select
Select
26.109 kB
9.403 kB
react-slider
Slider
33.405 kB
11.51 kB
react-spinbutton
SpinButton
33.661 kB
11.013 kB
react-swatch-picker
@fluentui/react-swatch-picker - package
93.186 kB
27.608 kB
react-switch
Switch
33.402 kB
10.566 kB
react-table
DataGrid
147.809 kB
43.977 kB
react-table
Table (Primitives only)
38.042 kB
12.69 kB
react-table
Table as DataGrid
119.68 kB
33.755 kB
react-table
Table (Selection only)
66.557 kB
18.957 kB
react-table
Table (Sort only)
65.22 kB
18.58 kB
react-tag-picker
@fluentui/react-tag-picker - package
174.763 kB
54.56 kB
react-tags
InteractionTag
13.629 kB
5.386 kB
react-tags
Tag
29.542 kB
9.338 kB
react-tags
TagGroup
70.806 kB
21.811 kB
react-teaching-popover
TeachingPopover
101.77 kB
32.204 kB
react-textarea
Textarea
24.606 kB
8.912 kB
react-timepicker-compat
TimePicker
142.037 kB
46.44 kB
react-toast
Toast (including Toaster)
91.488 kB
28.541 kB
react-tooltip
Tooltip
54.69 kB
19.443 kB
react-tree
FlatTree
136.693 kB
40.821 kB
react-tree
PersonaFlatTree
138.556 kB
41.361 kB
react-tree
PersonaTree
134.637 kB
40.143 kB
react-tree
Tree
132.78 kB
39.638 kB
🤖 This report was generated against f51ba35768877b1e01346ac76e3ad4af7689a5eb

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant