Skip to content

feat(registry)!: adopt the new Remix APIs in the Vanilla preset - #342

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

leoafarias wants to merge 13 commits into
stack/follow-ups-integrationfrom
feat/registry-vanilla-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.

Vanilla now uses the Remix APIs from the open follow-up PRs.

  • Focus rings: tab, toggle, toggle group option, menu trigger, accordion, and disclosure draw vanillaFocusRing() through containerEffects (triggerEffects on accordion and disclosure) instead of a foreground decoration.
    • A toggle group option draws its ring inside itself (inset: true), because the group clips.
    • vanillaFocusRingDecoration is removed.
    • The accordion ring used to wrap the whole opened section. It now wraps the trigger only.
  • Labels: the radio and switch recipes set the new label as a form label (textSm, w500, foreground), with a 12px gap on a radio and 8px on a switch. The fixture and widgetbook demos pass label.
  • Kebab: the order-table kebab is a real ghost VanillaIconButton passed to RemixMenuTrigger.control. It has one focus stop, the menu's expanded state is merged onto the button, and the button takes a muted fill while the menu is open.
  • Select width: the panel is max(128, trigger width). It keeps the 128px minimum and adds remixAnchorWidth as a second minimum through a box modifier.
  • Account menu: the dashboard account trigger draws its own focus ring through a RemixCard with containerEffects, because a RemixMenuTrigger.builder replaces the styled trigger and its effects. The menu docs' avatar example now does the same.
  • Spec deviation: the inset ring on toggle group options is recorded in specs/vanilla.md.

Left as is:

  • the account menu, a profile row, which is the documented .builder case;
  • the settings rows' semanticLabel.

Validation

  • registry_source Vanilla and dashboard suites: 490 pass. The ring tests read rendered pixels, so they hold however a part paints. The accordion, select width, kebab fill, and sidebar ring cases fail against the earlier recipes.
  • dart analyze on registry_source and the Vanilla apps, root format, ci:checks, docs:check, generate:check, build_registry --check, and open-code:apps:check: clean.
  • tool/check_open_code.dart --source checkout (Vanilla): passed (373 fixture tests).
  • The sidebar destination ring relies on feat(remix): add containerEffects to focusable parts #331 painting containerEffects on sidebar destinations, which is in chore: integration check for the follow-up stack (do not merge) #339.

Related Issues

Part of PR E in the Remix API follow-up plan. Depends on #331, #332, #337, and #326 through 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.

Installed Vanilla source: the theme item no longer exports <prefix>FocusRingDecoration. Reinstall the theme together with the tab, toggle, toggle group, menu, accordion, and disclosure items. The recipes need remix 1.0.0-beta.14 and naked_ui 1.3.0.

Tab, toggle, toggle group option, menu trigger, accordion, and disclosure
drew their ring with a foreground decoration, which needed a second helper
with its own radius and inset arguments and, on the accordion, ringed the
whole section so the ring grew with the revealed panel.

They now use vanillaFocusRing in containerEffects, or triggerEffects on the
accordion and disclosure, so the ring follows the part's own corners, takes
no layout space, and wraps only the trigger. A toggle group option rings
inside itself with the new inset argument because the group clips. The
vanillaFocusRingDecoration helper is removed.
RemixRadio and RemixSwitch take a label drawn inside the control's own tap
target, focus ring, and accessible name. The Vanilla recipes now set it as a
form label like the checkbox does, textSm at medium weight in foreground,
12px from a radio and 8px from a switch, so callers no longer compose a
Row of the control and a Text.

The fixture gallery and the Vanilla widgetbook radio and switch cases pass
label instead.
…button

The order table kebab was a Semantics-wrapped Box passed as a visual
RemixMenuTrigger.builder, so it had no hover, focus ring, or disabled state
of its own. It is now a ghost VanillaIconButton given to
RemixMenuTrigger.control with the menu handle's focusNode and toggle, keeping
the muted glyph. The menu still owns the overlay, Escape, and focus return,
and merges its expanded state onto the button, which stays the one focus stop.
… trigger

The using-remix Vanilla reference lists the single focus ring helper and the
slots it goes in, the radio and switch label, and how to open a menu from a
real button, and both it and the open-code guide tell an older install to
reinstall the ring recipes with the theme now that the decoration helper is
gone.
…debar

The ring probe matched a drawPath call, so most cases failed against the old
foreground-decoration recipes for how the ring was painted rather than for
what a reader sees. It now compares the frame before and after focus: the
ring's color laid over what was there at each point it must cover, and no
change at the points it must not, keeping the size and neighbor checks.

A sidebar destination case is added, and the state distinguishability case
for the sidebar destination reads the spec as before instead of its effects
layer. RemixSidebar draws destinations with RowBox, which does not paint
containerEffects, so both fail until the sidebar paints them.
… trigger

shadcn's default item-aligned select never draws the panel narrower than its
trigger. The panel keeps its 128px minimum and adds remixAnchorWidth as a
second minimum through a box modifier, so the panel is max(128, trigger) and a
caller who replaces the floor through style still keeps the trigger's width.
…is open

shadcn's dashboard block fills the kebab with muted while its menu is open.
The button reads the menu handle's isOpen and merges that fill in, and the
dashboard test checks the fill follows the menu open and closed.
…bel note

The Vanilla spec and the open-code guide said the focus ring is always drawn
outside with no offset. A toggle group option draws it inside because the
group clips its options, where the reference lifts the focused item with
focus-visible:z-10, so the spec gains a deviation row and the guide notes it.

The using-remix Vanilla reference no longer says the radio and switch label
sits inside the focus ring: the label is part of the tap and focus target,
and the ring wraps the circle or track only. The toggle's "No shadow" comment
is dropped, since a decoration shadow is no longer the reason the outline
toggle has none, and the spec row says so.
The dashboard's account row is a RemixMenuTrigger.builder, and a builder
replaces the styled trigger and its effects, so the row was a keyboard focus
stop that showed no focus. Its content is now a RemixCard that draws
vanillaFocusRing() in containerEffects on focus-visible, resolved under the
menu's widget states, so the ring paints outside the row and moves nothing.
It is the only builder trigger in the Vanilla recipes.

The ring test focuses the real trigger in the dashboard and checks the ring
pixels and that the trigger and its content keep their boxes. The menu docs'
avatar builder example draws its own ring and says why, and the Vanilla
reference notes it.

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