Skip to content

detach support added for all comps#1723

Open
GoldGroove06 wants to merge 2 commits intomainfrom
new-detach-prop-support
Open

detach support added for all comps#1723
GoldGroove06 wants to merge 2 commits intomainfrom
new-detach-prop-support

Conversation

@GoldGroove06
Copy link
Copy Markdown
Collaborator

@GoldGroove06 GoldGroove06 commented Mar 31, 2026

should close #1513

Summary by CodeRabbit

Release Notes

  • New Features

    • Added detach prop support across all UI components. When enabled, components will not apply Rad UI's generated CSS classes, allowing for complete styling customization.
  • Tests

    • Added test coverage for the new detach functionality.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 31, 2026

🦋 Changeset detected

Latest commit: 4dab9b1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@radui/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Added an optional detach boolean prop across all UI components that, when enabled, removes all Rad UI generated classes by threading the flag through the customClassSwitcher function. Core implementation updated to return an empty string when detach is true.

Changes

Cohort / File(s) Summary
Changeset & Core Logic
.changeset/floppy-spoons-appear.md, src/core/customClassSwitcher/index.ts, src/core/customClassSwitcher/customClassSwitcher.test.tsx
Added patch release note and updated customClassSwitcher to accept detach parameter, returning empty string when detach === true. Added test case covering this behavior.
Root Components - Accordion, AlertDialog, Avatar, AvatarGroup
src/components/ui/Accordion/fragments/AccordionRoot.tsx, src/components/ui/AlertDialog/fragments/AlertDialogRoot.tsx, src/components/ui/Avatar/fragments/AvatarRoot.tsx, src/components/ui/AvatarGroup/fragments/AvatarGroupRoot.tsx
Added optional detach?: boolean prop to each Root component props interface and threaded it into customClassSwitcher calls with default value false.
Root Components - Badge, BlockQuote, Button, Callout
src/components/ui/Badge/Badge.tsx, src/components/ui/BlockQuote/BlockQuote.tsx, src/components/ui/Button/Button.tsx, src/components/ui/Callout/fragments/CalloutRoot.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher invocations to include the detach parameter with default value false.
Root Components - Card, Checkbox, CheckboxCards, CheckboxGroup
src/components/ui/Card/fragments/CardRoot.tsx, src/components/ui/Checkbox/fragments/CheckboxRoot.tsx, src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx, src/components/ui/CheckboxGroup/fragments/CheckboxGroupRoot.tsx
Added optional detach?: boolean prop to props interfaces and threaded it into customClassSwitcher calls.
Root Components - Code, Collapsible, Combobox, ContextMenu
src/components/ui/Code/Code.tsx, src/components/ui/Collapsible/fragments/CollapsibleRoot.tsx, src/components/ui/Combobox/fragments/ComboboxRoot.tsx, src/components/ui/ContextMenu/fragments/ContextMenuRoot.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher calls with default value false.
Root Components - DataList, Dialog, Disclosure, DropdownMenu
src/components/ui/DataList/fragments/DataListRoot.tsx, src/components/ui/Dialog/fragments/DialogRoot.tsx, src/components/ui/Disclosure/fragments/DisclosureRoot.tsx, src/components/ui/DropdownMenu/fragments/DropdownMenuRoot.tsx
Added optional detach?: boolean prop and threaded it into customClassSwitcher invocations with default value false.
Root Components - Em, Heading, HoverCard, Kbd
src/components/ui/Em/Em.tsx, src/components/ui/Heading/Heading.tsx, src/components/ui/HoverCard/fragments/HoverCardRoot.tsx, src/components/ui/Kbd/Kbd.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher calls. HoverCard also updated rootTriggerClass computation.
Root Components - Link, Menubar, Minimap, NavigationMenu
src/components/ui/Link/Link.tsx, src/components/ui/Menubar/fragments/MenubarRoot.tsx, src/components/ui/Minimap/fragments/MinimapRoot.tsx, src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx
Added optional detach?: boolean prop and threaded it into customClassSwitcher calls with default value false.
Root Components - Progress, Quote, Radio, RadioCards
src/components/ui/Progress/fragments/ProgressRoot.tsx, src/components/ui/Quote/Quote.tsx, src/components/ui/Radio/Radio.tsx, src/components/ui/RadioCards/fragments/RadioCardsRoot.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher invocations.
Root Components - RadioGroup, ScrollArea, Select, Separator
src/components/ui/RadioGroup/fragments/RadioGroupRoot.tsx, src/components/ui/ScrollArea/fragments/ScrollAreaRoot.tsx, src/components/ui/Select/fragments/SelectRoot.tsx, src/components/ui/Separator/Separator.tsx
Added optional detach?: boolean prop and threaded it into customClassSwitcher calls.
Root Components - Skeleton, Slider, Spinner, Splitter
src/components/ui/Skeleton/Skeleton.tsx, src/components/ui/Slider/fragments/SliderRoot.tsx, src/components/ui/Spinner/Spinner.tsx, src/components/ui/Splitter/fragments/SplitterRoot.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher invocations with default value false.
Root Components - Steps, Strong, Switch, TabNav
src/components/ui/Steps/fragments/StepRoot.tsx, src/components/ui/Strong/Strong.tsx, src/components/ui/Switch/fragments/SwitchRoot.tsx, src/components/ui/TabNav/fragments/TabNavRoot.tsx
Added optional detach?: boolean prop and threaded it into customClassSwitcher calls with default value false.
Root Components - Table, Tabs, Text, TextArea
src/components/ui/Table/fragments/TableRoot.tsx, src/components/ui/Tabs/fragments/TabContent.tsx, src/components/ui/Tabs/fragments/TabRoot.tsx, src/components/ui/Text/Text.tsx, src/components/ui/TextArea/fragments/TextAreaRoot.tsx
Added optional detach?: boolean prop to props interfaces and updated customClassSwitcher invocations.
Root Components - Toggle, ToggleGroup, Toolbar, Tree, VisuallyHidden
src/components/ui/Toggle/Toggle.tsx, src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx, src/components/ui/Toolbar/fragments/ToolbarRoot.tsx, src/components/ui/Tree/fragments/TreeRoot.tsx, src/components/ui/VisuallyHidden/VisuallyHidden.tsx
Added optional detach?: boolean prop and threaded it into customClassSwitcher calls with default value false.
Test & Story Updates
src/components/ui/Toggle/tests/Toggle.test.js, src/components/ui/ToggleGroup/stories/ToggleGroup.stories.tsx
Added test case verifying detach={true} removes all generated classes. Updated ToggleGroup story template with customRootClass prop.
Utility Refactoring
src/core/utils/mergeRefs.ts
Reformatted indentation and normalized string quotes from double to single; no functional behavior changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes follow a highly repetitive pattern applied across 67+ component files, each adding the same detach prop and threading it into customClassSwitcher. While individual component changes are straightforward, the core logic change in customClassSwitcher requires careful verification, and the systematic application across all components demands validation of consistency. The test coverage and single story update provide adequate validation points.

Possibly related PRs

Suggested reviewers

  • kotAPI

🐰 Hop along, detach is here today!
Remove the rad-ui classes, hip-hooray!
No more CSS clutter in the way,
Pure styling freedom—let's celebrate! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'detach support added for all comps' is concise and clearly summarizes the main change: adding detach prop support across all components.
Linked Issues check ✅ Passed The PR successfully implements the detach feature from #1513: adds optional detach prop to all component Root nodes, defaults to false, and returns empty string from customClassSwitcher when detach=true, eliminating Rad UI-generated classes.
Out of Scope Changes check ✅ Passed All changes are within scope: detach prop additions to component Props types, customClassSwitcher implementation update, unit test, and one story update with customRootClass. The mergeRefs.ts reformatting is a minor maintenance change unrelated to detach.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-detach-prop-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

Coverage

This report compares the PR with the base branch. "Δ" shows how the PR affects each metric.

Metric PR Δ
Statements 80.96% +0.01%
Branches 64.89% +1.05%
Functions 69.07% +0.00%
Lines 82.28% +0.01%

Coverage improved or stayed the same. Great job!

Run npm run coverage locally for detailed reports and target untested areas to raise these numbers.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/components/ui/Combobox/fragments/ComboboxRoot.tsx (1)

