Radix Umbrella Slot Root Crash
This troubleshooting note covers the dependency migration in 9a51be0, the partial type-only follow-up in 76d14ce, and the runtime fix in 7a20d6d.
Symptoms
- The app crashes with a React runtime error such as
Element type is invalid. - The failure appears only on shadcn-style
asChildpaths, especially in sidebar menu components. - TypeScript may still pass if the code only adds a cast around the component variable.
Diagnosis
Compare the current owned-forked primitives:
lumie-frontend/components/ui/button.tsxusesconst Comp = asChild ? Slot.Root : "button".lumie-frontend/components/ui/sidebar.tsxnow does the same forSidebarGroupLabel,SidebarGroupAction,SidebarMenuButton,SidebarMenuAction, andSidebarMenuSubButton.
That is the key clue. With the radix-ui umbrella package, Slot is a namespace object, not the directly renderable component value that older @radix-ui/react-slot call sites expected.
Root Cause
9a51be0 migrated shadcn primitives from package-specific imports such as @radix-ui/react-slot to the umbrella import import { Slot } from "radix-ui".
Some call sites still rendered bare Slot in asChild ? Slot : ... expressions. That compiled, but at runtime React received an object instead of a component and threw the invalid element-type error.
76d14ce added React.ElementType casts in sidebar.tsx, which fixed the typing problem but not the runtime behavior. The actual fix landed in 7a20d6d, which changed the render targets to Slot.Root.
Fix
When importing from radix-ui, render Slot.Root rather than bare Slot in asChild branches.
Use lumie-frontend/components/ui/button.tsx as the reference pattern. The sidebar components now match it.
Prevention And Verification
- After any Radix dependency migration, grep for
asChild ? Slot :or other bareSlotrender expressions insidecomponents/ui. - Treat
React.ElementTypecasts as type adapters only; they do not make an invalid runtime component valid. - Verify current source:
lumie-frontend/components/ui/button.tsxusesSlot.Root.lumie-frontend/components/ui/sidebar.tsxusesSlot.Rootfor everyasChildbranch that previously crashed.
Operational Detail
This is a dependency-shape failure. The TypeScript surface can look acceptable while React still receives a value it cannot render.
When asChild is involved, the failing component usually does not crash at import time. It crashes only when a consumer actually renders the asChild branch, which is why sidebar, dropdown, and button composition paths are the highest-risk areas.
| Signal | Meaning |
|---|---|
Element type is invalid | React received an object or undefined instead of a component. |
Crash only with asChild | The normal element branch is fine; the slot branch is wrong. |
Cast to React.ElementType compiles | The type system was bypassed, but runtime shape was not fixed. |
Recovery Playbook
- Search owned UI primitives for bare
Slotrender targets. - Replace
SlotwithSlot.Rootwhere the import comes fromradix-ui. - Remove casts that were only hiding the mismatch unless another generic type issue remains.
- Render the affected components in their
asChildmode, not only their default mode. - Check routes that compose sidebar links, menu buttons, dropdown triggers, and command items.
Verification Evidence
Collect evidence at two levels:
- static:
rg -n "asChild \\? Slot(\\s|:)" components src appreturns no bare umbrellaSlotrender branches, - runtime: a page using the affected
asChildcomponents renders without the invalid element-type error, - regression: the normal non-
asChildbranch still renders the expected native element.
Safe Future Changes
For future Radix upgrades, inspect import semantics before running broad codemods. Package-specific imports and umbrella imports can expose the same conceptual primitive with a different runtime shape. Migration review should include at least one composed shadcn component path, because those paths exercise the slot primitive more heavily than basic button rendering.