Skip to content

fix(react-provider): escape theme delimiters instead of omitting valid nested theme values - #36985

Draft
Victor Genaev (mainframev) wants to merge 1 commit into
microsoft:masterfrom
mainframev:fix/react-provider-nested-theme-token-values
Draft

Victor Genaev (mainframev) wants to merge 1 commit into
microsoft:masterfrom
mainframev:fix/react-provider-nested-theme-token-values

Conversation

@mainframev

@mainframev Victor Genaev (mainframev) commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Previous Behavior

#36830 replaced escaping in createCSSRuleFromTheme (used by FluentProvider) with blocklists plus a leftover-delimiter check. That check only discards flat (...) groups and top-level strings, so valid values with nested functions or quoted arguments are silently omitted from the provider style tag:

  • var(--custom-color, rgb(0 0 0)), var(--a, var(--b)), var(--font, "Segoe UI")
  • calc(var(--size) * 2), clamp(1rem, calc(var(--scale, 1) * 2vw), 3rem)
  • color-mix(in srgb, var(--a) 50%, transparent), oklch(from var(--a) l c h / 0.5)

The built-in themes only use flat values, so no test in this repo caught it. Downstream, Microsoft 365 Copilot (Bebop) derives hover and pressed colors with relative color syntax, for example calc(l - 0.03 * clamp(1,(.5 - l)/.1,3)) c h / calc(alpha + 0.04) and oklch(from var(--token) var(--interactionStandardHover)). With @fluentui/react-provider@9.22.22 those tokens disappear and the dependent colors fall back to transparent.

New Behavior

Go back to escaping, as in the first revision of #36830. Token names and values are serialized unchanged except for <, >, {, }, ; and \, which become CSS escapes. That keeps every token inside its own declaration, the generated rule and the surrounding <style> tag, which is what the fix for MSRC 129538 required. Quotes and parentheses are no longer checked, so valid nested values are preserved.

  • < and >: the value can't close the <style> tag (SSR).
  • { and }: the value can't close the rule or open a new one.
  • ;: the value can't add declarations to the provider rule.
  • \: the value can't escape the separator that follows it, for example a trailing red\.