15-21: ⚠️ Potential issue | 🔴 Critical

detach currently leaks generated classes in Combobox (-root).
At Line 21, ${rootClass}-root becomes -root when detach is true (rootClass === ''), which breaks the feature contract of emitting no Rad UI-generated classes.

🐛 Suggested fix
 const ComboboxRoot = React.forwardRef<ComboboxRootElement, ComboboxRootProps>(
     ({ customRootClass, detach = false, children, defaultValue, value, onValueChange, shift, ...props }, forwardedRef) => {
         const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
+        const scopedRootClass = rootClass ? `${rootClass}-root` : '';

         return (
             <ComboboxRootContext.Provider value={{ rootClass }}>
                 <ComboboxPrimitive.Root
-                    className={`${rootClass}-root`}
+                    className={scopedRootClass}
                     defaultValue={defaultValue}
                     value={value}
                     onValueChange={onValueChange}

Also apply the same guard pattern in Combobox child fragments that build ${rootClass}-* (e.g., item/text) to fully satisfy detach mode.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Combobox/fragments/ComboboxRoot.tsx` around lines 15 - 21,
The Combobox is leaking a generated class when detach is true because
`${rootClass}-root` becomes `-root` if rootClass is empty; change the class
construction in the ComboboxRoot component (where you use customClassSwitcher,
rootClass, and render ComboboxPrimitive.Root inside
ComboboxRootContext.Provider) to only append the `-root` suffix when rootClass
is non-empty (e.g., return an empty string/undefined or omit the className when
rootClass === ''), and apply the identical guard pattern to all child fragments
that build `${rootClass}-*` (e.g., item, text) so no `-*` classes are emitted in
detach mode.
src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx (1)

28-28: ⚠️ Potential issue | 🔴 Critical

Critical bug: Invalid CSS class names generated when detach={true}

When detach is true, customClassSwitcher returns an empty string. The template literals across all CheckboxCards fragments then produce invalid class names: -root, -item, -tick-icon, and -content. This defeats the purpose of detach, which should suppress all generated classes.

Affected files:

  • CheckboxCardsRoot.tsx:28 → -root
  • CheckboxCardsItem.tsx:15 → -item
  • CheckboxCardsIndicator.tsx:34 → -tick-icon
  • CheckboxCardsContent.tsx:52 → -content
🐛 Required fixes

CheckboxCardsRoot.tsx:28

-            <CheckboxGroupPrimitive.Root ref={ref} className={clsx(`${rootClass}-root`, rootClass, className)} {...props} {...composedAttributes()}>
+            <CheckboxGroupPrimitive.Root ref={ref} className={clsx(rootClass && `${rootClass}-root`, rootClass, className)} {...props} {...composedAttributes()}>

CheckboxCardsItem.tsx:15

-        <CheckboxGroupPrimitive.Trigger ref={ref} className={clsx(`${rootClass}-item`, className)} value={value} {...props}>
+        <CheckboxGroupPrimitive.Trigger ref={ref} className={clsx(rootClass && `${rootClass}-item`, className)} value={value} {...props}>

CheckboxCardsIndicator.tsx:34

-        <svg ref={ref} width="15" height="15" viewBox="0 0 15 15" className={clsx(`${rootClass}-tick-icon`, className)} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}><path d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
+        <svg ref={ref} width="15" height="15" viewBox="0 0 15 15" className={clsx(rootClass && `${rootClass}-tick-icon`, className)} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}><path d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>

CheckboxCardsContent.tsx:52

-        <CheckboxGroupPrimitive.Content ref={ref} className={`${rootClass}-content`} {...props} >{children}</CheckboxGroupPrimitive.Content>
+        <CheckboxGroupPrimitive.Content ref={ref} className={clsx(rootClass && `${rootClass}-content`, props.className)} {...props} >{children}</CheckboxGroupPrimitive.Content>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx` at line 28,
The generated classnames use template literals like `${rootClass}-root` which
produce invalid names when customClassSwitcher returns an empty string; update
each occurrence to only append the suffix when rootClass is truthy (e.g., change
clsx(`${rootClass}-root`, rootClass, className) to clsx(rootClass &&
`${rootClass}-root`, rootClass, className)) so no leading hyphen is emitted;
apply the same pattern in CheckboxCardsRoot (CheckboxGroupPrimitive.Root),
CheckboxCardsItem (the item class), CheckboxCardsIndicator (the tick-icon class)
and CheckboxCardsContent (the content class).
src/components/ui/Splitter/fragments/SplitterRoot.tsx (1)

10-44: ⚠️ Potential issue | 🔴 Critical

Fix invalid CSS class names when detach is enabled.

When detach = true, customClassSwitcher returns an empty string. This causes child components to generate invalid CSS class names:

  • SplitterPanel produces className="-panel" (line 34)
  • SplitterHandle produces className="-handle" (line 23)

CSS class names cannot start with a hyphen. The clsx function filters falsy values only, not invalid strings, so these malformed classes are included in the output.

Fix: Avoid appending suffixes when rootClass is empty. For example:

className={clsx(rootClass && `${rootClass}-panel`, className)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Splitter/fragments/SplitterRoot.tsx` around lines 10 - 44,
When detach is true customClassSwitcher can return an empty string causing
rootClass to be falsy and yielding invalid class names like "-panel" and
"-handle"; update SplitterRoot rendering logic so className concatenation for
child components (SplitterPanel, SplitterHandle) only appends suffixes when
rootClass is non-empty (e.g. use conditional expressions with rootClass before
`${rootClass}-panel` / `${rootClass}-handle`), keep using clsx for merging but
ensure you pass either the full suffixed string or nothing when rootClass is
empty to prevent leading-hyphen class names.
src/components/ui/Switch/fragments/SwitchRoot.tsx (1)

20-57: ⚠️ Potential issue | 🔴 Critical

Fix SwitchThumb to handle empty rootClass when detach is enabled.

When detach=true, rootClass becomes an empty string, causing SwitchThumb's className to render as "-indicator" (line 27), which is an invalid CSS class. SwitchThumb must conditionally build the className to avoid this:

Suggested fix for SwitchThumb.tsx
className={rootClass ? `${rootClass}-indicator ${className || ''}` : className || ''}

This pattern should also be checked in any other child components that interpolate rootClass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Switch/fragments/SwitchRoot.tsx` around lines 20 - 57, The
SwitchThumb className improperly interpolates rootClass when detach is true
(rootClass becomes an empty string) causing an invalid "-indicator" class;
update the SwitchThumb component to conditionally prepend
`${rootClass}-indicator` only when rootClass is non-empty (use the rootClass
value computed in SwitchRoot via customClassSwitcher) and fall back to the
original className when rootClass is falsy, and audit other child components
that concatenate rootClass similarly to apply the same conditional pattern.
src/components/ui/Spinner/Spinner.tsx (1)

17-23: ⚠️ Potential issue | 🟠 Major

Detach still emits -container in Spinner.

rootClass can be empty, but the container always concatenates -container, so detach does not fully remove generated classes.

Proposed fix
-        <div className={`${rootClass}-container`}>
+        <div className={rootClass ? `${rootClass}-container` : undefined}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Spinner/Spinner.tsx` around lines 17 - 23, The Spinner
always appends "-container" to rootClass even when customClassSwitcher returns
an empty string (when detach is true), producing a stray "-container" class;
update the Spinner render logic (in the React.forwardRef component named
Spinner) to only append the "-container" suffix when rootClass is non-empty
(e.g., conditionally build the container class using rootClass ?
`${rootClass}-container` : '' or omit the class attribute), and ensure any other
places that concatenate rootClass (referencing customClassSwitcher,
COMPONENT_NAME, customRootClass, detach, and useCreateDataAttribute) follow the
same conditional pattern so detach fully removes generated classes.
src/components/ui/Slider/fragments/SliderRoot.tsx (1)

35-50: ⚠️ Potential issue | 🔴 Critical

detach breaks slider runtime targeting and still leaks generated class tokens.

Line 50 can make rootClass empty. Downstream code in src/components/ui/Slider/fragments/SliderRangeSlider.tsx (Lines 27-82) and src/components/ui/Slider/fragments/SliderTrack.tsx (Lines 11-19) derives selectors/classes from rootClass, causing collisions (data-slider-root="") and class tokens like -thumb/-track instead of removing generated classes.

Suggested direction (separate runtime key from CSS namespace)
+    const runtimeRootId = React.useId();
     const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
+    const rootQueryKey = detach ? runtimeRootId : rootClass;
-                data-slider-root={rootClass}
+                data-slider-root={rootQueryKey}

Then update slider fragments to use the runtime key for querying and avoid ${rootClass}-... when detached.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Slider/fragments/SliderRoot.tsx` around lines 35 - 50, The
detach flag currently makes rootClass empty which breaks runtime querying and
leaks generated class tokens; ensure SliderRoot always emits a stable runtime
key (e.g., data-slider-root or runtimeClassKey) independent of the CSS namespace
and keep customClassSwitcher(COMPONENT_NAME, detach) only for the CSS namespace;
update the SliderRangeSlider and SliderTrack fragments to query elements by that
runtime key (e.g., attribute selector or runtimeClassKey) instead of
constructing selectors using `${rootClass}-...`, and when detach is true avoid
appending generated class tokens like `-thumb`/`-track` to the CSS namespace so
tokens are not leaked. Ensure symbols involved: detach, rootClass,
customClassSwitcher, COMPONENT_NAME, SliderRangeSlider, SliderTrack and the
runtime key are created in SliderRoot and consumed by the fragments.
src/components/ui/Select/fragments/SelectRoot.tsx (1)

16-21: ⚠️ Potential issue | 🟠 Major

Empty rootClass propagates malformed class names to all Select fragments.

When detach=true, rootClass becomes '', and the context-provided value propagates to child fragments producing:

  • Line 21: ${rootClass}-root-root
  • SelectTrigger: ${rootClass}-trigger-trigger
  • SelectContent: ${rootClass}-content-content
  • SelectItem: ${rootClass}-item, ${rootClass}-text-item, -text

Either propagate detach through SelectRootContext so children can conditionally skip class application, or guard template literals against empty rootClass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Select/fragments/SelectRoot.tsx` around lines 16 - 21,
rootClass can be an empty string when detach=true, causing malformed class names
like `-root` to be emitted; change the provider and rendering to avoid emitting
suffix-only classes by either passing detach through SelectRootContext or
guarding template literals: update customClassSwitcher/SelectRoot to include
detach in the context value (e.g., provide { rootClass, detach }) and then in
child fragments (SelectTrigger, SelectContent, SelectItem) read detach from
SelectRootContext and only append suffixes when rootClass is non-empty (or
alternatively wrap `${rootClass}-...` with a conditional that returns the suffix
class only when rootClass) so ComboboxPrimitive.Root and all fragment className
interpolations no longer produce `-root`, `-trigger`, `-content`, or `-item`
when detached.
src/components/ui/Table/fragments/TableRoot.tsx (1)

20-28: ⚠️ Potential issue | 🟠 Major

-wrapper class generated when detach=true.

When detach=true, rootClass becomes '':

  • Line 26: `${rootClass}-wrapper`-wrapper (malformed)
  • Line 28: clsx(rootClass)'' (correctly empty)

The wrapper div will have an unintended -wrapper class. Consider guarding the template literal:

🛠️ Suggested fix
-    return <div ref={ref} className={clsx(`${rootClass}-wrapper`, className)} {...props} >
+    return <div ref={ref} className={clsx(rootClass && `${rootClass}-wrapper`, className)} {...props} >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Table/fragments/TableRoot.tsx` around lines 20 - 28, The
wrapper div builds a malformed `-wrapper` class when `rootClass` is empty
(detach=true); update the JSX in TableRoot.tsx so the wrapper class is only
added when rootClass is truthy (e.g., replace
className={clsx(`${rootClass}-wrapper`, className)} with a conditional such as
className={clsx(rootClass ? `${rootClass}-wrapper` : undefined, className)} or
className={clsx(rootClass && `${rootClass}-wrapper`, className)}; keep the table
className={clsx(rootClass)} unchanged and no changes to customClassSwitcher or
detach logic.
🧹 Nitpick comments (1)
src/components/ui/ToggleGroup/stories/ToggleGroup.stories.tsx (1)

18-18: Avoid hardcoding customRootClass in the shared template.
Line 18 forces customRootClass='s' for every story variant, which masks default class-generation behavior.

♻️ Suggested update
-            <ToggleGroup.Root type={args.type} className={args.className} color={args.color} customRootClass='s'>
+            <ToggleGroup.Root
+                type={args.type}
+                className={args.className}
+                color={args.color}
+                customRootClass={args.customRootClass}
+            >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/ToggleGroup/stories/ToggleGroup.stories.tsx` at line 18,
The shared story template hardcodes customRootClass='s' on ToggleGroup.Root
which forces the same class for every story and hides default class-generation;
remove the hardcoded customRootClass prop from the template (or make it driven
by args like args.customRootClass) so ToggleGroup.Root uses its default behavior
or the story-provided value—update the ToggleGroup.Root usage in the story file
to stop passing a literal 's' and instead rely on args or omit the prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ui/Accordion/fragments/AccordionRoot.tsx`:
- Line 44: The AccordionRoot currently always appends the "-root" token because
rootClass is computed unconditionally by calling
customClassSwitcher(customRootClass, COMPONENT_NAME, detach); update the logic
so that when detach is true no generated "-root" token is emitted: either change
the call site in AccordionRoot to set rootClass = '' when detach === true, or
modify customClassSwitcher to early-return an empty string when the detach flag
is true; reference the symbols AccordionRoot, customClassSwitcher,
customRootClass, COMPONENT_NAME and the detach variable when making the change
so the rootClass no longer contains the "-root" suffix in detach mode.

In `@src/components/ui/Avatar/fragments/AvatarRoot.tsx`:
- Around line 19-20: AvatarRoot can produce dash-prefixed child classes when
rootClass is empty (e.g., AvatarImage/AvatarFallback end up with
"-image"/"-fallback") because rootClass is blindly concatenated; update
AvatarRoot to ensure rootClass is non-empty before it is used to build
descendant class names (use customClassSwitcher(customRootClass, COMPONENT_NAME,
detach) but guard/normalize its return to '' when it would produce a lone dash),
and update any places that derive `${rootClass}-image` / `${rootClass}-fallback`
so they only append the dash when rootClass is truthy; focus changes in the
AvatarRoot component (variables: rootClass, customRootClass, detach, and
function customClassSwitcher) and ensure AvatarImage and AvatarFallback class
construction logic checks rootClass before adding the '-' prefix.

In `@src/components/ui/CheckboxGroup/fragments/CheckboxGroupRoot.tsx`:
- Around line 19-20: CheckboxGroupRoot builds rootClass via
customClassSwitcher(customRootClass, COMPONENT_NAME, detach) but later
unconditionally appends `${rootClass}-root`, which yields `-root` when detach
true (rootClass empty); update the JSX/className assembly in the
CheckboxGroupRoot component so the `${rootClass}-root` token is only added when
rootClass is non-empty (e.g., guard the addition based on rootClass truthiness)
and continue to respect props customRootClass and detach so no stray `-root`
class is emitted.

In `@src/components/ui/ContextMenu/fragments/ContextMenuRoot.tsx`:
- Around line 18-28: The string interpolation that appends part suffixes (e.g.,
`${rootClass}-root`) emits leading hyphens when rootClass is empty due to
detach; update ContextMenuRoot (and sibling fragments ContextMenuContent,
ContextMenuTrigger, ContextMenuItem) to only append
`-root`/`-content`/`-trigger`/`-item` when rootClass is non-empty—use the
existing customClassSwitcher/COMPONENT_NAME logic to compute rootClass and guard
the template concatenation so that when detach produces an empty rootClass you
render just `rootClass` (empty) plus any className instead of `-root` (i.e.,
conditionally join the suffix only if rootClass !== "").

In `@src/components/ui/DataList/fragments/DataListRoot.tsx`:
- Around line 11-15: The DataListRoot component's detach prop causes
customClassSwitcher to return an empty rootClass which makes child components
that build classes like `${rootClass}-value`, `${rootClass}-label`,
`${rootClass}-item` produce malformed names (e.g., `-value`); fix by either
removing the unused detach prop from DataListRoot and its type, or updating one
of two spots: (A) change customClassSwitcher so when detach=true it returns a
sensible default base class (not an empty string), or (B) update child
components that reference rootClass (the templates producing `${rootClass}-...`)
to guard against empty rootClass by using a prefix expression such as
`${rootClass ? rootClass + '-' : ''}value` so no leading hyphen is produced.
Ensure references include DataListRoot, detach, customClassSwitcher and the
child class interpolation sites.

In `@src/components/ui/Dialog/fragments/DialogRoot.tsx`:
- Around line 20-23: DialogRoot currently sets contextProps.rootClass to an
empty string when detach is true, which lets downstream fragments
(Trigger/Content/Overlay/Close) build invalid classes like "-trigger"; update
DialogRoot and the fragments so that the suffixes are only generated when
rootClass is non-empty: compute and expose either an empty string or full
suffixes (e.g., `${rootClass}-trigger`) or expose rootClass and have each
fragment guard when composing their class (use a conditional like rootClass ?
`${rootClass}-<suffix>` : ''), and apply this same guard pattern to Trigger,
Content, Overlay and Close so no leading hyphen classes are emitted when detach
is true.

In `@src/components/ui/Disclosure/fragments/DisclosureRoot.tsx`:
- Line 18: rootClass (computed by customClassSwitcher with arguments
customRootClass, COMPONENT_NAME, detach) can be an empty string causing
className templates like `${rootClass}-root` to produce `-root`; update the
DisclosureRoot render so you only append `-root` when rootClass is non-empty
(e.g., conditionally add `${rootClass}-root`), and confirm whether the
`${rootClass}-root` should be applied to RovingFocusGroup.Group or the inner div
(RovingFocusGroup.Group and the inner div currently both receive it) — remove
the duplicate application if it was unintentional and apply the conditional
logic to the className expressions for RovingFocusGroup.Group and the inner div
so no `-root` appears when detach=true.

In `@src/components/ui/DropdownMenu/fragments/DropdownMenuRoot.tsx`:
- Around line 17-21: The root currently concatenates `${rootClass}-root` even
when detach is true and rootClass is empty, producing a stray "-root"; update
DropdownMenuRoot to only append the suffix when rootClass is non-empty (e.g.,
compute the className as rootClass ? `${rootClass}-root` : undefined or omit the
generated token) before passing it to MenuPrimitive.Root and into
DropdownMenuContext.Provider, and apply the same guard in all child fragments
that construct `${rootClass}-...` class names (use the same conditional
concatenation pattern wherever rootClass is used).

In `@src/components/ui/Menubar/fragments/MenubarRoot.tsx`:
- Line 18: MenubarRoot is still appending the generated "-root" class token when
detach is true; update the construction of rootClass (and any slot class
concatenation at the slot line mentioned) so that when detach === true you do
not include the generated token from customClassSwitcher. Specifically, change
the logic around the rootClass assignment and the slot class concatenation in
MenubarRoot to use only customRootClass (and skip adding
COMPONENT_NAME-derived/generated tokens) when detach is true; keep calling
customClassSwitcher(customRootClass, COMPONENT_NAME, detach) only in the
non-detached branch so the generated "-root" class is not emitted while
detached.

In `@src/components/ui/Minimap/fragments/MinimapRoot.tsx`:
- Around line 17-18: MinimapRoot can produce an empty rootClass which causes
child fragments to emit `-item`/`-line`/`-track`; update the descendant
fragments that build class names from rootClass (currently using
`${rootClass}-item`, `${rootClass}-line`, `${rootClass}-track`) to conditionally
prefix the dash only when rootClass is non-empty (for example compute const
rootPrefix = rootClass ? `${rootClass}-` : '' and use `${rootPrefix}item` /
`${rootPrefix}line` / `${rootPrefix}track`), or otherwise use the ternary
`${rootClass ? `${rootClass}-` : ''}item` inline so no leading dash is produced.

In `@src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx`:
- Line 35: NavigationMenuRoot currently builds a slot class even when
customClassSwitcher returns an empty rootClass (so detach mode still emits a
"-root" token); update the logic in NavigationMenuRoot (around the rootClass
computation that calls customClassSwitcher with COMPONENT_NAME and detach) to
only construct the slot class string when rootClass is truthy — e.g., if
rootClass is empty, set the slot class to an empty string instead of
concatenating suffixes like "-root"; this ensures no generated "-root" token
appears in detach mode.

In `@src/components/ui/RadioGroup/fragments/RadioGroupRoot.tsx`:
- Around line 27-29: In RadioGroupRoot, the computed DOM class always includes
the literal suffix "-root" even when detach is true because rootClass can be
empty; change the className construction (where rootClass is derived via
customClassSwitcher and used to form `${rootClass}-root`) to conditionally
append the "-root" token only when rootClass is non-empty (e.g., use rootClass ?
`${rootClass}-root` : ''), preserving any passed-in className and
dataAttributes; update the code that builds the final class string used by the
component (referencing rootClass, customClassSwitcher, detach, and
COMPONENT_NAME) so no "-root" token is emitted when detached.

In `@src/components/ui/Separator/Separator.tsx`:
- Around line 33-34: The current logic builds orientationClass using rootClass
even when detach is true, causing generated fragments like "-vertical" or
"-horizontal" when customClassSwitcher(customRootClass, COMPONENT_NAME, detach)
returns an empty string; update the orientationClass computation in
Separator.tsx so it only appends `-vertical`/`-horizontal` when rootClass is
non-empty (i.e., when detach is false or customClassSwitcher returned a
non-empty rootClass); keep the existing variables (customClassSwitcher,
customRootClass, COMPONENT_NAME, detach, rootClass, orientationClass,
orientation) and guard the concatenation so no leading hyphen fragments are
produced in detach mode.

In `@src/components/ui/Steps/fragments/StepRoot.tsx`:
- Around line 34-37: rootClass can be an empty string when detach=true which
produces malformed class names like "-horizontal"; update the JSX and context so
suffixes are only added when rootClass is truthy and propagate the detach state
to children: in the component that computes rootClass (using customClassSwitcher
and COMPONENT_NAME) compute conditional classes (e.g., orientationClass =
rootClass ? `${rootClass}-${orientation}` : '' and itemClass = rootClass ?
`${rootClass}-item` : '') and pass either the boolean detach or the computed
rootClass/itemClass into StepsContext.Provider (value: { currentStep,
setCurrentStep, rootClass, orientation, detachOrItemClass }) so child fragments
(e.g., StepItem) can avoid rendering `${rootClass}-item` when rootClass is
empty. Ensure all template usages of `${rootClass}-...` check rootClass
truthiness before concatenation.

In `@src/components/ui/TabNav/fragments/TabNavRoot.tsx`:
- Line 26: The child slot class (`${rootClass}-link`) can become `-link` when
rootClass is empty in detach mode; fix by making TabNavRoot provide detach-aware
class info to descendants: compute safe slot classes (e.g., slotClasses.link =
detach ? '' : `${rootClass}-link`) in TabNavRoot (where rootClass is created via
customClassSwitcher and COMPONENT_NAME) and pass either the detach flag or the
precomputed slotClasses through the existing context/provider so TabNavLink (and
other fragments) use the context value instead of deriving `${rootClass}-link`
themselves; ensure the same change is applied to the other slot constructions
mentioned around the later block (48-52) so no generated classes are emitted in
detach mode.

In `@src/components/ui/Toolbar/fragments/ToolbarRoot.tsx`:
- Around line 35-36: rootClass can be empty when detach is true, but child
fragments that build classes by suffixing `${rootClass}-button` still emit
leftover "-button" classes; update the child fragments that suffix rootClass to
guard against falsy rootClass (use the same detach check used in
customClassSwitcher/customRootClass/COMPONENT_NAME logic) so they only append
`-button` (or other suffixes) when rootClass is truthy—locate usages that
reference rootClass in toolbar subcomponents and change them to compute suffix
classes only if rootClass is non-empty.

In `@src/components/ui/Tree/fragments/TreeRoot.tsx`:
- Around line 16-24: The TreeItem component currently interpolates rootClass
into className as clsx(`${rootClass}-item`, className), which yields "-item"
when customClassSwitcher returned an empty string (detach=true); update TreeItem
(component TreeItem) to guard the interpolation by using conditional logic so
the item class is only added when rootClass is truthy (e.g., use clsx(rootClass
&& `${rootClass}-item`, className)), referencing the rootClass produced in
TreeRoot via customClassSwitcher to avoid emitting a leading dash when detached.

In `@src/core/customClassSwitcher/index.ts`:
- Around line 7-10: customClassSwitcher currently returns an empty string when
detach=true which leads to malformed child class names like `-item`; change the
contract so customClassSwitcher(customRootClass, componentName, detach) returns
a clear sentinel (e.g., null/undefined) when detach is requested and then update
all child fragments that use the returned value (places that build classes with
template literals such as `${rootClass}-item`, `${rootClass}-trigger`,
`${rootClass}-wrapper`) to conditionally omit the entire class attribute when
rootClass is the sentinel. Alternatively, if you prefer propagation, pass the
detach boolean through context (from where customClassSwitcher is called) so
child components can check detach and skip assigning class names instead of
relying on an empty string.

---

Outside diff comments:
In `@src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx`:
- Line 28: The generated classnames use template literals like
`${rootClass}-root` which produce invalid names when customClassSwitcher returns
an empty string; update each occurrence to only append the suffix when rootClass
is truthy (e.g., change clsx(`${rootClass}-root`, rootClass, className) to
clsx(rootClass && `${rootClass}-root`, rootClass, className)) so no leading
hyphen is emitted; apply the same pattern in CheckboxCardsRoot
(CheckboxGroupPrimitive.Root), CheckboxCardsItem (the item class),
CheckboxCardsIndicator (the tick-icon class) and CheckboxCardsContent (the
content class).

In `@src/components/ui/Combobox/fragments/ComboboxRoot.tsx`:
- Around line 15-21: The Combobox is leaking a generated class when detach is
true because `${rootClass}-root` becomes `-root` if rootClass is empty; change
the class construction in the ComboboxRoot component (where you use
customClassSwitcher, rootClass, and render ComboboxPrimitive.Root inside
ComboboxRootContext.Provider) to only append the `-root` suffix when rootClass
is non-empty (e.g., return an empty string/undefined or omit the className when
rootClass === ''), and apply the identical guard pattern to all child fragments
that build `${rootClass}-*` (e.g., item, text) so no `-*` classes are emitted in
detach mode.

In `@src/components/ui/Select/fragments/SelectRoot.tsx`:
- Around line 16-21: rootClass can be an empty string when detach=true, causing
malformed class names like `-root` to be emitted; change the provider and
rendering to avoid emitting suffix-only classes by either passing detach through
SelectRootContext or guarding template literals: update
customClassSwitcher/SelectRoot to include detach in the context value (e.g.,
provide { rootClass, detach }) and then in child fragments (SelectTrigger,
SelectContent, SelectItem) read detach from SelectRootContext and only append
suffixes when rootClass is non-empty (or alternatively wrap `${rootClass}-...`
with a conditional that returns the suffix class only when rootClass) so
ComboboxPrimitive.Root and all fragment className interpolations no longer
produce `-root`, `-trigger`, `-content`, or `-item` when detached.

In `@src/components/ui/Slider/fragments/SliderRoot.tsx`:
- Around line 35-50: The detach flag currently makes rootClass empty which
breaks runtime querying and leaks generated class tokens; ensure SliderRoot
always emits a stable runtime key (e.g., data-slider-root or runtimeClassKey)
independent of the CSS namespace and keep customClassSwitcher(COMPONENT_NAME,
detach) only for the CSS namespace; update the SliderRangeSlider and SliderTrack
fragments to query elements by that runtime key (e.g., attribute selector or
runtimeClassKey) instead of constructing selectors using `${rootClass}-...`, and
when detach is true avoid appending generated class tokens like
`-thumb`/`-track` to the CSS namespace so tokens are not leaked. Ensure symbols
involved: detach, rootClass, customClassSwitcher, COMPONENT_NAME,
SliderRangeSlider, SliderTrack and the runtime key are created in SliderRoot and
consumed by the fragments.

In `@src/components/ui/Spinner/Spinner.tsx`:
- Around line 17-23: The Spinner always appends "-container" to rootClass even
when customClassSwitcher returns an empty string (when detach is true),
producing a stray "-container" class; update the Spinner render logic (in the
React.forwardRef component named Spinner) to only append the "-container" suffix
when rootClass is non-empty (e.g., conditionally build the container class using
rootClass ? `${rootClass}-container` : '' or omit the class attribute), and
ensure any other places that concatenate rootClass (referencing
customClassSwitcher, COMPONENT_NAME, customRootClass, detach, and
useCreateDataAttribute) follow the same conditional pattern so detach fully
removes generated classes.

In `@src/components/ui/Splitter/fragments/SplitterRoot.tsx`:
- Around line 10-44: When detach is true customClassSwitcher can return an empty
string causing rootClass to be falsy and yielding invalid class names like
"-panel" and "-handle"; update SplitterRoot rendering logic so className
concatenation for child components (SplitterPanel, SplitterHandle) only appends
suffixes when rootClass is non-empty (e.g. use conditional expressions with
rootClass before `${rootClass}-panel` / `${rootClass}-handle`), keep using clsx
for merging but ensure you pass either the full suffixed string or nothing when
rootClass is empty to prevent leading-hyphen class names.

In `@src/components/ui/Switch/fragments/SwitchRoot.tsx`:
- Around line 20-57: The SwitchThumb className improperly interpolates rootClass
when detach is true (rootClass becomes an empty string) causing an invalid
"-indicator" class; update the SwitchThumb component to conditionally prepend
`${rootClass}-indicator` only when rootClass is non-empty (use the rootClass
value computed in SwitchRoot via customClassSwitcher) and fall back to the
original className when rootClass is falsy, and audit other child components
that concatenate rootClass similarly to apply the same conditional pattern.

In `@src/components/ui/Table/fragments/TableRoot.tsx`:
- Around line 20-28: The wrapper div builds a malformed `-wrapper` class when
`rootClass` is empty (detach=true); update the JSX in TableRoot.tsx so the
wrapper class is only added when rootClass is truthy (e.g., replace
className={clsx(`${rootClass}-wrapper`, className)} with a conditional such as
className={clsx(rootClass ? `${rootClass}-wrapper` : undefined, className)} or
className={clsx(rootClass && `${rootClass}-wrapper`, className)}; keep the table
className={clsx(rootClass)} unchanged and no changes to customClassSwitcher or
detach logic.

---

Nitpick comments:
In `@src/components/ui/ToggleGroup/stories/ToggleGroup.stories.tsx`:
- Line 18: The shared story template hardcodes customRootClass='s' on
ToggleGroup.Root which forces the same class for every story and hides default
class-generation; remove the hardcoded customRootClass prop from the template
(or make it driven by args like args.customRootClass) so ToggleGroup.Root uses
its default behavior or the story-provided value—update the ToggleGroup.Root
usage in the story file to stop passing a literal 's' and instead rely on args
or omit the prop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e579faa-df87-4d32-8481-081073c7f451

📥 Commits

Reviewing files that changed from the base of the PR and between 38503b6 and 4dab9b1.

📒 Files selected for processing (61)
  • .changeset/floppy-spoons-appear.md
  • src/components/ui/Accordion/fragments/AccordionRoot.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogRoot.tsx
  • src/components/ui/AspectRatio/AspectRatio.tsx
  • src/components/ui/Avatar/fragments/AvatarRoot.tsx
  • src/components/ui/AvatarGroup/fragments/AvatarGroupRoot.tsx
  • src/components/ui/Badge/Badge.tsx
  • src/components/ui/BlockQuote/BlockQuote.tsx
  • src/components/ui/Button/Button.tsx
  • src/components/ui/Callout/fragments/CalloutRoot.tsx
  • src/components/ui/Card/fragments/CardRoot.tsx
  • src/components/ui/Checkbox/fragments/CheckboxRoot.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx
  • src/components/ui/CheckboxGroup/fragments/CheckboxGroupRoot.tsx
  • src/components/ui/Code/Code.tsx
  • src/components/ui/Collapsible/fragments/CollapsibleRoot.tsx
  • src/components/ui/Combobox/fragments/ComboboxRoot.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuRoot.tsx
  • src/components/ui/DataList/fragments/DataListRoot.tsx
  • src/components/ui/Dialog/fragments/DialogRoot.tsx
  • src/components/ui/Disclosure/fragments/DisclosureRoot.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuRoot.tsx
  • src/components/ui/Em/Em.tsx
  • src/components/ui/Heading/Heading.tsx
  • src/components/ui/HoverCard/fragments/HoverCardRoot.tsx
  • src/components/ui/Kbd/Kbd.tsx
  • src/components/ui/Link/Link.tsx
  • src/components/ui/Menubar/fragments/MenubarRoot.tsx
  • src/components/ui/Minimap/fragments/MinimapRoot.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx
  • src/components/ui/Progress/fragments/ProgressRoot.tsx
  • src/components/ui/Quote/Quote.tsx
  • src/components/ui/Radio/Radio.tsx
  • src/components/ui/RadioCards/fragments/RadioCardsRoot.tsx
  • src/components/ui/RadioGroup/fragments/RadioGroupRoot.tsx
  • src/components/ui/ScrollArea/fragments/ScrollAreaRoot.tsx
  • src/components/ui/Select/fragments/SelectRoot.tsx
  • src/components/ui/Separator/Separator.tsx
  • src/components/ui/Skeleton/Skeleton.tsx
  • src/components/ui/Slider/fragments/SliderRoot.tsx
  • src/components/ui/Spinner/Spinner.tsx
  • src/components/ui/Splitter/fragments/SplitterRoot.tsx
  • src/components/ui/Steps/fragments/StepRoot.tsx
  • src/components/ui/Strong/Strong.tsx
  • src/components/ui/Switch/fragments/SwitchRoot.tsx
  • src/components/ui/TabNav/fragments/TabNavRoot.tsx
  • src/components/ui/Table/fragments/TableRoot.tsx
  • src/components/ui/Tabs/fragments/TabContent.tsx
  • src/components/ui/Tabs/fragments/TabRoot.tsx
  • src/components/ui/Text/Text.tsx
  • src/components/ui/TextArea/fragments/TextAreaRoot.tsx
  • src/components/ui/Toggle/Toggle.tsx
  • src/components/ui/Toggle/tests/Toggle.test.js
  • src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx
  • src/components/ui/ToggleGroup/stories/ToggleGroup.stories.tsx
  • src/components/ui/Toolbar/fragments/ToolbarRoot.tsx
  • src/components/ui/Tree/fragments/TreeRoot.tsx
  • src/components/ui/VisuallyHidden/VisuallyHidden.tsx
  • src/core/customClassSwitcher/customClassSwitcher.test.tsx
  • src/core/customClassSwitcher/index.ts
  • src/core/utils/mergeRefs.ts

}, forwardedRef) => {
const accordionRef = useRef<HTMLDivElement | null>(null);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Detach mode still outputs -root class in AccordionRoot.

Line 149 unconditionally appends -root. With detach=true (Line 44 path), this still emits a generated token (-root) instead of no class output.

Also applies to: 149-149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Accordion/fragments/AccordionRoot.tsx` at line 44, The
AccordionRoot currently always appends the "-root" token because rootClass is
computed unconditionally by calling customClassSwitcher(customRootClass,
COMPONENT_NAME, detach); update the logic so that when detach is true no
generated "-root" token is emitted: either change the call site in AccordionRoot
to set rootClass = '' when detach === true, or modify customClassSwitcher to
early-return an empty string when the detach flag is true; reference the symbols
AccordionRoot, customClassSwitcher, customRootClass, COMPONENT_NAME and the
detach variable when making the change so the rootClass no longer contains the
"-root" suffix in detach mode.

Comment on lines +19 to +20
const AvatarRoot = React.forwardRef<React.ElementRef<typeof AvatarPrimitiveRoot>, AvatarRootProps>(({ children, customRootClass = '', detach = false, className = '', size = '', variant = '', color = '', ...props }, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

detach currently leaks dash-prefixed Avatar child classes.

When Line [20] yields rootClass = '', AvatarImage / AvatarFallback still build ${rootClass}-image and ${rootClass}-fallback, producing -image / -fallback instead of no generated classes.

Proposed fix for descendant class guards
# src/components/ui/Avatar/fragments/AvatarImage.tsx
- className={clsx(`${rootClass}-image`)}
+ className={clsx(rootClass && `${rootClass}-image`)}

# src/components/ui/Avatar/fragments/AvatarFallback.tsx
- className={clsx(`${rootClass}-fallback`, className)}
+ className={clsx(rootClass && `${rootClass}-fallback`, className)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Avatar/fragments/AvatarRoot.tsx` around lines 19 - 20,
AvatarRoot can produce dash-prefixed child classes when rootClass is empty
(e.g., AvatarImage/AvatarFallback end up with "-image"/"-fallback") because
rootClass is blindly concatenated; update AvatarRoot to ensure rootClass is
non-empty before it is used to build descendant class names (use
customClassSwitcher(customRootClass, COMPONENT_NAME, detach) but guard/normalize
its return to '' when it would produce a lone dash), and update any places that
derive `${rootClass}-image` / `${rootClass}-fallback` so they only append the
dash when rootClass is truthy; focus changes in the AvatarRoot component
(variables: rootClass, customRootClass, detach, and function
customClassSwitcher) and ensure AvatarImage and AvatarFallback class
construction logic checks rootClass before adding the '-' prefix.

Comment on lines +19 to +20
const CheckboxGroupRoot = forwardRef<CheckboxGroupRootElement, CheckboxGroupRootProps>(({ children, customRootClass = '', detach = false, className = '', color = '', variant = '', size = '', ...props }, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

detach=true still emits a generated -root class.

Line [20] can set rootClass to empty, but Line [28] always adds ${rootClass}-root, which becomes -root.

Proposed fix
- <CheckboxGroupPrimitive.Root ref={ref} className={clsx(`${rootClass}-root`, rootClass, className)} {...props} {...composedAttributes()}>
+ <CheckboxGroupPrimitive.Root
+   ref={ref}
+   className={clsx(rootClass && `${rootClass}-root`, rootClass, className)}
+   {...props}
+   {...composedAttributes()}
+ >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/CheckboxGroup/fragments/CheckboxGroupRoot.tsx` around lines
19 - 20, CheckboxGroupRoot builds rootClass via
customClassSwitcher(customRootClass, COMPONENT_NAME, detach) but later
unconditionally appends `${rootClass}-root`, which yields `-root` when detach
true (rootClass empty); update the JSX/className assembly in the
CheckboxGroupRoot component so the `${rootClass}-root` token is only added when
rootClass is non-empty (e.g., guard the addition based on rootClass truthiness)
and continue to respect props customRootClass and detach so no stray `-root`
class is emitted.

Comment on lines +18 to 28
const ContextMenuRoot = forwardRef<ContextMenuRootElement, ContextMenuRootProps>(({ children, customRootClass, detach = false, className, open, defaultOpen = false, onOpenChange, ...props }, ref) => {
const [isOpen, setIsOpen] = useControllableState(
open,
defaultOpen,
onOpenChange
);
const [coords, setCoords] = React.useState({ x: 0, y: 0 });
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
return (
<ContextMenuContext.Provider value={{ rootClass, setCoords, setIsOpen }} >
<MenuPrimitive.Root ref={ref} className={clsx(`${rootClass}-root`, className)} mainAxisOffset={-coords.y} crossAxisOffset={coords.x} open={isOpen} onOpenChange={setIsOpen} {...props}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Detach is incomplete: generated class tokens are still emitted.

Line 28 builds ${rootClass}-root; when detached this becomes -root. The same pattern exists in ContextMenuContent, ContextMenuTrigger, and ContextMenuItem, producing -content/-trigger/-item. That violates the requested detach behavior.

Proposed root-level fix
-            <MenuPrimitive.Root ref={ref} className={clsx(`${rootClass}-root`, className)} mainAxisOffset={-coords.y} crossAxisOffset={coords.x} open={isOpen} onOpenChange={setIsOpen} {...props}>
+            <MenuPrimitive.Root ref={ref} className={clsx(rootClass && `${rootClass}-root`, className)} mainAxisOffset={-coords.y} crossAxisOffset={coords.x} open={isOpen} onOpenChange={setIsOpen} {...props}>

Please mirror this guard in the sibling fragments that append -${part} to rootClass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/ContextMenu/fragments/ContextMenuRoot.tsx` around lines 18
- 28, The string interpolation that appends part suffixes (e.g.,
`${rootClass}-root`) emits leading hyphens when rootClass is empty due to
detach; update ContextMenuRoot (and sibling fragments ContextMenuContent,
ContextMenuTrigger, ContextMenuItem) to only append
`-root`/`-content`/`-trigger`/`-item` when rootClass is non-empty—use the
existing customClassSwitcher/COMPONENT_NAME logic to compute rootClass and guard
the template concatenation so that when detach produces an empty rootClass you
render just `rootClass` (empty) plus any className instead of `-root` (i.e.,
conditionally join the suffix only if rootClass !== "").

Comment on lines +11 to +15
detach?: boolean;
}

const DataListRoot = forwardRef<DataListRootElement, DataListRootProps>(({ children, className = '', customRootClass = '', ...props }, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const DataListRoot = forwardRef<DataListRootElement, DataListRootProps>(({ children, className = '', customRootClass = '', detach = false, ...props }, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for string interpolation patterns with rootClass in DataList
rg -n '\$\{rootClass\}' src/components/ui/DataList/

Repository: rad-ui/ui

Length of output: 535


🏁 Script executed:

#!/bin/bash
# Find customClassSwitcher implementation to understand what it returns
rg -n 'customClassSwitcher' src/components/ui/DataList/ -A 3 -B 3

Repository: rad-ui/ui

Length of output: 1360


🏁 Script executed:

#!/bin/bash
# Find the definition of customClassSwitcher function
fd -t f -e tsx -e ts . | xargs rg -l 'export.*customClassSwitcher|const customClassSwitcher|function customClassSwitcher'

Repository: rad-ui/ui

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Check DataListRoot to see what COMPONENT_NAME and customRootClass values might be
rg -n 'COMPONENT_NAME\|customRootClass' src/components/ui/DataList/fragments/DataListRoot.tsx -B 2 -A 2

Repository: rad-ui/ui

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Read the customClassSwitcher implementation
cat src/core/customClassSwitcher/index.ts

Repository: rad-ui/ui

Length of output: 779


🏁 Script executed:

#!/bin/bash
# Also check what's exported from core to understand the function signature
head -50 src/core/index.ts

Repository: rad-ui/ui

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Search for usage of DataList component with detach prop
rg -n 'detach\s*=' src/components/ui/DataList/ -A 2 -B 2

Repository: rad-ui/ui

Length of output: 622


🏁 Script executed:

#!/bin/bash
# Search for DataList usage in stories or examples
fd -t f -e tsx -e ts . | xargs rg -l 'DataList' | head -20

Repository: rad-ui/ui

Length of output: 528


🏁 Script executed:

#!/bin/bash
# Check if there are tests for DataList detach behavior
fd -t f \( -name '*.test.tsx' -o -name '*.spec.tsx' -o -name '*.stories.tsx' \) | xargs rg -l 'DataList' 2>/dev/null

Repository: rad-ui/ui

Length of output: 899


🏁 Script executed:

#!/bin/bash
# Check test file for detach usage
rg -n 'detach' src/components/ui/DataList/tests/DataList.test.tsx

Repository: rad-ui/ui

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check stories file for detach examples
rg -n 'detach' src/components/ui/DataList/stories/DataList.stories.tsx

Repository: rad-ui/ui

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Read the child component implementations to confirm the pattern
cat src/components/ui/DataList/fragments/DataListValue.tsx

Repository: rad-ui/ui

Length of output: 706


🏁 Script executed:

#!/bin/bash
# Check the README or docs for DataList component purpose and detach usage
fd -t f \( -name 'README*' -o -name '*.md' \) . | xargs rg -l 'DataList' | head -5

Repository: rad-ui/ui

Length of output: 573


🏁 Script executed:

#!/bin/bash
# Look at all child components to see the pattern consistency
cat src/components/ui/DataList/fragments/DataListLabel.tsx src/components/ui/DataList/fragments/DataListItem.tsx

Repository: rad-ui/ui

Length of output: 1355


🏁 Script executed:

#!/bin/bash
# Check if detach is exported or documented in the public API
rg -n 'detach' src/components/ui/DataList/DataList.tsx

Repository: rad-ui/ui

Length of output: 35


Malformed CSS class names when detach=true is used.

The implementation produces invalid class names. When detach=true, customClassSwitcher returns an empty string, causing child components to generate malformed classes like -value, -label, -item (with leading hyphens). This occurs because all child components use ${rootClass}-suffix interpolation without handling the empty rootClass case.

Since detach is not used in tests, stories, or the public API, either:

  1. Remove the unused detach property entirely, or
  2. Fix child components to handle empty rootClass (e.g., ${rootClass ? rootClass + '-' : ''}value), or
  3. Modify customClassSwitcher to return a sensible default instead of empty string when detach=true.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/DataList/fragments/DataListRoot.tsx` around lines 11 - 15,
The DataListRoot component's detach prop causes customClassSwitcher to return an
empty rootClass which makes child components that build classes like
`${rootClass}-value`, `${rootClass}-label`, `${rootClass}-item` produce
malformed names (e.g., `-value`); fix by either removing the unused detach prop
from DataListRoot and its type, or updating one of two spots: (A) change
customClassSwitcher so when detach=true it returns a sensible default base class
(not an empty string), or (B) update child components that reference rootClass
(the templates producing `${rootClass}-...`) to guard against empty rootClass by
using a prefix expression such as `${rootClass ? rootClass + '-' : ''}value` so
no leading hyphen is produced. Ensure references include DataListRoot, detach,
customClassSwitcher and the child class interpolation sites.

Comment on lines +34 to 37
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);

return <StepsContext.Provider value={{ currentStep, setCurrentStep, rootClass, orientation }}>
<Primitive.div className={clsx(rootClass, className, `${rootClass}-${orientation}`)} data-orientation={orientation} {...props}>{children}</Primitive.div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Empty rootClass produces malformed class names when detach=true.

When detach=true, rootClass becomes '', causing:

  • Line 37: `${rootClass}-${orientation}`-horizontal or -vertical
  • Child StepItem (from context snippet): `${rootClass}-item`-item

These are unintended CSS class names rather than the "no class output" behavior requested in issue #1513. The detach flag or empty-rootClass check needs to propagate to child fragments and this template usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Steps/fragments/StepRoot.tsx` around lines 34 - 37,
rootClass can be an empty string when detach=true which produces malformed class
names like "-horizontal"; update the JSX and context so suffixes are only added
when rootClass is truthy and propagate the detach state to children: in the
component that computes rootClass (using customClassSwitcher and COMPONENT_NAME)
compute conditional classes (e.g., orientationClass = rootClass ?
`${rootClass}-${orientation}` : '' and itemClass = rootClass ?
`${rootClass}-item` : '') and pass either the boolean detach or the computed
rootClass/itemClass into StepsContext.Provider (value: { currentStep,
setCurrentStep, rootClass, orientation, detachOrItemClass }) so child fragments
(e.g., StepItem) can avoid rendering `${rootClass}-item` when rootClass is
empty. Ensure all template usages of `${rootClass}-...` check rootClass
truthiness before concatenation.

value, ...props
}, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

TabNav detach is incomplete for child slots (-link leak).

Line 26 can produce empty rootClass, but children derive classes from context using ${rootClass}-link (src/components/ui/TabNav/fragments/TabNavLink.tsx, Line 45). That results in -link when detached.

Please propagate detach-aware slot class construction (e.g., pass detach in context or pass precomputed safe slot classes) so descendants output no generated classes in detach mode.

Also applies to: 48-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/TabNav/fragments/TabNavRoot.tsx` at line 26, The child slot
class (`${rootClass}-link`) can become `-link` when rootClass is empty in detach
mode; fix by making TabNavRoot provide detach-aware class info to descendants:
compute safe slot classes (e.g., slotClasses.link = detach ? '' :
`${rootClass}-link`) in TabNavRoot (where rootClass is created via
customClassSwitcher and COMPONENT_NAME) and pass either the detach flag or the
precomputed slotClasses through the existing context/provider so TabNavLink (and
other fragments) use the context value instead of deriving `${rootClass}-link`
themselves; ensure the same change is applied to the other slot constructions
mentioned around the later block (48-52) so no generated classes are emitted in
detach mode.

Comment on lines +35 to 36
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
const context = React.useMemo(() => ({ rootClass, orientation, dir }), [rootClass, orientation, dir]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Toolbar detach currently produces leftover -button class names.

detach sets rootClass to empty, but child fragments suffix it (${rootClass}-button), which still emits generated-style classes and breaks the detach contract.

Suggested fix direction
// src/components/ui/Toolbar/fragments/ToolbarRoot.tsx
- const context = React.useMemo(() => ({ rootClass, orientation, dir }), [rootClass, orientation, dir]);
+ const context = React.useMemo(() => ({ rootClass, detach, orientation, dir }), [rootClass, detach, orientation, dir]);
// src/components/ui/Toolbar/fragments/ToolbarButton.tsx
- className={clsx(`${rootClass}-button`, className)}
+ className={clsx(detach ? undefined : `${rootClass}-button`, className)}

Use the same guard in other Toolbar subcomponents that suffix rootClass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Toolbar/fragments/ToolbarRoot.tsx` around lines 35 - 36,
rootClass can be empty when detach is true, but child fragments that build
classes by suffixing `${rootClass}-button` still emit leftover "-button"
classes; update the child fragments that suffix rootClass to guard against falsy
rootClass (use the same detach check used in
customClassSwitcher/customRootClass/COMPONENT_NAME logic) so they only append
`-button` (or other suffixes) when rootClass is truthy—locate usages that
reference rootClass in toolbar subcomponents and change them to compute suffix
classes only if rootClass is non-empty.

Comment on lines +16 to +24
detach?: boolean;
'aria-label'?: string;
'aria-labelledby'?: string;
} & ComponentPropsWithoutRef<typeof Primitive.div>;

const TreeRoot = forwardRef<TreeRootElement, TreeRootProps>(({ children, className = '', customRootClass = '', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }, ref) => {
const TreeRoot = forwardRef<TreeRootElement, TreeRootProps>(({ children, className = '', customRootClass = '', detach = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }, ref) => {
const treeRef = useRef<TreeRootElement>(null);
useImperativeHandle(ref, () => treeRef.current as TreeRootElement);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME, detach);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for string interpolation patterns with rootClass in Tree fragments
rg -n '\$\{rootClass\}' src/components/ui/Tree/

Repository: rad-ui/ui

Length of output: 170


🏁 Script executed:

# Find customClassSwitcher function definition
rg -n 'customClassSwitcher' src/components/ui/Tree/ -B 2 -A 2

Repository: rad-ui/ui

Length of output: 967


🏁 Script executed:

# Get full context of TreeItem.tsx around line 79
cat -n src/components/ui/Tree/fragments/TreeItem.tsx | sed -n '70,90p'

Repository: rad-ui/ui

Length of output: 1083


🏁 Script executed:

# Find the customClassSwitcher function definition to understand its return value
fd -e ts -e tsx | xargs grep -l "customClassSwitcher" | head -5

Repository: rad-ui/ui

Length of output: 324


🏁 Script executed:

# Find customClassSwitcher implementation in the core module
fd -e ts -e tsx -e js src/ | xargs grep -l "export.*customClassSwitcher" | head -1

Repository: rad-ui/ui

Length of output: 374


🏁 Script executed:

# Also check the core directory structure
find src -type d -name "core" | head -3

Repository: rad-ui/ui

Length of output: 61


🏁 Script executed:

# Find customClassSwitcher in src/core
rg -n "export.*customClassSwitcher" src/core/

Repository: rad-ui/ui

Length of output: 301


🏁 Script executed:

# Or search for the function definition
rg -n "customClassSwitcher" src/core/ -A 10 | head -50

Repository: rad-ui/ui

Length of output: 4389


🏁 Script executed:

# Check if there are other similar string interpolation patterns in Tree components
rg -n '\$\{rootClass\}-' src/components/ui/Tree/

Repository: rad-ui/ui

Length of output: 170


Fix string interpolation pattern in TreeItem to handle empty rootClass when detached.

TreeItem.tsx:79 uses className={clsx(\${rootClass}-item`, className)}, which produces the unwanted class name -itemwhendetach=true(sincecustomClassSwitcherreturns an empty string). Use conditional logic instead:clsx(rootClass && `${rootClass}-item`, className)`.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Tree/fragments/TreeRoot.tsx` around lines 16 - 24, The
TreeItem component currently interpolates rootClass into className as
clsx(`${rootClass}-item`, className), which yields "-item" when
customClassSwitcher returned an empty string (detach=true); update TreeItem
(component TreeItem) to guard the interpolation by using conditional logic so
the item class is only added when rootClass is truthy (e.g., use clsx(rootClass
&& `${rootClass}-item`, className)), referencing the rootClass produced in
TreeRoot via customClassSwitcher to avoid emitting a leading dash when detached.

Comment on lines +7 to +10
export const customClassSwitcher = (customRootClass: string = '', componentName: string = '', detach: boolean = false): string => {
if (detach) {
return '';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all child fragment components that use ${rootClass}- pattern
rg -n '\$\{rootClass\}-' --type=tsx --type=ts -g '!*.test.*' | head -50

Repository: rad-ui/ui

Length of output: 80


🏁 Script executed:

#!/bin/bash
# First, let's look at the actual implementation
echo "=== customClassSwitcher implementation ==="
cat -n src/core/customClassSwitcher/index.ts

echo -e "\n=== Searching for ${rootClass}- pattern (corrected) ==="
# Search without specifying tsx type, just use ts and check all files
rg '\$\{rootClass\}-' --type=ts -g '!*.test.*' | head -60

Repository: rad-ui/ui

Length of output: 9062


🏁 Script executed:

#!/bin/bash
echo "=== Examining AvatarGroupItem to see how rootClass is obtained ==="
cat -n src/components/ui/AvatarGroup/fragments/AvatarGroupItem.tsx

echo -e "\n=== Looking for context or hook that provides rootClass ==="
rg 'rootClass' src/components/ui/AvatarGroup/ --type=ts -B2 -A2 | head -80

Repository: rad-ui/ui

Length of output: 5136


🏁 Script executed:

#!/bin/bash
echo "=== Checking AvatarGroupContext definition ==="
cat -n src/components/ui/AvatarGroup/contexts/AvatarGroupContext.tsx

echo -e "\n=== Verifying one more component type for consistency ==="
cat -n src/components/ui/Accordion/fragments/AccordionItem.tsx

Repository: rad-ui/ui

Length of output: 3202


Returning empty string causes malformed class names in child components.

When detach=true, this returns '', but child components throughout the codebase use template literals like `${rootClass}-item`, `${rootClass}-trigger`, `${rootClass}-wrapper`, etc. This produces invalid class names such as -item, -trigger, -wrapper instead of no classes at all.

The detach parameter is not propagated through context to child components, so they cannot conditionally omit class assignments. Per issue #1513, the intent is to suppress class generation entirely. Consider either:

  1. Returning a sentinel value and updating child fragments to conditionally omit class attributes
  2. Propagating detach through context so child components can skip class assignment entirely
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/customClassSwitcher/index.ts` around lines 7 - 10,
customClassSwitcher currently returns an empty string when detach=true which
leads to malformed child class names like `-item`; change the contract so
customClassSwitcher(customRootClass, componentName, detach) returns a clear
sentinel (e.g., null/undefined) when detach is requested and then update all
child fragments that use the returned value (places that build classes with
template literals such as `${rootClass}-item`, `${rootClass}-trigger`,
`${rootClass}-wrapper`) to conditionally omit the entire class attribute when
rootClass is the sentinel. Alternatively, if you prefer propagation, pass the
detach boolean through context (from where customClassSwitcher is called) so
child components can check detach and skip assigning class names instead of
relying on an empty string.

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.

[New Feature] Detach prop support

1 participant