Skip to content

feat(registry)!: adopt the new Remix APIs in the HeroUI preset - #343

Draft
leoafarias wants to merge 27 commits into
stack/follow-ups-integrationfrom
feat/registry-heroui-adopt-remix-apis
Draft

leoafarias wants to merge 27 commits into
stack/follow-ups-integrationfrom
feat/registry-heroui-adopt-remix-apis

Conversation

@leoafarias

@leoafarias leoafarias commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Draft, stacked on #339. Do not merge until remix 1.0.0-beta.14 and naked_ui 1.3.0 are on pub.dev. The preset uses APIs that exist only in those releases, and the registry remix: floor moves in the beta.14 release PR, not here.

The HeroUI preset adopts the Remix and Naked UI APIs from the follow-up PRs, so it stops approximating what they now express.

  • Radio and switch labels (feat(remix)!: add label to RemixRadio and RemixSwitch #332): both pass label. The label is inside the tap target, so hover, press, focus, and semantics cover one target. HeroUiControlRow becomes HeroUiDescribedControl, which lays out only the description beneath the label.
  • Focus rings (feat(remix): add containerEffects to focusable parts #331): the ring is an outline effect (containerEffects, triggerEffects) on tabs, toggles, toggle group items, select and menu rows, the menu trigger, and the accordion and disclosure triggers.
    • Focus no longer changes layout.
    • Toggle group items draw the ring inside, as upstream ring-inset does. The surface accordion's trigger also draws it inside, as an approximation.
  • Selected tab shadow: it is containerEffects(heroUiSurfaceEffects()), so it follows --surface-shadow (none in dark) and combines with the focus ring.
  • Select width (feat(remix): expose the anchor width to select and menu overlays #326): the select content takes remixAnchorWidth() as its minWidth.
  • Overlay triggers (feat(remix)!: let a real button trigger RemixMenu #337, fix(naked_ui): keep focus on the caller's control in a popover opened without tap #338): a HeroUiButton is the dropdown trigger through RemixMenuTrigger.control, and a popover button shares one FocusNode with the popover. The gallery fixture uses both.
  • Toast: the action stacks under the message at 768px and below (actionPlacement: below, inclusive like max-width: 768px). Below 640px it adds mt-2 through onNot(Breakpoint.minWidth(640)), the exact complement of sm:mt-0.
  • Docs: the worksheets, a dated adoption revision in ADR 0002, and skills/using-remix/references/heroui.md record what this closes.

Not done:

  • HeroUI's .animate(...) transitions still ignore reduced motion, because Mix 2.3.1's .animate does not read it. This is tracked as a Mix follow-up.
  • From 640 to 768px the toast's action sits 6px below the message, where upstream has 0. This is recorded in toast.yaml.

Validation

Continuation validation (2026-10-27)

Merged final integration a6adc9592 (including #347–#350, the effects-test focus baseline correction and the scoped Vanilla consumer-host fix). Independent review of combined tree f40536ba9 found no merge-delta issues. The strict complete local gate on that exact combined tree passes all 8 CI legs, with zero failed steps and unchanged clean initial/final SHA. Flutter counts: naked_ui1124 (3 existing skips), remix3132, registry_source2422; final HeroUI consumer fixture17 pass. Fresh GitHub CI on this PR is green; older pass counts below belong to their recorded earlier heads. Draft/release requirements are unchanged.

  • flutter test in registry_source: 2326 pass. The new behavior tests fail against the previous recipes. They include the intrinsic-sizing regression tests: a labeled HeroUiRadioGroup under IntrinsicWidth and a HeroUiSwitchGroup under IntrinsicHeight, which pass with feat(remix)!: add label to RemixRadio and RemixSwitch #332's labeled-control fix.
  • dart analyze registry_source, root dart format, generate:check, build_registry --check, then open-code:registry:build and open-code:apps:check, and melos run ci:checks (includes docs:check and heroui:parity:check): pass.
  • dart run tool/check_open_code.dart --preset heroui --source checkout: pass.

Related Issues

Depends on #318, #326, #331, #332, #335, #337, and #338, all merged into the #339 stack.


Checklist

  • My PR includes unit or integration tests for all changed/updated/fixed behaviors.
  • I have updated or added relevant documentation (doc comments with ///).
  • I am prepared to follow up on review comments in a timely manner.

Breaking Change

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

This breaks installs of the HeroUI preset from #324. The preset needs remix 1.0.0-beta.14 and naked_ui 1.3.0.

  • HeroUiControlRow is replaced by HeroUiDescribedControl.
    • Its label, controlWidth, and invalid are gone; pass the label to HeroUiRadio or HeroUiSwitch instead.
    • The description is indented by descriptionInset, which is the control width plus 12.
  • heroUiRingBorder and heroUiInsideRing are removed.
  • heroUiSurfaceBoxShadows is removed; use containerEffects(heroUiSurfaceEffects()).
  • Focus rings are drawn outside the part, so an app clip can crop them, and a select or dropdown list long enough to scroll crops the outside row ring at its edges. Toggle group items and the surface accordion draw theirs inside.
  • The toast stacks its action at 768px and below. A caller action must restate heroUiButtonStyle() in the base action, and again, with .margin(.top(2)), in an onNot(Breakpoint.minWidth(640)) fragment, the same variant the recipe uses.
  • The select popover is at least as wide as its trigger.
  • radio_group and switch_group now depend on theme in the registry.
  • HeroUiRadio and HeroUiSwitch need a label or a semanticLabel instead of a required semanticLabel, and HeroUiSwitch gains invalid.

HeroUiRadio and HeroUiSwitch take the label RemixRadio and RemixSwitch now
draw inside their tap target, so pointing at the label hovers, presses, and
toggles the control and one semantics node carries its name. The group
composition drops its label row; HeroUiControlRow becomes
HeroUiDescribedControl, which lays out only the description beneath the
label.
Tabs, toggles, toggle group items, select and menu rows, the menu trigger, and
the accordion and disclosure triggers paint HeroUI's focus ring as an outline
effect instead of an always-present transparent inside border. The ring takes
no layout space, so focus no longer resizes a part or needs padding reduced by
the ring width, and it is the measured gap-then-ring outside the part.
Toggle group items and the surface accordion, whose parent clips, draw
ring-inset inside the part. heroUiRingBorder and heroUiInsideRing are gone.
The select content takes remixAnchorWidth() as its minimum width, the port of
min-w-(--trigger-width), so the popover is at least as wide as the trigger
instead of as wide as its widest item.
RemixMenuTrigger.control makes a real HeroUiButton the menu trigger, so the
dropdown no longer needs a label standing in for it. The tests pin one tab
stop, focus returning to the button, one expandable semantics node, and a
disabled button not opening the menu.
… 768px

The toast sets actionPlacement to below under maxWidth(768), inclusive like
max-width: 768px, and adds mt-2 below 640px by restating the action style
under maxWidth(639), because an action replaces rather than merges.
… button

A HeroUiButton that opens a popover with openOnTap: false passes the same
FocusNode as the popover's triggerFocusNode and its own focusNode, so the
button is the only tab stop and takes focus back on close. The recipe doc, the
worksheet, and the popover test show it.
…gers

The gallery's dropdown trigger is an AcmeButton through
RemixMenuTrigger.control, and its popover button shares a focus node with the
popover. The fixture tests pin that the menu opens, a row reports its action,
and focus returns to each button.
…kill

ADR 0002's verdict rows for focus rings, the select width, radio and switch
labels, and the dropdown trigger now say they shipped and are adopted. The
HeroUI reference covers labeled controls, the button triggers and shared
popover focus node, the select width, the toast action placement, and that
Mix's .animate does not yet honor reduced motion.
A labeled radio or switch group sits under IntrinsicWidth in an alert dialog's
content, and Remix's labeled-control helper used a LayoutBuilder that cannot
report intrinsic sizes. The two tests fail until the Remix fix for that reaches
this branch.
…fects

The selected pill takes heroUiSurfaceEffects() instead of plain box shadows, so
it follows the --surface-shadow token (none in dark) and combines with the
focus ring field by field. heroUiSurfaceBoxShadows is removed, and the dark
mode approximation with it.
onNot(Breakpoint.minWidth(sm)) is the complement of sm:mt-0, so the margin
reaches the last fraction below 640px where maxWidth(639) missed 639 to 640.
The recipe doc and the test spell out the caller pattern: restate
heroUiButtonStyle() in the base action and in the below-sm fragment, and assert
the restated size, fill, and gap.
The inset check only repeated the shadow parsing, so expectFocusRing takes an
inset flag. The dropdown button's height comes from the measured secondary
button instead of a literal.
The decided table stays as written, and a dated Adoption revision lists the
four approximations it replaces and what remains, with the surface accordion's
inside ring attributed as an approximation. The select alignment claim is
softened, the corner crop of the inside rings is recorded, the toast caller
pattern is documented, and the reduced-motion note no longer links a closed
Mix PR.
…bels

Besides laying out without an exception, the radio and switch groups under
IntrinsicWidth and IntrinsicHeight take a finite size that contains every label.
The variant holds the 640px-and-up breakpoint, so onNot(fromSm, ...) reads as
the below-sm fragment it is.
…s ends

The inside ring survives the group's clip except at the joined pill's rounded
ends, as toggle_button_group.yaml records.
… notes

The revision quotes the popover row's verdict as decided, names the five rows it
supersedes, and folds the tab shadow into the ring row, where it matches
upstream. Reduced motion says only that Mix 2.3.1's .animate does not read it.
The toast example restates a HeroUI variant, and toast.yaml says the 640 and
768px heights are derived from the CSS and cites the 768px rule in the
published @heroui/react 3.2.6 toast.js.

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