Selector escaping (#36464, backslash parity) is unchanged.

Tradeoff, accepted deliberately: values are no longer validated. A malformed value, such as an unclosed string, comment or function ("unfinished, calc(1px, red /* x), can absorb the later tokens in the same provider rule. This is the token-isolation class from MSRC 138890 that #36713/#36830 addressed by validation. The value still cannot leave the declaration list, the rule or the <style> tag, so the markup/rule breakout from MSRC 129538 stays fixed. Theme data from untrusted sources still needs an application-specific schema, as the docs say. Development warnings and omit-and-warn behavior are removed. Docs (Theming.mdx, createCSSRuleFromThemeDescription.md) are updated.

Compared with the first revision of #36830 (3c21f40), this also escapes ; (as its second commit did) and keeps the current backslash-parity selector escaping from #36464 instead of escaping \ in selectors.

Validation

  • jest src/components/FluentProvider (react-provider): 8 suites, 108 tests passed. This includes the jsdom CSSOM check that red; } .other { color: transparent stays a single rule with no injected color and later tokens intact, and the SSR check that markup values produce one <style> element and no <script>.
  • eslint and prettier --check on the changed files: passed.

Related Issue(s)

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-components
react-components: Button, FluentProvider & webLightTheme
68.214 kB
19.863 kB
67.948 kB
19.725 kB
-266 B
-138 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
223.278 kB
67.379 kB
223.012 kB
67.239 kB
-266 B
-140 B
react-components
react-components: FluentProvider & webLightTheme
41.444 kB
13.893 kB
41.178 kB
13.771 kB
-266 B
-122 B
react-components
react-components: entire library
1.283 MB
322.23 kB
1.283 MB
322.081 kB
-275 B
-149 B
react-provider
FluentProvider
20.836 kB
8.049 kB
20.57 kB
7.927 kB
-266 B
-122 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-avatar
Avatar
49.166 kB
15.658 kB
react-avatar
AvatarGroup
14.126 kB
5.692 kB
react-avatar
AvatarGroupItem
58.643 kB
18.172 kB
react-breadcrumb
@fluentui/react-breadcrumb - package
103.23 kB
29.071 kB
react-calendar-preview
Calendar
159.337 kB
46.707 kB
react-card
Card - All
93.139 kB
27.125 kB
react-card
Card
85.734 kB
25.241 kB
react-card
CardFooter
13.021 kB
5.17 kB
react-card
CardHeader
15.568 kB
6.015 kB
react-card
CardPreview
13.099 kB
5.282 kB
react-charts
AreaChart
402.942 kB
125.854 kB
react-charts
DeclarativeChart
756.223 kB
221.658 kB
react-charts
DonutChart
313.421 kB
96.687 kB
react-charts
FunnelChart
304.836 kB
93.372 kB
react-charts
GanttChart
385.85 kB
120.344 kB
react-charts
GaugeChart
313.382 kB
96.231 kB
react-charts
GroupedVerticalBarChart
394.163 kB
123.176 kB
react-charts
HeatMapChart
388.057 kB
121.294 kB
react-charts
HorizontalBarChart
293.131 kB
89.081 kB
react-charts
HorizontalBarChartWithAxis
63 B
83 B
react-charts
Legends
232.566 kB
69.71 kB
react-charts
LineChart
414.183 kB
129.026 kB
react-charts
PolarChart
341.319 kB
106.588 kB
react-charts
SankeyChart
209.067 kB
67.203 kB
react-charts
ScatterChart
393.396 kB
123.028 kB
react-charts
Sparkline
79.774 kB
26.585 kB
react-charts
VerticalBarChart
430.382 kB
128.028 kB
react-charts
VerticalStackedBarChart
399.949 kB
124.471 kB
react-checkbox
Checkbox
31.71 kB
11.289 kB
react-color-picker
ColorArea
27.778 kB
10.463 kB
react-color-picker
ColorPicker
12.913 kB
5.244 kB
react-color-picker
ColorSlider
22.818 kB
9.023 kB
react-combobox
Combobox (including child components)
137.042 kB
44.29 kB
react-combobox
Dropdown (including child components)
135.723 kB
43.711 kB
react-components
react-components: all base hooks
219.615 kB
68.539 kB
react-datepicker-compat
DatePicker Compat
216.641 kB
64.398 kB
react-dialog
Dialog (including children components)
86.865 kB
26.93 kB
react-field
Field
23.331 kB
8.683 kB
react-headless-components-preview
react-headless-components-preview: entire library
269.204 kB
80.184 kB
react-headless-components-preview
react-headless-components-preview: Calendar
42.084 kB
13.907 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
50.254 kB
16.375 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
33.766 kB
11.09 kB
react-input
Input
26.594 kB
8.782 kB
react-list
List
71.03 kB
21.795 kB
react-list
ListItem
95.718 kB
29.156 kB
react-menu
Menu (including children components)
156.704 kB
49.751 kB
react-menu
Menu (including selectable components)
159.869 kB
50.385 kB
react-message-bar
MessageBar (all components)
26.349 kB
9.34 kB
react-overflow
hooks only
10.203 kB
3.696 kB
react-persona
Persona
56.119 kB
17.602 kB
react-popover
Popover
122.078 kB
39.348 kB
react-portal
Portal
13.959 kB
5.447 kB
react-portal-compat
PortalCompatProvider
5.198 kB
2.104 kB
react-positioning
usePositioning
29.156 kB
10.251 kB
react-positioning
useSafeZoneArea
13.461 kB
5.425 kB
react-progress
ProgressBar
20.566 kB
7.98 kB
react-radio
Radio
28.38 kB
9.229 kB
react-radio
RadioGroup
14.286 kB
5.757 kB
react-range-slider-preview
RangeSlider
39.889 kB
13.475 kB
react-select
Select
27.184 kB
9.835 kB
react-slider
Slider
33.727 kB
11.61 kB
react-spinbutton
SpinButton
34.735 kB
11.44 kB
react-swatch-picker
@fluentui/react-swatch-picker - package
89.202 kB
26.275 kB
react-switch
Switch
34.378 kB
10.933 kB
react-table
DataGrid
143.634 kB
42.543 kB
react-table
Table (Primitives only)
38.903 kB
13.017 kB
react-table
Table as DataGrid
119.146 kB
33.785 kB
react-table
Table (Selection only)
67.409 kB
19.301 kB
react-table
Table (Sort only)
66.072 kB
18.926 kB
react-tag-picker
@fluentui/react-tag-picker - package
171.164 kB
53.355 kB
react-tags
InteractionTag
13.986 kB
5.531 kB
react-tags
Tag
30.614 kB
9.763 kB
react-tags
TagGroup
69.63 kB
21.545 kB
react-teaching-popover
TeachingPopover
97.048 kB
30.59 kB
react-textarea
Textarea
24.959 kB
9.052 kB
react-timepicker-compat
TimePicker
139.754 kB
45.561 kB
react-toast
Toast (including Toaster)
90.483 kB
28.281 kB
react-tooltip
Tooltip
54.989 kB
19.528 kB
react-tree
FlatTree
132.701 kB
39.468 kB
react-tree
PersonaFlatTree
134.558 kB
39.973 kB
react-tree
PersonaTree
130.645 kB
38.769 kB
react-tree
Tree
128.788 kB
38.287 kB
🤖 This report was generated against 6d3e9d686434d69ac8fc9ca4c4a93f3afaa3d676

@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

@@ -7,10 +7,11 @@ underscores are rejected in names. Values must be strings or finite numbers.
Values containing `<`, `>`, `{`, `}`, `;`, `\`, `[`, `]`, `!`, the CSS comment opener `/*`, or non-whitespace control

@github-actions github-actions Bot Oct 23, 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/Avatar Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Avatar Converged.badgeMask.normal.chromium.png 6 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 33 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 767 Changed
vr-tests-react-components/ProgressBar converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 1 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 69 Changed
vr-tests-react-components/TagPicker 2 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 - RTL.disabled input hover.chromium.png 635 Changed

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

@mainframev Victor Genaev (mainframev) changed the title fix(react-provider): preserve theme tokens with balanced nested parentheses fix(react-provider): escape theme delimiters instead of omitting valid nested theme values Oct 23, 2026
@mainframev Victor Genaev (mainframev) changed the title fix(react-provider): escape theme delimiters instead of omitting valid nested theme values fix(react-provider): preserve theme tokens with balanced nested parentheses Oct 23, 2026
@mainframev Victor Genaev (mainframev) changed the title fix(react-provider): preserve theme tokens with balanced nested parentheses fix(react-provider): escape theme delimiters instead of omitting valid nested theme values Oct 23, 2026
…d nested theme values

createCSSRuleFromTheme omitted theme values whose parentheses were nested or
whose function arguments were quoted, so valid tokens such as
var(--a, rgb(0 0 0)) and oklch(from var(--a) l c h / 0.5) were silently
dropped from the FluentProvider style tag.

Escape <, >, {, }, ; and backslash in token names and values instead, and
serialize everything else unchanged. Each token stays inside its declaration,
the rule and the style tag. A malformed value can still affect later tokens in
the same provider rule; this tradeoff is accepted to avoid a CSS syntax
validator.
@mainframev
Victor Genaev (mainframev) force-pushed the fix/react-provider-nested-theme-token-values branch from c14de9f to c4c5101 Compare October 23, 2026 14:58

This branch has not been deployed

No deployments
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