Repository navigation
Theming: Dynamic Theme Building #4264
Copy link
Copy link
Closed
Description
Summary
Support both Flex-first and Fluent-theme authoring without eagerly constructing
both token models. Add a Flex-value context contract, resolve Flex tokens only
for ThemeState consumers, and construct the legacy Fluent token set only when
legacy hooks request it.
Today every theme must start as a full Fluent Theme and useFlexTokens
ignores context. This task adds the second authoring path while preserving lazy,
shared theme state.
Goal
Let a theme be defined either in Flex tokens or in Fluent tokens, construct the
Fluent token set only when a legacy theme hook is used, ensure Flex tokens only
when the new ThemeState routines are used, and add a context type for
supplying Flex values directly.
Stage
Stage 1 - Beta foundations.
Why it matters
- Observed.
useFlexTokenscurrently ignores context entirely and always
returnsdefaultFlexTokens
(useFlexTokens.ts),
which was landed as an explicitly temporary implementation in
PR #4156.
It is imported by 16 component test files under
packages/agentic/components/src/components
as the expected-value oracle, so those tests currently assert against defaults
rather than against the theme in context. - Observed. A theme author has no way to supply Flex values. The only theme
context isReact.createContext<Theme>(undefined)in
theming/context.ts,
andThemerequires the full Fluent shape:colors,typography,
components,shadows,spacing, andhost
(Theme.types.ts). - Inferred. Because
ThemeStateis derived from aTheme, any Flex-first
theme must currently be expressed as a Fluent theme first, which forces the
Fluent token set to be constructed even for consumers that only read Flex
tokens.
Observed current state
- Observed.
useThemeStatereadsThemefrom context and lazily builds one
ThemeStateperThemeobject, storing it on the theme under a
non-enumerable symbol so all consumers of that theme share one identity
(useThemeState.ts).
When context is empty it returns a module-leveldefaultThemeState. - Observed.
ThemeStateis{ tokens: FlexTokens; highContrast: boolean; themeStyles: Record<symbol, unknown> }, andthemedStyleSheetFactorycaches
oneStyleSheet.createresult per component perThemeState. - Observed.
flexTokensFromTheme(theme)merges a projection of the theme
throughflex-from-theme.json
overnonFluentFlexTokens
(flexTokensFromTheme.ts),
landed in PR #4186. - Observed.
FlexTokensis a nine-group object type --color,shadow,
fontWeight,fontFamily,fontSize,lineHeight,borderRadius,
spacing,strokeWidth-- wherecolorisSemanticColorTokenValuesplus
hoverandpressedoverride maps
(flex.types.ts). - Observed.
ThemeProvideraccepts aThemeReference, subscribes to its
change notifications, and pushes the resolvedThemeinto context
(ThemeProvider.tsx);
ThemeReferenceresolves recipes lazily and invalidates listeners
(themeReference.ts). - Observed. The legacy hooks that consume the Fluent
Themelive in
packages/framework/use-tokensand
packages/framework/use-styling,
and platform themes such as
createAppleTheme.macos.ts
construct a full FluentThemeeagerly inside aThemeReferencerecipe. - Observed.
flex-token-map.yamlrecords thatflex-from-theme.jsonand
nonFluentFlexTokensmust be kept synchronized by hand, and that unmapped
hover or pressed fallbacks are only included when their rest destination is
also unmapped
(flex-token-map.yaml).
Upstream Flex authoring model (x3-design/fluent-design at d334acf)
- Observed. x3 already has the Flex-first authoring model this task adds.
createTheme(overrides?)indev/web/flex-themes/createTheme.d.tsaccepts
{ brand, primitives, generics }and returns aThemeResultcarrying both
resolved modes plus atoCss(mode?, selector?)serializer. Prebuilt
lightThemeanddarkThemeconstants are exported from the same module. - Observed. Override granularity is per token with an optional per-mode
shape: a scalar applies to both modes, and{ light, dark }sets them
separately with either side omittable. Overrides are layered in a documented
order --brand, thenprimitives, thengenericson top of resolved values. - Observed. A theme is a flat
Record<string, string | number>keyed by
--gnrc-*token name, not a nested object. - Observed. Brand input is a small structured record --
{ color, step?, preset?, darkMode? }-- from which a full ramp is generated,
rather than a full palette the author must supply. - Inferred. The FURN Flex-value context type has a directly comparable
upstream precedent for the partial-override question below: upstream accepts
sparse overrides layered over resolved defaults, and expresses per-mode values
inline rather than requiring two complete theme objects.
Scope
- Define the Flex-value context type: a context that carries Flex token values
(in full or as a partial layered over generated defaults) without requiring a
FluentTheme. - Define how the two authoring models compose when both are present, including
precedence and provider nesting. - Make
useFlexTokensresolve from context, preserving the identity guarantee
thatuseThemeStatealready provides sothemedStyleSheetFactorycaching
stays valid. - Make
ThemeStateconstruction lazy per token set, so a Flex-authored theme
does not build the Fluent token set and a Fluent-authored theme does not build
Flex tokens until aThemeStateroutine asks for them. - Keep the legacy hooks working against a Fluent
Theme, constructing it only
when those hooks are used. - Update the 16 component test files that use
useFlexTokensas an oracle so
they assert against the theme under test.
Out of scope
- Generating the default value sets; see
Default Values Codegen. - Adding resolved color-scheme values to
ThemeState; see
System Appearance Handling. - Deriving hover and pressed values from rest values; see
Runtime Color Utilities. - Authoring a platform theme in the new model; see
Apple Theme.
Deliverables
- A Flex-value context type and its provider, exported from the design package.
- A context-aware
useFlexTokensthat replaces the temporary implementation. - Lazy construction of the Fluent and Flex token sets, wired through
useThemeState. - A documented precedence model for Flex-authored and Fluent-authored themes,
including nesting. - Tests covering: Flex-only theme, Fluent-only theme, both present, neither
present, andThemeStateidentity stability across re-renders. - Updated component tests and changesets.
Acceptance criteria
- A theme can be supplied as Flex token values and consumed by
useThemeStateanduseFlexTokenswithout constructing a FluentTheme. -
useFlexTokensreturns values from context when a theme is present and
falls back to the generated defaults only when no theme is in context. - The Fluent token set is not constructed for a Flex-authored theme unless a
legacy hook consumes it, verified by a test that observes construction. - Flex tokens are not constructed until a
ThemeStateroutine requests
them, verified by a test. -
ThemeStateidentity remains stable for a given theme across re-renders,
andthemedStyleSheetFactorystill creates each style sheet once, as
asserted inuseThemeState.test.ts. - Precedence between a Flex provider and a Fluent
ThemeProvider, including
nesting, is covered by tests. - Existing agentic components render unchanged under an unchanged Fluent
theme; component snapshots and assertions still pass. -
yarn build,yarn lage test, andyarn lage lintpass at the
repository root, and changesets are present.
Dependencies and ordering
- Should follow Package Consolidation so the new
context type is published from its final submodule. - Blocks Default Values Codegen: the generated
default shape must match the context type defined here. - Blocks System Appearance Handling, which adds fields
to theThemeStateproduced by this task. - Blocks Apple Theme, which is authored in the Flex model.
Risks and open decisions
- Open decision. Whether the Flex context carries a complete
FlexTokens
object or a deep partial merged over generated defaults. Observed:
FlexTokensrequires every group and every token, anddefaultTokens.ts
already layersnonFluentFlexTokensunder a fuller default set, so a partial
model has precedent in the repository. Observed: upstreamcreateTheme
also takes sparse overrides layered over resolved defaults. - Open decision. Whether a FURN Flex theme carries one appearance or both,
as upstream'sThemeResultdoes. Observed:ThemeStatetoday is derived
from a single already-resolvedTheme, so carrying both modes would be a new
shape; this interacts directly with
System Appearance Handling. - Open decision. Whether per-mode override values use upstream's inline
{ light, dark }shape or FURN's existing pattern of separate resolved themes. - Open decision. Whether Flex and Fluent providers may be nested, and which
wins. Inferred: without a rule, a component tree could read Flex tokens
from one ancestor and Fluent-derived tokens from another and render
inconsistently. - Open decision. Whether the caching mechanism for a Flex-authored theme
reuses the symbol-on-theme-object approach inuseThemeStateor a different
mechanism, since a plain Flex value object may be frozen or shared. - Risk. The 16 component test files that call
useFlexTokensas an oracle
will change behavior once it becomes context-aware; they must be updated in
the same change to avoid masking regressions. - Risk. Laziness that is implemented with mutable memo state on a shared
theme object can leak across themes.useThemeStatealready guards this by
giving eachThemeStateits ownthemeStylesregistry.
Evidence and references
packages/agentic/design/src/useThemeState.ts:ThemeState, caching, and style sheet factory.packages/agentic/design/src/useThemeState.test.ts: identity and caching guarantees to preserve.packages/agentic/design/src/tokens/useFlexTokens.ts: temporary context-free implementation.packages/agentic/design/src/tokens/flexTokensFromTheme.tsandflexTokensFromTheme.test.ts: Fluent-to-Flex projection.packages/agentic/design/src/tokens/flex.types.ts: theFlexTokenscontract.packages/agentic/design/src/theming/context.ts,ThemeProvider.tsx,themeReference.ts: existing context and provider model.packages/agentic/design/src/tokens/mappings/flex-token-map.yaml: mapping provenance and synchronization rules.- microsoft/fluentui-react-native@
1eef74e: PR Temporary implementation ofuseFlexTokenswith a default set of tokens #4156, temporaryuseFlexTokens. - microsoft/fluentui-react-native@
ea738f0: PR Ensure that useThemeState will work with existing furn themes #4186,useThemeStateover existing FURN themes. - x3
dev/web/flex-themes/createTheme.d.ts:ThemeOverrides,BrandInput,ThemeResult, and the per-mode override shape. - x3
dev/web/flex-themes/index.d.ts: the package's public theme surface. - x3
dev/web/flex-themes/README.md: factory usage, override layering, and prebuilt light and dark themes.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
[-]Dynamic Theme Building[/-][+]Theming: Dynamic Theme Building[/+]