You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.useFlexTokens currently ignores context entirely and always
returns defaultFlexTokens
(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 is React.createContext<Theme>(undefined) in theming/context.ts,
and Theme requires the full Fluent shape: colors, typography, components, shadows, spacing, and host
(Theme.types.ts).
Inferred. Because ThemeState is derived from a Theme, 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.useThemeState reads Theme from context and lazily builds one ThemeState per Theme object, 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-level defaultThemeState.
Observed.ThemeState is { tokens: FlexTokens; highContrast: boolean; themeStyles: Record<symbol, unknown> }, and themedStyleSheetFactory caches
one StyleSheet.create result per component per ThemeState.
Observed.FlexTokens is a nine-group object type -- color, shadow, fontWeight, fontFamily, fontSize, lineHeight, borderRadius, spacing, strokeWidth -- where color is SemanticColorTokenValues plus hover and pressed override maps
(flex.types.ts).
Observed.ThemeProvider accepts a ThemeReference, subscribes to its
change notifications, and pushes the resolved Theme into context
(ThemeProvider.tsx); ThemeReference resolves recipes lazily and invalidates listeners
(themeReference.ts).
Observed.flex-token-map.yaml records that flex-from-theme.json and nonFluentFlexTokens must 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?) in dev/web/flex-themes/createTheme.d.ts accepts { brand, primitives, generics } and returns a ThemeResult carrying both
resolved modes plus a toCss(mode?, selector?) serializer. Prebuilt lightTheme and darkTheme constants 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, then primitives, then generics on 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
Fluent Theme.
Define how the two authoring models compose when both are present, including
precedence and provider nesting.
Make useFlexTokens resolve from context, preserving the identity guarantee
that useThemeState already provides so themedStyleSheetFactory caching
stays valid.
Make ThemeState construction 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 a ThemeState routine 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 useFlexTokens as an oracle so
they assert against the theme under test.
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 useFlexTokens that 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, and ThemeState identity stability across re-renders.
Updated component tests and changesets.
Acceptance criteria
A theme can be supplied as Flex token values and consumed by useThemeState and useFlexTokens without constructing a Fluent Theme.
useFlexTokens returns 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 ThemeState routine requests
them, verified by a test.
ThemeState identity remains stable for a given theme across re-renders,
and themedStyleSheetFactory still creates each style sheet once, as
asserted in useThemeState.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, and yarn lage lint pass 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 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: FlexTokens requires every group and every token, and defaultTokens.ts
already layers nonFluentFlexTokens under a fuller default set, so a partial
model has precedent in the repository. Observed: upstream createTheme
also takes sparse overrides layered over resolved defaults.
Open decision. Whether a FURN Flex theme carries one appearance or both,
as upstream's ThemeResult does. Observed:ThemeState today is derived
from a single already-resolved Theme, 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 in useThemeState or a different
mechanism, since a plain Flex value object may be frozen or shared.
Risk. The 16 component test files that call useFlexTokens as 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. useThemeState already guards this by
giving each ThemeState its own themeStyles registry.
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
ThemeStateconsumers, and construct the legacy Fluent token set only whenlegacy hooks request it.
Today every theme must start as a full Fluent
ThemeanduseFlexTokensignores 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
ThemeStateroutines are used, and add a context type forsupplying Flex values directly.
Stage
Stage 1 - Beta foundations.
Why it matters
useFlexTokenscurrently ignores context entirely and alwaysreturns
defaultFlexTokens(
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/componentsas the expected-value oracle, so those tests currently assert against defaults
rather than against the theme in context.
context is
React.createContext<Theme>(undefined)intheming/context.ts,and
Themerequires the full Fluent shape:colors,typography,components,shadows,spacing, andhost(
Theme.types.ts).ThemeStateis derived from aTheme, any Flex-firsttheme 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
useThemeStatereadsThemefrom context and lazily builds oneThemeStateperThemeobject, storing it on the theme under anon-enumerable symbol so all consumers of that theme share one identity
(
useThemeState.ts).When context is empty it returns a module-level
defaultThemeState.ThemeStateis{ tokens: FlexTokens; highContrast: boolean; themeStyles: Record<symbol, unknown> }, andthemedStyleSheetFactorycachesone
StyleSheet.createresult per component perThemeState.flexTokensFromTheme(theme)merges a projection of the themethrough
flex-from-theme.jsonover
nonFluentFlexTokens(
flexTokensFromTheme.ts),landed in PR #4186.
FlexTokensis a nine-group object type --color,shadow,fontWeight,fontFamily,fontSize,lineHeight,borderRadius,spacing,strokeWidth-- wherecolorisSemanticColorTokenValuesplushoverandpressedoverride maps(
flex.types.ts).ThemeProvideraccepts aThemeReference, subscribes to itschange notifications, and pushes the resolved
Themeinto context(
ThemeProvider.tsx);ThemeReferenceresolves recipes lazily and invalidates listeners(
themeReference.ts).Themelive inpackages/framework/use-tokensandpackages/framework/use-styling,and platform themes such as
createAppleTheme.macos.tsconstruct a full Fluent
Themeeagerly inside aThemeReferencerecipe.flex-token-map.yamlrecords thatflex-from-theme.jsonandnonFluentFlexTokensmust be kept synchronized by hand, and that unmappedhover 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)createTheme(overrides?)indev/web/flex-themes/createTheme.d.tsaccepts{ brand, primitives, generics }and returns aThemeResultcarrying bothresolved modes plus a
toCss(mode?, selector?)serializer. PrebuiltlightThemeanddarkThemeconstants are exported from the same module.shape: a scalar applies to both modes, and
{ light, dark }sets themseparately with either side omittable. Overrides are layered in a documented
order --
brand, thenprimitives, thengenericson top of resolved values.Record<string, string | number>keyed by--gnrc-*token name, not a nested object.{ color, step?, preset?, darkMode? }-- from which a full ramp is generated,rather than a full palette the author must supply.
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
(in full or as a partial layered over generated defaults) without requiring a
Fluent
Theme.precedence and provider nesting.
useFlexTokensresolve from context, preserving the identity guaranteethat
useThemeStatealready provides sothemedStyleSheetFactorycachingstays valid.
ThemeStateconstruction lazy per token set, so a Flex-authored themedoes not build the Fluent token set and a Fluent-authored theme does not build
Flex tokens until a
ThemeStateroutine asks for them.Theme, constructing it onlywhen those hooks are used.
useFlexTokensas an oracle sothey assert against the theme under test.
Out of scope
Default Values Codegen.
ThemeState; seeSystem Appearance Handling.
Runtime Color Utilities.
Apple Theme.
Deliverables
useFlexTokensthat replaces the temporary implementation.useThemeState.including nesting.
present, and
ThemeStateidentity stability across re-renders.Acceptance criteria
useThemeStateanduseFlexTokenswithout constructing a FluentTheme.useFlexTokensreturns values from context when a theme is present andfalls back to the generated defaults only when no theme is in context.
legacy hook consumes it, verified by a test that observes construction.
ThemeStateroutine requeststhem, verified by a test.
ThemeStateidentity remains stable for a given theme across re-renders,and
themedStyleSheetFactorystill creates each style sheet once, asasserted in
useThemeState.test.ts.ThemeProvider, includingnesting, is covered by tests.
theme; component snapshots and assertions still pass.
yarn build,yarn lage test, andyarn lage lintpass at therepository root, and changesets are present.
Dependencies and ordering
context type is published from its final submodule.
default shape must match the context type defined here.
to the
ThemeStateproduced by this task.Risks and open decisions
FlexTokensobject or a deep partial merged over generated defaults. Observed:
FlexTokensrequires every group and every token, anddefaultTokens.tsalready layers
nonFluentFlexTokensunder a fuller default set, so a partialmodel has precedent in the repository. Observed: upstream
createThemealso takes sparse overrides layered over resolved defaults.
as upstream's
ThemeResultdoes. Observed:ThemeStatetoday is derivedfrom a single already-resolved
Theme, so carrying both modes would be a newshape; this interacts directly with
System Appearance Handling.
{ light, dark }shape or FURN's existing pattern of separate resolved themes.wins. Inferred: without a rule, a component tree could read Flex tokens
from one ancestor and Fluent-derived tokens from another and render
inconsistently.
reuses the symbol-on-theme-object approach in
useThemeStateor a differentmechanism, since a plain Flex value object may be frozen or shared.
useFlexTokensas an oraclewill change behavior once it becomes context-aware; they must be updated in
the same change to avoid masking regressions.
theme object can leak across themes.
useThemeStatealready guards this bygiving each
ThemeStateits 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.1eef74e: PR Temporary implementation ofuseFlexTokenswith a default set of tokens #4156, temporaryuseFlexTokens.ea738f0: PR Ensure that useThemeState will work with existing furn themes #4186,useThemeStateover existing FURN themes.dev/web/flex-themes/createTheme.d.ts:ThemeOverrides,BrandInput,ThemeResult, and the per-mode override shape.dev/web/flex-themes/index.d.ts: the package's public theme surface.dev/web/flex-themes/README.md: factory usage, override layering, and prebuilt light and dark themes.