Skip to content

fix(mix): apply variants when resolving nested styles#926

Merged
tilucasoli merged 1 commit into
mainfrom
fix/nested-style-variant-resolution
Jun 5, 2026
Merged

fix(mix): apply variants when resolving nested styles#926
tilucasoli merged 1 commit into
mainfrom
fix/nested-style-variant-resolution

Conversation

@tilucasoli
Copy link
Copy Markdown
Collaborator

Problem

A Style stored inside another Style's Prop — i.e. a component sub-style — silently drops its own variants.

Prop.resolveProp() merges the MixSource values and then resolves them with mergedMix.resolve(context). For a Style, resolve() only resolves props; it does not apply variants. Context variants (widget states hovered/pressed/disabled/focused, brightness, breakpoints, platform, …) are applied solely by Style.build() (mergeActiveVariants(...)resolve(...)). The top-level style goes through StyleBuilderstyle.build(context), but every nested style prop went through resolve() — so its variants were quietly ignored.

This is what makes btwld/remix#59 fail:

RemixSelectStyle().trigger(
  RemixSelectTriggerStyle()
    .onHovered(RemixSelectTriggerStyle().labelColor(Colors.red)) // never applied
)

The trigger style is stored as a nested Prop<StyleSpec<RemixSelectTriggerSpec>>, so .onHovered / .onDisabled had no effect. (Dropdown items worked only because each is built through its own StyleBuilder + controller.)

Fix

Build nested styles instead of resolving them, so their context variants resolve against the current context:

resolvedValue = mergedMix is Style
    ? (mergedMix as Style).build(context) as V
    : mergedMix.resolve(context);
  • No-op for nested styles without variantsmergeActiveVariants returns the style unchanged, then resolves identically to before. The blast radius is exactly "nested styles that carry variants."
  • Only Style has build(); every other Mix (e.g. ModifierMix, DecorationMix, ShapeBorderMix) stays on the original resolve() path via the type guard.
  • Type-safe: when mergedMix is a Style<S>, the prop's V is StyleSpec<S>, which is exactly what build() returns.

Verification

  • ✅ Full mix test suite: 2716/2716 pass with the change.
  • ✅ New regression test test/src/core/nested_style_variant_test.dart is red/green-proven (fails without the fix, passes with it).
  • ✅ Downstream remix full suite: 1742/1742 pass — issue Pressable freezes on press when onPressed: null #59 is fixed with no remix-side code change.

Known boundary (follow-up, not addressed here)

StyleBuilder decides whether to auto-install a WidgetStateProvider from the top-level style's widgetStates only (it doesn't recurse into nested props). So a nested-only widget-state variant activates only when a state scope already exists (e.g. a component that passes a controller, as remix's Select does). Making Style.widgetStates recurse into nested style props is a larger, separate change worth a dedicated issue.

Fixes btwld/remix#59

🤖 Generated with Claude Code

@docs-page
Copy link
Copy Markdown

docs-page Bot commented Jun 5, 2026

To view this pull requests documentation preview, visit the following URL:

docs.page/btwld/mix~926

Documentation is deployed and generated using docs.page.

@github-actions github-actions Bot added the mix label Jun 5, 2026
@tilucasoli tilucasoli changed the base branch from feat/styler-generator to main June 5, 2026 02:20
Prop.resolveProp resolved a merged nested Style via resolve(), which only
resolves props and skips variants. Context variants — widget states
(hovered/pressed/disabled/focused), brightness, breakpoints, etc. — are
applied by Style.build(), not Style.resolve(). So a Style nested inside
another Style's Prop (a component sub-style) silently dropped its own
variants. For example RemixSelectTriggerStyle().onHovered(...) had no
effect because the trigger style is resolved as a nested prop of
RemixSelectStyle.

Build nested styles instead, so their variants resolve against the
current context. This is a no-op for nested styles without variants
(mergeActiveVariants returns the style unchanged), keeping the change
scoped to styles that actually carry variants.

Fixes btwld/remix#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tilucasoli tilucasoli force-pushed the fix/nested-style-variant-resolution branch from 01e7bb1 to 26d1679 Compare June 5, 2026 02:25
@tilucasoli tilucasoli merged commit 3e1b9d0 into main Jun 5, 2026
4 checks passed
@tilucasoli tilucasoli deleted the fix/nested-style-variant-resolution branch June 5, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.onHover variant won't work in RemixSelectStyle

1 participant