Skip to content

fix(toast): render the react root through the factory so asChild works - #4072

Open
aaron-easygo wants to merge 1 commit into
chakra-ui:mainfrom
aaron-easygo:fix/react-toast-root-as-child
Open

aaron-easygo wants to merge 1 commit into
chakra-ui:mainfrom
aaron-easygo:fix/react-toast-root-as-child

Conversation

@aaron-easygo

@aaron-easygo aaron-easygo commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Toast.Root on React declares PolymorphicProps but renders a plain <div> rather than ark.div, so asChild is never read. Two consequences:

  • the child is nested inside an extra root element instead of becoming the root, and
  • asChild is spread onto the DOM, which React warns about: React does not recognize the asChild prop on a DOM element.

Before, for <Toast.Root asChild><section data-testid="custom-root">…</section></Toast.Root>:

<div data-scope="toast" data-part="root" role="status" …>
  <div data-ghost="before"></div>
  <section data-testid="custom-root">…</section>
  <div data-ghost="after"></div>
</div>

After:

<section data-testid="custom-root" data-scope="toast" data-part="root" role="status" …>
  <div data-ghost="before"></div>
  …
  <div data-ghost="after"></div>
</section>

It is the only React part that claims asChild without going through the factory — I found it while widening the check:nodes script in #4071, which flags parts whose root is not an ark node.

The ghosts

The reason this part never moved to the factory is presumably its two ghost elements. They belong to the root — their job is to keep the pointer inside the group between toasts — so with asChild they have to end up inside the child, not as siblings of it. Handing the factory a fragment of three children would not work: it would clone the fragment and React rejects props on React.Fragment. So the root clones the child with the ghosts around the child's own children, and the factory still does the prop merge and ref composition.

If asChild is set and the child is not a valid element, it passes through untouched and the factory returns null, same as every other part.

Tests

should render the asChild element as the root, keeping the ghosts inside it — asserts the section carries data-part="root", that there is exactly one [data-part="root"] in the document, that no aschild attribute is emitted, and that the ghosts are the child's first and last elements. It fails on main and passes here. bun run --cwd packages/react test, typecheck, biome and prettier are clean.

Not in this PR

The other three adapters cannot honour asChild on this part either, in three different ways:

  • Vue — Dynamic clones the first non-comment child, which here is the ghost-before div, so the root props land on the ghost and all three children render as siblings.
  • Solid / Svelte — asChild is a render prop / snippet that replaces the factory's own children, so both ghosts disappear.

Those need a decision rather than a patch (the render-prop shape gives the part no way to inject children into the consumer's element), so I have left them alone. Happy to open an issue for it, or to follow this up if you have a direction in mind.

If #4071 lands first, this PR also needs toast/toast-root deleted from knownUnreadableRoots in scripts/src/check-nodes.ts — that list is shrink-only, so the check fails until the line goes. Say the word and I will rebase whichever lands second.

RetriggerConfidence Score: 4/5

The behavioral fix appears sound, but the explicit repository commenting requirement must be satisfied before merging.

Fix All in Claude CodeFindings

  1. P2 Comment Violates Repository Guide ▶
Fix with agent prompt
### Issue 1
packages/react/src/components/toast/toast-root.tsx:24
This explanatory inline comment violates the repository directive to avoid comments and follow the existing minimal-commenting pattern. This repository requirement must be satisfied before merging; remove the comment and keep the implementation self-explanatory.

```suggestion

```

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

This PR fixes React Toast.Root polymorphism by rendering through ark.div and injecting the toast positioning ghosts into the selected child before factory prop and ref composition.

  • Makes an asChild element the actual toast root.
  • Keeps both ghost elements inside that root.
  • Adds focused regression coverage and a patch changeset.
  • Adds one inline comment that conflicts with the repository’s explicit minimal-commenting rule.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Toast.Root props] --> B{asChild?}
  B -- No --> C[Wrap ordinary children with before/after ghosts]
  B -- Yes --> D[Clone valid child with ghosts around its children]
  C --> E[ark.div factory]
  D --> E
  E --> F[Merge toast and child props]
  E --> G[Compose forwarded and child refs]
  F --> H[Rendered toast root]
  G --> H
Loading

Reviews (1) · Last reviewed commit: "fix(toast): render the react root throug..."

@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

@aaron-easygo is attempting to deploy a commit to the Chakra UI Team on Vercel.

A member of the Team first needs to authorize it.

</>
)

// The ghosts belong to the root, so asChild has to nest them inside the child it renders as.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Comment violates repository guide

This explanatory inline comment violates the repository directive to avoid comments and follow the existing minimal-commenting pattern. This repository requirement must be satisfied before merging; remove the comment and keep the implementation self-explanatory.

Suggested change
// The ghosts belong to the root, so asChild has to nest them inside the child it renders as.

Context Used: CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react/src/components/toast/toast-root.tsx
Line: 24

Comment:
**Comment violates repository guide**

This explanatory inline comment violates the repository directive to avoid comments and follow the existing minimal-commenting pattern. This repository requirement must be satisfied before merging; remove the comment and keep the implementation self-explanatory.

```suggestion

```

**Context Used:** CLAUDE.md ([source](https://github.com/chakra-ui/ark/blob/main/CLAUDE.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Toast.Root declared PolymorphicProps but rendered a plain div, so asChild was
never read: the child stayed nested inside an extra root element and the prop
was spread onto the DOM, which React warns about. It is the only React part
that claims asChild without going through the factory.

The machine's ghost elements belong to the root, so they are nested inside the
child the root renders as rather than left as siblings of it.

With the root going through the factory, toast/toast-root now reads on all four
adapters (div everywhere), so it comes off knownUnreadableRoots in check-nodes —
the list is shrink-only, and leaving it there fails the check.
@aaron-easygo
aaron-easygo force-pushed the fix/react-toast-root-as-child branch from 83888da to 94448aa Compare September 21, 2026 00:11
@aaron-easygo

Copy link
Copy Markdown
Contributor Author

Rebased onto main (post-#4084) and dropped toast/toast-root from knownUnreadableRoots in check-nodes.ts, now that #4071 has landed. That deletion is required rather than tidy-up: Solid, Vue and Svelte already render this root as an ark div, so with React going through the factory the part reads on all four adapters and the shrink-only list fails as stale if the line stays. bun scripts check:nodes is green — 490 parts, 6 known divergences, 3 unreadable roots.

Worth noting for triage: v6 already renders ToastRoot through ark.div with the ghosts inside, so this is a v5-line fix only, and nothing here needs forward-porting.

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