Skip to content

Kotapi/refactor unstyled first behaviiour#1895

Merged
kotAPI merged 9 commits intomainfrom
kotapi/refactor-unstyled-first-behaviiour
Apr 16, 2026
Merged

Kotapi/refactor unstyled first behaviiour#1895
kotAPI merged 9 commits intomainfrom
kotapi/refactor-unstyled-first-behaviiour

Conversation

@kotAPI
Copy link
Copy Markdown
Collaborator

@kotAPI kotAPI commented Apr 16, 2026

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added comprehensive guides covering semantic data attributes, styling namespaces, headless component principles, and CSS best practices.
  • New Features

    • Theme component now supports optional classNamespace prop for namespaced CSS class generation.
    • Introduced new styling infrastructure for consistent class naming across components.
  • Refactor

    • Updated styling system to conditionally apply CSS classes only when a namespace is configured.
    • Enhanced component styling compatibility with headless and class-based design patterns.
  • Tests

    • Added test coverage for Theme namespace functionality and conditional class application.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 16, 2026

⚠️ No Changeset found

Latest commit: c25d146

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • src/components/ui/Slider/tests/__snapshots__/Slider.test.tsx.snap is excluded by !**/*.snap
  • src/components/ui/TextArea/__snapshots__/TextArea.test.tsx.snap is excluded by !**/*.snap

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 537cb11f-642e-42d4-8882-7fa60894e1bd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request refactors component class generation by introducing a new useComponentClass hook that derives component CSS classes from a Theme context's classNamespace prop, replacing scattered customClassSwitcher usage. It updates ~150+ components to conditionally guard against falsy rootClass values, adds comprehensive headless-component documentation, and extends the Theme component to provide class namespace configuration.

Changes

Cohort / File(s) Summary
Documentation
knowledge/features/data-attributes.md, knowledge/features/styling-namespaces.md, knowledge/what-makes-a-library-truly-headless.md, knowledge/good_practices/index.md
Added four new documentation pages defining semantic data-attribute contracts, styling namespace behavior, headless library principles, and best practices for deterministic SSR/hydration, class namespace generation, and DOM hygiene.
Theme Infrastructure
src/components/ui/Theme/Theme.tsx, src/components/ui/Theme/ThemeContext.tsx, src/components/ui/Theme/useComponentClass.ts, src/components/ui/Theme/tests/Theme.test.tsx
Extended Theme component to accept optional classNamespace prop and expose it via ThemeContext; added new useComponentClass hook that derives component class names from theme context, converting component names to dash-case and prefixing with classNamespace.
Core Utility Update
src/core/customClassSwitcher/index.ts, src/core/customClassSwitcher/customClassSwitcher.test.tsx
Removed hardcoded rad-ui- prefix from customClassSwitcher, now requires explicit customRootClass input; returns empty string when either input is falsy; deleted comprehensive test suite for deprecated function.
SandboxEditor
src/components/tools/SandboxEditor/SandboxEditor.tsx
Updated to provide classNamespace="rad-ui" to Theme component alongside existing props.
Accordion Fragments
src/components/ui/Accordion/fragments/AccordionRoot.tsx, src/components/ui/Accordion/fragments/Accordion*.tsx, src/components/ui/Accordion/tests/Accordion.test.tsx
Replaced customClassSwitcher with useComponentClass in root; guarded all fragment class names (-content, -header, -item, -trigger) to only apply when rootClass is truthy; added tests validating classless rendering and Theme namespace-based class generation.
AlertDialog Fragments
src/components/ui/AlertDialog/fragments/AlertDialog*.tsx
Replaced root class computation with useComponentClass; imported clsx and updated all fragment class compositions to conditionally append suffixes only when rootClass is truthy.
AspectRatio & Avatar
src/components/ui/AspectRatio/AspectRatio.tsx, src/components/ui/Avatar/fragments/Avatar*.tsx, src/components/ui/AvatarGroup/fragments/AvatarGroup*.tsx
Updated root class computation to use useComponentClass; guarded all fragment class names against falsy rootClass.
Badge, BlockQuote, Button
src/components/ui/Badge/Badge.tsx, src/components/ui/BlockQuote/BlockQuote.tsx, src/components/ui/Button/Button.tsx, src/components/ui/Button/tests/Button*.test.tsx
Switched to useComponentClass; updated test cases to pass customRootClass="rad-ui" prop to satisfy expected class assertions.
Callout & Card
src/components/ui/Callout/fragments/Callout*.tsx, src/components/ui/Card/fragments/Card*.tsx, src/components/ui/Card/tests/Card.test.tsx
Root components replaced with useComponentClass; all fragment classes now conditionally applied only when rootClass is truthy; updated test to pass customRootClass="rad-ui".
Checkbox, CheckboxCards, CheckboxGroup
src/components/ui/Checkbox/fragments/Checkbox*.tsx, src/components/ui/CheckboxCards/fragments/CheckboxCards*.tsx, src/components/ui/CheckboxGroup/fragments/CheckboxGroup*.tsx
Roots switched to useComponentClass; all fragments guard class names; CheckboxCards/CheckboxGroup updated to conditionally apply -root suffix when rootClass is truthy.
Code, Collapsible, Combobox
src/components/ui/Code/Code.tsx, src/components/ui/Collapsible/fragments/CollapsibleRoot.tsx, src/components/ui/Combobox/fragments/Combobox*.tsx
Root class computation replaced with useComponentClass; Combobox fragments updated to accept/merge className props and conditionally apply derived classes.
ContextMenu, DataList, Dialog
src/components/ui/ContextMenu/fragments/ContextMenu*.tsx, src/components/ui/DataList/fragments/DataList*.tsx, src/components/ui/Dialog/fragments/Dialog*.tsx, src/components/ui/DataList/tests/DataList.test.tsx
ContextMenu/Dialog roots use useComponentClass; all fragments guard class names; DataList test updated to pass customRootClass="rad-ui".
Disclosure, DropdownMenu
src/components/ui/Disclosure/fragments/Disclosure*.tsx, src/components/ui/DropdownMenu/fragments/DropdownMenu*.tsx
Root class computation switched to useComponentClass; fragment class names now conditionally applied only when rootClass is truthy.
Em, Heading, HoverCard, Kbd, Link
src/components/ui/Em/Em.tsx, src/components/ui/Heading/Heading.tsx, src/components/ui/HoverCard/fragments/HoverCard*.tsx, src/components/ui/Kbd/Kbd.tsx, src/components/ui/Link/Link.tsx
All updated to use useComponentClass instead of customClassSwitcher for root class derivation.
Menubar, Minimap, NavigationMenu, NumberField
src/components/ui/Menubar/fragments/Menubar*.tsx, src/components/ui/Minimap/fragments/Minimap*.tsx, src/components/ui/NavigationMenu/fragments/NavigationMenu*.tsx, src/components/ui/NumberField/fragments/NumberField*.tsx
Roots switched to useComponentClass; fragments updated to conditionally apply classes; NumberField root extended with customRootClass prop and conditional -root suffix.
Progress, Quote, Radio, RadioCards, RadioGroup
src/components/ui/Progress/fragments/Progress*.tsx, src/components/ui/Quote/Quote.tsx, src/components/ui/Radio/Radio.tsx, src/components/ui/RadioCards/fragments/RadioCards*.tsx, src/components/ui/RadioGroup/fragments/RadioGroup*.tsx
Roots replaced with useComponentClass; all fragment/component classes now guarded to apply only when rootClass is truthy.
ScrollArea, Select, Separator, Skeleton
src/components/ui/ScrollArea/fragments/ScrollAreaRoot.tsx, src/components/ui/Select/fragments/Select*.tsx, src/components/ui/Separator/Separator.tsx, src/components/ui/Skeleton/Skeleton.tsx, src/components/ui/Skeleton/tests/Skeleton.test.tsx
Roots use useComponentClass; Select fragments updated to accept/merge className props and conditionally apply derived classes; Separator added generic orientation-based class; Skeleton test comment removed.
Slider
src/components/ui/Slider/fragments/Slider*.tsx
Root class computation switched to useComponentClass; all fragment classes conditionally applied; Range/RangeSlider updated to accept/merge className and style props; Root updated thumb-detection logic to fall back to [role="slider"] when rootClass is falsy; Marks, Range, RangeSlider have comprehensive class/style guarding.
Spinner, Splitter, Steps, Strong, Switch
src/components/ui/Spinner/Spinner.tsx, src/components/ui/Splitter/fragments/Splitter*.tsx, src/components/ui/Steps/fragments/Step*.tsx, src/components/ui/Strong/Strong.tsx, src/components/ui/Switch/fragments/Switch*.tsx, src/components/ui/Switch/tests/Switch.test.tsx
Roots use useComponentClass; fragments guard class names; Spinner updated to merge className and add explicit ref forwarding; Splitter removed unused imports; Switch tests updated to pass customRootClass="rad-ui".
TabNav, Table, Tabs, Text, TextArea, Toggle, ToggleGroup
src/components/ui/TabNav/fragments/TabNav*.tsx, src/components/ui/Table/fragments/TableRoot.tsx, src/components/ui/Tabs/fragments/Tab*.tsx, src/components/ui/Text/Text.tsx, src/components/ui/TextArea/fragments/TextAreaRoot.tsx, src/components/ui/Toggle/Toggle.tsx, src/components/ui/ToggleGroup/fragments/ToggleGroup*.tsx
All roots switched to useComponentClass; fragments updated to conditionally apply classes; TabList, Tabs, ToggleItem updated with clsx for better class merging.
Toolbar, Tree, VisuallyHidden
src/components/ui/Toolbar/fragments/Toolbar*.tsx, src/components/ui/Tree/fragments/Tree*.tsx, src/components/ui/VisuallyHidden/VisuallyHidden.tsx
Roots use useComponentClass; fragments guard class names; Toolbar removed unused imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The PR is large in file count (~150+ files modified) but exhibits high homogeneity with a consistent refactoring pattern applied repeatedly: replacing customClassSwitcher with useComponentClass, guarding class names, and adding clsx imports. The documentation additions and new hook are straightforward. The repetitive nature of changes reduces per-file cognitive load despite the volume.

Possibly related issues

Possibly related PRs

  • [New Component] Theme #1048: Related because both modify the Theme surface and component class derivation—the retrieved PR introduces Theme component with data attributes while this PR extends Theme with classNamespace and introduces useComponentClass hook that components use to derive namespaced class names.

  • chore: add portalling and make ui updates #1726: Related because both modify Theme/ThemeContext surface and consumer usage—retrieved PR adds ThemeContext with container/portal refs, while this PR extends it with classNamespace and adds useComponentClass hook for theme-driven class namespace derivation.

  • Util classname 3 #632: Related because both refactor className composition across many UI components, introducing/standardizing clsx-based conditional class merging and replacing raw template interpolation with guarded conditional patterns across the same file set.

Suggested labels

codex

Suggested reviewers

  • GoldGroove06
  • mrkazmi333

🐰 A rabbit hops through the classNamespace grove,
Where themes and components dance in harmony's cove,
No more falsy suffixes trailing behind,
Headless and clean—just the style we designed,
The refactor is done, the contracts are clear,
A truly headless future is finally here!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kotapi/refactor-unstyled-first-behaviiour

…led-first-behaviiour

# Conflicts:
#	src/components/tools/SandboxEditor/SandboxEditor.tsx
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: 5

Caution

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

⚠️ Outside diff range comments (4)
src/components/ui/Slider/fragments/SliderRange.tsx (1)

11-12: ⚠️ Potential issue | 🟠 Major

className can override and remove the base range class.

At Line 42 you compute the internal class, but spreading {...props} at Line 53 allows props.className to overwrite it. This can strip ${rootClass}-range and break styling hooks. Please merge consumer and internal classes explicitly.

💡 Proposed fix
-const SliderRange = forwardRef<SliderRangeElement, SliderRangeProps>(({ children, ...props }, ref) => {
+const SliderRange = forwardRef<SliderRangeElement, SliderRangeProps>(({ children, className, ...props }, ref) => {
@@
-            className={rootClass ? `${rootClass}-range` : undefined}
+            className={[rootClass && `${rootClass}-range`, className].filter(Boolean).join(' ') || undefined}
@@
-            {...props}
+            {...props}

Also applies to: 42-42, 53-53

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

In `@src/components/ui/Slider/fragments/SliderRange.tsx` around lines 11 - 12, The
computed internal class for the range element is being overwritten by consumer
props because SliderRange spreads {...props} after computing classes; update
SliderRange to merge its internal class (which includes `${rootClass}-range`)
with any incoming props.className (e.g., compute a mergedClass like
`${internalClass} ${props.className || ''}`) and pass that mergedClass as the
className prop to the rendered element instead of allowing props.className to
overwrite it; ensure you reference SliderRange, SliderContext (rootClass), and
props.className when making the change.
src/components/ui/Combobox/fragments/ComboboxTrigger.tsx (1)

18-23: ⚠️ Potential issue | 🟡 Minor

className override can drop the namespaced trigger class.

Because {...props} is spread after className, a consumer-provided className replaces ${rootClass}-trigger instead of merging with it.

Suggested fix
+'use client';
 import React, { useContext } from 'react';
+import clsx from 'clsx';
 import ComboboxPrimitive from '~/core/primitives/Combobox/ComboboxPrimitive';
@@
-const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, ComboboxTriggerProps>(({ customRootClass, children, disabled, placeholder, ...props }, forwardedRef) => {
+const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, ComboboxTriggerProps>(({ customRootClass, children, disabled, placeholder, className, ...props }, forwardedRef) => {
@@
-            className={rootClass ? `${rootClass}-trigger` : undefined}
+            className={clsx(rootClass && `${rootClass}-trigger`, className)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Combobox/fragments/ComboboxTrigger.tsx` around lines 18 -
23, The component ComboboxTrigger currently spreads {...props} after setting
className, so a consumer className overwrites `${rootClass}-trigger`; fix by
computing a merged className (merge `${rootClass}-trigger` with props.className
or the incoming className prop) and pass that single computed value to the
className prop instead of relying on spread order—update the JSX in
ComboboxTrigger.tsx to derive combinedClassName (or use a utility like clsx) and
remove/avoid letting props.className override the namespaced
`${rootClass}-trigger`.
src/components/ui/Slider/fragments/SliderTrack.tsx (1)

11-26: ⚠️ Potential issue | 🟠 Major

className can be unintentionally overridden by prop spread.

Line 17 sets the internal track class, but line 25 spreads ...props afterward, so any passed className replaces it instead of merging. Similarly, the style prop at lines 18-23 can be overridden by style in the spread, losing the orientation-based positioning. This breaks component styling and functionality.

Destructure className and style separately from props, then use clsx to merge the className. This pattern is already used correctly in SliderRoot.tsx in the same directory.

Suggested fix
 import React, { forwardRef, ElementRef, ComponentPropsWithoutRef } from 'react';
+import clsx from 'clsx';
 import { SliderContext } from '../context/SliderContext';
 
-const SliderTrack = forwardRef<SliderTrackElement, SliderTrackProps>(({ children, ...props }, ref) => {
+const SliderTrack = forwardRef<SliderTrackElement, SliderTrackProps>(({ children, className, style, ...props }, ref) => {
     const { rootClass, orientation } = React.useContext(SliderContext);
 
     return (
         <div
             ref={ref}
-            className={rootClass ? `${rootClass}-track` : undefined}
-            style={ orientation === 'vertical' ? { 
+            className={clsx(rootClass && `${rootClass}-track`, className)}
+            style={{ ...(orientation === 'vertical' ? {
                 rotate: '180deg',
                 position: 'relative'
             } : {
                 position: 'relative'
-            }}
+            }), ...style }}
 
             {...props}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Slider/fragments/SliderTrack.tsx` around lines 11 - 26, The
SliderTrack component currently spreads props last which allows incoming
className and style to overwrite internal values; fix this in the forwardRef
SliderTrack function by destructuring className and style from props (e.g., ({
children, className, style, ...props }, ref)), compute the internal class using
clsx like clsx(rootClass ? `${rootClass}-track` : undefined, className) and
compute the orientation style, then merge styles so the orientation-driven style
wins (finalStyle = { ...style, ...orientationStyle }) and pass finalStyle and
the merged className into the div before spreading the remaining ...props;
follow the pattern used in SliderRoot.tsx for merging className/style.
src/components/ui/Spinner/Spinner.tsx (1)

16-27: ⚠️ Potential issue | 🟠 Major

Forwarded ref is not attached to the rendered span element.

React.forwardRef receives ref and declares SpinnerElement = ElementRef<'span'>, but the <span> element at line 22 does not use ref={ref}. Consumers will get null when accessing the ref. Attach the ref to the rendered span element.

🔧 Proposed fix
             <span
+                ref={ref}
                 className={clsx(rootClass, className)} {...props}
🤖 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 16 - 27, The forwarded
ref passed into React.forwardRef in the Spinner component isn't attached to the
rendered DOM node, so consumers of Spinner get null; update the Spinner
implementation (the forwardRef callback that declares
SpinnerElement/SpinnerProps and the span JSX) to pass the received ref to the
span (i.e., add ref={ref} on the <span>), ensuring the ref variable from
forwardRef is forwarded to the rendered span element.
🧹 Nitpick comments (6)
src/components/ui/Combobox/fragments/ComboboxSearch.tsx (1)

12-14: Avoid losing internal class via prop spread order.

Line 12 can be overridden by props.className from Line 14. If this component should keep its root namespace class, merge className explicitly.

Proposed refactor
 import React, { useContext } from 'react';
 import ComboboxPrimitive from '~/core/primitives/Combobox/ComboboxPrimitive';
 import { ComboboxRootContext } from '../contexts/ComboboxRootContext';
+import clsx from 'clsx';
@@
-const ComboboxSearch = React.forwardRef<ComboboxSearchElement, ComboboxSearchProps>((props, forwardedRef) => {
+const ComboboxSearch = React.forwardRef<ComboboxSearchElement, ComboboxSearchProps>(({ className, ...props }, forwardedRef) => {
     const { rootClass } = useContext(ComboboxRootContext);
     return (
         <ComboboxPrimitive.Search
-            className={rootClass ? `${rootClass}-search` : undefined}
+            className={clsx(rootClass && `${rootClass}-search`, className)}
             ref={forwardedRef}
             {...props}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Combobox/fragments/ComboboxSearch.tsx` around lines 12 -
14, The component currently sets className using rootClass (className={rootClass
? `${rootClass}-search` : undefined}) but then spreads {...props}, allowing
props.className to overwrite the internal class; update ComboboxSearch to merge
class names instead of letting props override: compute a mergedClassName that
includes the internal `${rootClass}-search` (when rootClass exists) and any
incoming props.className (using a safe join or a classnames utility) and pass
that mergedClassName to the element while still spreading the other props and
keeping forwardedRef.
src/components/ui/Select/fragments/SelectGroup.tsx (1)

16-18: Preserve namespace class when consumer passes className.

On Line 16, className is set before {...props} (Lines 17-18), so props.className can override it. If additive styling is intended, merge both and remove className from spread.

Proposed refactor
 import React, { useContext } from 'react';
 import ComboboxPrimitive from '~/core/primitives/Combobox/ComboboxPrimitive';
 import { SelectRootContext } from '../contexts/SelectRootContext';
+import clsx from 'clsx';
@@
-const SelectGroup = React.forwardRef<SelectGroupElement, SelectGroupComponentProps>(({ children, ...props }, forwardedRef) => {
+const SelectGroup = React.forwardRef<SelectGroupElement, SelectGroupComponentProps>(({ children, className, ...props }, forwardedRef) => {
     const { rootClass } = useContext(SelectRootContext);
     return (
         <ComboboxPrimitive.Group
-            className={rootClass ? `${rootClass}-group` : undefined}
+            className={clsx(rootClass && `${rootClass}-group`, className)}
             ref={forwardedRef}
             {...props}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Select/fragments/SelectGroup.tsx` around lines 16 - 18, The
component sets className={rootClass ? `${rootClass}-group` : undefined} before
spreading {...props}, allowing props.className to overwrite it; modify the
SelectGroup render so you merge the consumer class (props.className) with the
namespace class (rootClass ? `${rootClass}-group` : undefined) into a single
className string (e.g., combined and deduped), remove className from the props
spread, and pass the merged className along with forwardedRef and the remaining
props; locate this change around the SelectGroup JSX where rootClass,
forwardedRef and props are used.
src/components/ui/Combobox/fragments/ComboboxIndicator.tsx (1)

12-12: Merge props.className explicitly to keep indicator namespace class.

On Line 12, className may be replaced by props.className due to subsequent spread. If internal class should always be retained, merge both class values.

Proposed refactor
 'use client';
 import React, { useContext } from 'react';
 import { ComboboxRootContext } from '../contexts/ComboboxRootContext';
 import { Check } from 'lucide-react';
+import clsx from 'clsx';
@@
-const ComboboxIndicator = React.forwardRef<ComboboxIndicatorElement, ComboboxIndicatorProps>((props, forwardedRef) => {
+const ComboboxIndicator = React.forwardRef<ComboboxIndicatorElement, ComboboxIndicatorProps>(({ className, ...props }, forwardedRef) => {
     const { rootClass } = useContext(ComboboxRootContext);
     return (
-        <div className={rootClass ? `${rootClass}-item-indicator` : undefined} ref={forwardedRef} {...props}>
+        <div className={clsx(rootClass && `${rootClass}-item-indicator`, className)} ref={forwardedRef} {...props}>
             <Check width={16} height={16} />
         </div>
     );
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Combobox/fragments/ComboboxIndicator.tsx` at line 12, The
component currently spreads {...props} after setting className so
props.className can override the internal namespace; update ComboboxIndicator to
merge the classes instead: build a combined class string from rootClass ?
`${rootClass}-item-indicator` : '' and props.className (filtering falsy values
and joining with a space), then spread props first and pass
className={combinedClass} (or remove className from props before spreading) so
the internal indicator class (rootClass/`${rootClass}-item-indicator`) is always
preserved; reference symbols: rootClass, forwardedRef, props.className,
ComboboxIndicator.
knowledge/good_practices/index.md (3)

31-31: Avoid duplicated runtime-style-injection guidance.

The same policy is stated at Line 31 and again in the full section at Lines 212-224. Keeping both increases doc drift risk; keep one canonical section and cross-reference it.

Possible consolidation
-Do not rely on runtime CSS injection as the default styling mechanism. Runtime injection complicates SSR, streaming, CSP, style ordering, and first paint. Prefer static CSS imports or framework-level stylesheet links.
+See “Runtime Style Injection” below. Default styling should avoid runtime CSS injection and prefer static CSS imports or framework-level stylesheet links.

Also applies to: 212-224

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

In `@knowledge/good_practices/index.md` at line 31, Remove the duplicated guidance
sentence "Do not rely on runtime CSS injection as the default styling
mechanism..." found at the top (Line 31) and keep the full canonical policy in
the later section (Lines 212-224); replace the removed sentence with a brief
cross-reference pointing readers to the canonical section (or add an anchor link
to that section) so there is a single source of truth and no drift between
"runtime CSS injection" guidance instances.

134-138: Reduce repetitive “Document …” bullets for scannability.

Lines 134-138 repeat the same opening word five times. This is readable but a small polish opportunity for flow.

Example rewrite
-- Document public slots or parts for every stylable component.
-- Document generated class names using the namespace pattern.
-- Document public data attributes.
-- Document public CSS variables, if any exist.
-- Document DOM structure guarantees that consumers can safely rely on.
+- Public slots/parts for every stylable component.
+- Generated class names using the namespace pattern.
+- Public data attributes.
+- Public CSS variables, if any exist.
+- DOM structure guarantees consumers can safely rely on.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@knowledge/good_practices/index.md` around lines 134 - 138, The five list
items all start with the repeated verb "Document" (e.g., "Document public slots
or parts for every stylable component", "Document generated class names using
the namespace pattern", "Document public data attributes", "Document public CSS
variables, if any exist", "Document DOM structure guarantees that consumers can
safely rely on"), so replace the repetitive bullets with a short lead sentence
like "Document the following for each component:" and convert the five lines
into concise noun-phrased bullets (slots/parts, generated class names, public
data attributes, public CSS variables, DOM structure guarantees) or otherwise
reword them to avoid repeating "Document" at the start of each bullet while
preserving the original semantics and order.

210-210: Clarify wording in the portal warning sentence.

Line 210 reads awkwardly (“carefully anywhere ... is expected to flow”). Consider tightening it to improve readability and avoid ambiguity.

Proposed wording
-Portals can break styling assumptions when content escapes the original subtree. Audit dialog, popover, hover card, context menu, and similar components carefully anywhere theme context, CSS variables, or namespace-based styling is expected to flow across the portal boundary.
+Portals can break styling assumptions when content escapes the original subtree. Audit dialog, popover, hover card, context menu, and similar components carefully wherever theme context, CSS variables, or namespace-based styling is expected to flow across the portal boundary.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@knowledge/good_practices/index.md` at line 210, The sentence starting
"Portals can break styling assumptions when content escapes the original
subtree..." is awkward; replace it with a clearer phrasing such as: "Portals can
break styling assumptions when content renders outside the original subtree;
audit dialog, popover, hover card, context menu, and similar components wherever
theme context, CSS variables, or namespace-based styling must flow across the
portal boundary." Update that sentence in the markdown to improve readability
and remove the ambiguous "carefully anywhere ... is expected to flow" phrasing.
🤖 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/tests/Accordion.test.tsx`:
- Around line 68-69: Save the original window.matchMedia before you overwrite it
in Accordion.test.tsx (e.g., const originalMatchMedia = window.matchMedia), then
restore it in an afterEach/afterAll hook (window.matchMedia =
originalMatchMedia) so the direct reassignment at the test (the mock created
with jest.fn() that returns a MediaQueryList-like object) does not leak to other
tests; alternatively replace the direct assignment with jest.spyOn(window,
'matchMedia').mockImplementation(...) and call mockRestore() in the teardown to
ensure global state is restored.

In `@src/components/ui/Avatar/fragments/AvatarImage.tsx`:
- Around line 10-12: The AvatarImage component currently spreads {...props}
after setting className which lets a consumer-supplied className override the
generated rootClass-based class; update AvatarImage (the forwardRef wrapper
around AvatarPrimitiveImage) to merge class names instead of allowing overwrite
by combining clsx(rootClass && `${rootClass}-image`, props.className,
props.className && props.className) or equivalent so the generated
`${rootClass}-image` always remains while still honoring consumer classes;
reference AvatarImage, AvatarPrimitiveImage, AvatarContext, rootClass and
props.className when making the change.

In `@src/components/ui/CheckboxCards/fragments/CheckboxCardsContent.tsx`:
- Line 12: The generated content class `${rootClass}-content` can be overridden
by a consumer `className` because `{...props}` comes after the explicit
className; update the JSX for CheckboxGroupPrimitive.Content so it merges the
generated namespace class with any consumer class instead of letting props
replace it: compute a combined className from `rootClass ?
`${rootClass}-content` : undefined` plus any `className`/`class` found in
`props` (filtering falsy and joining with a space), then pass that combined
value as `className` to `CheckboxGroupPrimitive.Content` while spreading the
rest of `props` (without reintroducing the original class fields). Ensure you
reference `CheckboxGroupPrimitive.Content`, `rootClass`, `props`, and
`className` when implementing the fix.

In `@src/components/ui/Skeleton/Skeleton.tsx`:
- Around line 31-33: The call to the custom hook useComponentClass must run
unconditionally to respect React Hooks rules: move the invocation of
useComponentClass(customRootClass, COMPONENT_NAME) to before the early return in
the Skeleton component so rootClass is computed on every render (even when
loading is false) and then keep the existing early return if (!loading) return
<>{children}</>; this ensures hooks are called in the same order every render.

In `@src/core/index.ts`:
- Line 3: The core entry now only exports composeRefs and mergeProps but removed
the customClassSwitcher re-export, which can break downstream consumers; restore
customClassSwitcher to the public surface (add it back to the export list
alongside composeRefs and mergeProps) or, if intentional, add an explicit
migration path: reintroduce a deprecated re-export named customClassSwitcher
that proxies to the new location and add a CHANGELOG/compat note and prepare a
major-version bump; reference the export statement containing composeRefs and
mergeProps and the symbol customClassSwitcher when making the change.

---

Outside diff comments:
In `@src/components/ui/Combobox/fragments/ComboboxTrigger.tsx`:
- Around line 18-23: The component ComboboxTrigger currently spreads {...props}
after setting className, so a consumer className overwrites
`${rootClass}-trigger`; fix by computing a merged className (merge
`${rootClass}-trigger` with props.className or the incoming className prop) and
pass that single computed value to the className prop instead of relying on
spread order—update the JSX in ComboboxTrigger.tsx to derive combinedClassName
(or use a utility like clsx) and remove/avoid letting props.className override
the namespaced `${rootClass}-trigger`.

In `@src/components/ui/Slider/fragments/SliderRange.tsx`:
- Around line 11-12: The computed internal class for the range element is being
overwritten by consumer props because SliderRange spreads {...props} after
computing classes; update SliderRange to merge its internal class (which
includes `${rootClass}-range`) with any incoming props.className (e.g., compute
a mergedClass like `${internalClass} ${props.className || ''}`) and pass that
mergedClass as the className prop to the rendered element instead of allowing
props.className to overwrite it; ensure you reference SliderRange, SliderContext
(rootClass), and props.className when making the change.

In `@src/components/ui/Slider/fragments/SliderTrack.tsx`:
- Around line 11-26: The SliderTrack component currently spreads props last
which allows incoming className and style to overwrite internal values; fix this
in the forwardRef SliderTrack function by destructuring className and style from
props (e.g., ({ children, className, style, ...props }, ref)), compute the
internal class using clsx like clsx(rootClass ? `${rootClass}-track` :
undefined, className) and compute the orientation style, then merge styles so
the orientation-driven style wins (finalStyle = { ...style, ...orientationStyle
}) and pass finalStyle and the merged className into the div before spreading
the remaining ...props; follow the pattern used in SliderRoot.tsx for merging
className/style.

In `@src/components/ui/Spinner/Spinner.tsx`:
- Around line 16-27: The forwarded ref passed into React.forwardRef in the
Spinner component isn't attached to the rendered DOM node, so consumers of
Spinner get null; update the Spinner implementation (the forwardRef callback
that declares SpinnerElement/SpinnerProps and the span JSX) to pass the received
ref to the span (i.e., add ref={ref} on the <span>), ensuring the ref variable
from forwardRef is forwarded to the rendered span element.

---

Nitpick comments:
In `@knowledge/good_practices/index.md`:
- Line 31: Remove the duplicated guidance sentence "Do not rely on runtime CSS
injection as the default styling mechanism..." found at the top (Line 31) and
keep the full canonical policy in the later section (Lines 212-224); replace the
removed sentence with a brief cross-reference pointing readers to the canonical
section (or add an anchor link to that section) so there is a single source of
truth and no drift between "runtime CSS injection" guidance instances.
- Around line 134-138: The five list items all start with the repeated verb
"Document" (e.g., "Document public slots or parts for every stylable component",
"Document generated class names using the namespace pattern", "Document public
data attributes", "Document public CSS variables, if any exist", "Document DOM
structure guarantees that consumers can safely rely on"), so replace the
repetitive bullets with a short lead sentence like "Document the following for
each component:" and convert the five lines into concise noun-phrased bullets
(slots/parts, generated class names, public data attributes, public CSS
variables, DOM structure guarantees) or otherwise reword them to avoid repeating
"Document" at the start of each bullet while preserving the original semantics
and order.
- Line 210: The sentence starting "Portals can break styling assumptions when
content escapes the original subtree..." is awkward; replace it with a clearer
phrasing such as: "Portals can break styling assumptions when content renders
outside the original subtree; audit dialog, popover, hover card, context menu,
and similar components wherever theme context, CSS variables, or namespace-based
styling must flow across the portal boundary." Update that sentence in the
markdown to improve readability and remove the ambiguous "carefully anywhere ...
is expected to flow" phrasing.

In `@src/components/ui/Combobox/fragments/ComboboxIndicator.tsx`:
- Line 12: The component currently spreads {...props} after setting className so
props.className can override the internal namespace; update ComboboxIndicator to
merge the classes instead: build a combined class string from rootClass ?
`${rootClass}-item-indicator` : '' and props.className (filtering falsy values
and joining with a space), then spread props first and pass
className={combinedClass} (or remove className from props before spreading) so
the internal indicator class (rootClass/`${rootClass}-item-indicator`) is always
preserved; reference symbols: rootClass, forwardedRef, props.className,
ComboboxIndicator.

In `@src/components/ui/Combobox/fragments/ComboboxSearch.tsx`:
- Around line 12-14: The component currently sets className using rootClass
(className={rootClass ? `${rootClass}-search` : undefined}) but then spreads
{...props}, allowing props.className to overwrite the internal class; update
ComboboxSearch to merge class names instead of letting props override: compute a
mergedClassName that includes the internal `${rootClass}-search` (when rootClass
exists) and any incoming props.className (using a safe join or a classnames
utility) and pass that mergedClassName to the element while still spreading the
other props and keeping forwardedRef.

In `@src/components/ui/Select/fragments/SelectGroup.tsx`:
- Around line 16-18: The component sets className={rootClass ?
`${rootClass}-group` : undefined} before spreading {...props}, allowing
props.className to overwrite it; modify the SelectGroup render so you merge the
consumer class (props.className) with the namespace class (rootClass ?
`${rootClass}-group` : undefined) into a single className string (e.g., combined
and deduped), remove className from the props spread, and pass the merged
className along with forwardedRef and the remaining props; locate this change
around the SelectGroup JSX where rootClass, forwardedRef and props are used.
🪄 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: 1cbbabdb-c44e-4b8b-9bd5-df17c635853a

📥 Commits

Reviewing files that changed from the base of the PR and between 48c33ad and a380679.

⛔ Files ignored due to path filters (2)
  • src/components/ui/Slider/tests/__snapshots__/Slider.test.tsx.snap is excluded by !**/*.snap
  • src/components/ui/TextArea/__snapshots__/TextArea.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (187)
  • knowledge/features/data-attributes.md
  • knowledge/features/styling-namespaces.md
  • knowledge/good_practices/index.md
  • knowledge/what-makes-a-library-truly-headless.md
  • src/components/tools/SandboxEditor/SandboxEditor.tsx
  • src/components/ui/Accordion/fragments/AccordionContent.tsx
  • src/components/ui/Accordion/fragments/AccordionHeader.tsx
  • src/components/ui/Accordion/fragments/AccordionItem.tsx
  • src/components/ui/Accordion/fragments/AccordionRoot.tsx
  • src/components/ui/Accordion/fragments/AccordionTrigger.tsx
  • src/components/ui/Accordion/tests/Accordion.test.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogAction.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogCancel.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogContent.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogDescription.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogFooter.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogOverlay.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogRoot.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogTitle.tsx
  • src/components/ui/AlertDialog/fragments/AlertDialogTrigger.tsx
  • src/components/ui/AspectRatio/AspectRatio.tsx
  • src/components/ui/Avatar/fragments/AvatarFallback.tsx
  • src/components/ui/Avatar/fragments/AvatarImage.tsx
  • src/components/ui/Avatar/fragments/AvatarRoot.tsx
  • src/components/ui/AvatarGroup/fragments/AvatarGroupFallback.tsx
  • src/components/ui/AvatarGroup/fragments/AvatarGroupItem.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/Button/tests/Button.asChild.test.tsx
  • src/components/ui/Button/tests/Button.test.tsx
  • src/components/ui/Callout/fragments/CalloutIcon.tsx
  • src/components/ui/Callout/fragments/CalloutRoot.tsx
  • src/components/ui/Callout/fragments/CalloutText.tsx
  • src/components/ui/Card/fragments/CardAction.tsx
  • src/components/ui/Card/fragments/CardContent.tsx
  • src/components/ui/Card/fragments/CardDescription.tsx
  • src/components/ui/Card/fragments/CardFooter.tsx
  • src/components/ui/Card/fragments/CardHeader.tsx
  • src/components/ui/Card/fragments/CardRoot.tsx
  • src/components/ui/Card/fragments/CardTitle.tsx
  • src/components/ui/Card/tests/Card.test.tsx
  • src/components/ui/Checkbox/fragments/CheckboxIndicator.tsx
  • src/components/ui/Checkbox/fragments/CheckboxRoot.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsContent.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsIndicator.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsItem.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsRoot.tsx
  • src/components/ui/CheckboxGroup/fragments/CheckboxGroupIndicator.tsx
  • src/components/ui/CheckboxGroup/fragments/CheckboxGroupLabel.tsx
  • src/components/ui/CheckboxGroup/fragments/CheckboxGroupRoot.tsx
  • src/components/ui/CheckboxGroup/fragments/CheckboxGroupTrigger.tsx
  • src/components/ui/Code/Code.tsx
  • src/components/ui/Collapsible/fragments/CollapsibleRoot.tsx
  • src/components/ui/Combobox/fragments/ComboboxContent.tsx
  • src/components/ui/Combobox/fragments/ComboboxGroup.tsx
  • src/components/ui/Combobox/fragments/ComboboxIndicator.tsx
  • src/components/ui/Combobox/fragments/ComboboxItem.tsx
  • src/components/ui/Combobox/fragments/ComboboxRoot.tsx
  • src/components/ui/Combobox/fragments/ComboboxSearch.tsx
  • src/components/ui/Combobox/fragments/ComboboxTrigger.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuContent.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuItem.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuRoot.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuSub.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuSubTrigger.tsx
  • src/components/ui/ContextMenu/fragments/ContextMenuTrigger.tsx
  • src/components/ui/DataList/fragments/DataListItem.tsx
  • src/components/ui/DataList/fragments/DataListLabel.tsx
  • src/components/ui/DataList/fragments/DataListRoot.tsx
  • src/components/ui/DataList/fragments/DataListValue.tsx
  • src/components/ui/DataList/tests/DataList.test.tsx
  • src/components/ui/Dialog/fragments/DialogClose.tsx
  • src/components/ui/Dialog/fragments/DialogContent.tsx
  • src/components/ui/Dialog/fragments/DialogDescription.tsx
  • src/components/ui/Dialog/fragments/DialogFooter.tsx
  • src/components/ui/Dialog/fragments/DialogOverlay.tsx
  • src/components/ui/Dialog/fragments/DialogRoot.tsx
  • src/components/ui/Dialog/fragments/DialogTitle.tsx
  • src/components/ui/Dialog/fragments/DialogTrigger.tsx
  • src/components/ui/Disclosure/fragments/DisclosureContent.tsx
  • src/components/ui/Disclosure/fragments/DisclosureItem.tsx
  • src/components/ui/Disclosure/fragments/DisclosureRoot.tsx
  • src/components/ui/Disclosure/fragments/DisclosureTrigger.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuContent.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuItem.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuRoot.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuSub.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuSubTrigger.tsx
  • src/components/ui/DropdownMenu/fragments/DropdownMenuTrigger.tsx
  • src/components/ui/Em/Em.tsx
  • src/components/ui/Heading/Heading.tsx
  • src/components/ui/HoverCard/fragments/HoverCardArrow.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/MenubarContent.tsx
  • src/components/ui/Menubar/fragments/MenubarItem.tsx
  • src/components/ui/Menubar/fragments/MenubarMenu.tsx
  • src/components/ui/Menubar/fragments/MenubarRoot.tsx
  • src/components/ui/Menubar/fragments/MenubarSub.tsx
  • src/components/ui/Menubar/fragments/MenubarSubTrigger.tsx
  • src/components/ui/Menubar/fragments/MenubarTrigger.tsx
  • src/components/ui/Minimap/fragments/MinimapBubble.tsx
  • src/components/ui/Minimap/fragments/MinimapContent.tsx
  • src/components/ui/Minimap/fragments/MinimapItem.tsx
  • src/components/ui/Minimap/fragments/MinimapLine.tsx
  • src/components/ui/Minimap/fragments/MinimapRoot.tsx
  • src/components/ui/Minimap/fragments/MinimapTrack.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuContent.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuItem.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuLink.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuRoot.tsx
  • src/components/ui/NavigationMenu/fragments/NavigationMenuTrigger.tsx
  • src/components/ui/NumberField/fragments/NumberFieldDecrement.tsx
  • src/components/ui/NumberField/fragments/NumberFieldIncrement.tsx
  • src/components/ui/NumberField/fragments/NumberFieldInput.tsx
  • src/components/ui/NumberField/fragments/NumberFieldRoot.tsx
  • src/components/ui/Progress/fragments/ProgressIndicator.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/RadioCardsItem.tsx
  • src/components/ui/RadioCards/fragments/RadioCardsRoot.tsx
  • src/components/ui/RadioGroup/fragments/RadioGroupIndicator.tsx
  • src/components/ui/RadioGroup/fragments/RadioGroupItem.tsx
  • src/components/ui/RadioGroup/fragments/RadioGroupLabel.tsx
  • src/components/ui/RadioGroup/fragments/RadioGroupRoot.tsx
  • src/components/ui/ScrollArea/fragments/ScrollAreaRoot.tsx
  • src/components/ui/Select/fragments/SelectContent.tsx
  • src/components/ui/Select/fragments/SelectGroup.tsx
  • src/components/ui/Select/fragments/SelectIndicator.tsx
  • src/components/ui/Select/fragments/SelectItem.tsx
  • src/components/ui/Select/fragments/SelectRoot.tsx
  • src/components/ui/Select/fragments/SelectTrigger.tsx
  • src/components/ui/Separator/Separator.tsx
  • src/components/ui/Skeleton/Skeleton.tsx
  • src/components/ui/Skeleton/tests/Skeleton.test.tsx
  • src/components/ui/Slider/fragments/SliderMarks.tsx
  • src/components/ui/Slider/fragments/SliderRange.tsx
  • src/components/ui/Slider/fragments/SliderRangeSlider.tsx
  • src/components/ui/Slider/fragments/SliderRoot.tsx
  • src/components/ui/Slider/fragments/SliderThumb.tsx
  • src/components/ui/Slider/fragments/SliderTrack.tsx
  • src/components/ui/Spinner/Spinner.tsx
  • src/components/ui/Splitter/fragments/SplitterHandle.tsx
  • src/components/ui/Splitter/fragments/SplitterPanel.tsx
  • src/components/ui/Splitter/fragments/SplitterRoot.tsx
  • src/components/ui/Steps/fragments/StepBubble.tsx
  • src/components/ui/Steps/fragments/StepContent.tsx
  • src/components/ui/Steps/fragments/StepDescription.tsx
  • src/components/ui/Steps/fragments/StepItem.tsx
  • src/components/ui/Steps/fragments/StepLine.tsx
  • src/components/ui/Steps/fragments/StepRoot.tsx
  • src/components/ui/Steps/fragments/StepTitle.tsx
  • src/components/ui/Steps/fragments/StepTrack.tsx
  • src/components/ui/Strong/Strong.tsx
  • src/components/ui/Switch/fragments/SwitchRoot.tsx
  • src/components/ui/Switch/fragments/SwitchThumb.tsx
  • src/components/ui/Switch/tests/Switch.test.tsx
  • src/components/ui/TabNav/fragments/TabNavLink.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/TabList.tsx
  • src/components/ui/Tabs/fragments/TabRoot.tsx
  • src/components/ui/Tabs/fragments/TabTrigger.tsx
  • src/components/ui/Text/Text.tsx
  • src/components/ui/TextArea/fragments/TextAreaRoot.tsx
  • src/components/ui/Theme/Theme.tsx
  • src/components/ui/Theme/ThemeContext.tsx
  • src/components/ui/Theme/tests/Theme.test.tsx
  • src/components/ui/Theme/useComponentClass.ts
  • src/components/ui/Toggle/Toggle.tsx
  • src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx
  • src/components/ui/ToggleGroup/fragments/ToggleItem.tsx
  • src/components/ui/Toolbar/fragments/ToolbarButton.tsx
  • src/components/ui/Toolbar/fragments/ToolbarLink.tsx
  • src/components/ui/Toolbar/fragments/ToolbarRoot.tsx
  • src/components/ui/Toolbar/fragments/ToolbarSeparator.tsx
  • src/components/ui/Tree/fragments/TreeItem.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/index.ts
💤 Files with no reviewable changes (2)
  • src/core/customClassSwitcher/index.ts
  • src/core/customClassSwitcher/customClassSwitcher.test.tsx

Comment thread src/components/ui/Accordion/tests/Accordion.test.tsx
Comment thread src/components/ui/Avatar/fragments/AvatarImage.tsx Outdated
Comment thread src/components/ui/CheckboxCards/fragments/CheckboxCardsContent.tsx Outdated
Comment thread src/components/ui/Skeleton/Skeleton.tsx Outdated
Comment thread src/core/index.ts Outdated
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: 1

🧹 Nitpick comments (2)
knowledge/good_practices/index.md (1)

237-237: Small wording polish opportunity.

At Line 237, consider replacing “by accident” with “inadvertently” for tighter technical tone.

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

In `@knowledge/good_practices/index.md` at line 237, Replace the phrase "Do not
leak internal implementation attributes as part of the public styling API by
accident." with "Do not leak internal implementation attributes as part of the
public styling API inadvertently." — locate the exact sentence in the
knowledge/good_practices/index.md content (the line containing the phrase
"public styling API by accident") and update the wording to use "inadvertently"
for a tighter technical tone.
src/components/ui/Accordion/tests/Accordion.test.tsx (1)

63-70: Strengthen “classless” assertions to catch empty class="" regressions.

Using .className === '' passes both when the class attribute is absent and when class="" is rendered. If the contract is “no empty class attributes,” assert against the attribute directly.

Suggested test update
-        expect(screen.getByTestId('accordion-root').className).toBe('');
-        expect(screen.getByTestId('accordion-item').className).toBe('');
-        expect(screen.getByTestId('accordion-header').className).toBe('');
-        expect(screen.getByTestId('accordion-trigger').className).toBe('');
-        expect(content.className).toBe('');
-        expect(content.firstElementChild?.className).toBe('');
+        expect(screen.getByTestId('accordion-root')).not.toHaveAttribute('class');
+        expect(screen.getByTestId('accordion-item')).not.toHaveAttribute('class');
+        expect(screen.getByTestId('accordion-header')).not.toHaveAttribute('class');
+        expect(screen.getByTestId('accordion-trigger')).not.toHaveAttribute('class');
+        expect(content).not.toHaveAttribute('class');
+        expect(content.firstElementChild).not.toHaveAttribute('class');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Accordion/tests/Accordion.test.tsx` around lines 63 - 70,
The tests currently assert empty className (e.g.,
screen.getByTestId('accordion-root').className === ''), which doesn't
distinguish between missing class attribute and an explicit class=""; update
each assertion for the tested elements (accordion-root, accordion-item,
accordion-header, accordion-trigger, accordion-content, and
content.firstElementChild) to assert the class attribute is absent — e.g., use
element.getAttribute('class') toBeNull() or
expect(element.hasAttribute('class')).toBe(false) — so the test fails if an
empty class="" is rendered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@knowledge/good_practices/index.md`:
- Around line 157-170: The examples reference `root` but never define it; before
the assertions, capture the rendered element and assign it to `root` (e.g. call
render(...) and then const { container } = render(...); const root =
container.firstElementChild as HTMLElement; or const root =
container.querySelector('.accordion-root') as HTMLElement) — do this in both
snippets that render <Accordion.Root /> (and the themed variant with <Theme
classNamespace="acme">) so the subsequent expect(...) assertions have a defined
`root`.

---

Nitpick comments:
In `@knowledge/good_practices/index.md`:
- Line 237: Replace the phrase "Do not leak internal implementation attributes
as part of the public styling API by accident." with "Do not leak internal
implementation attributes as part of the public styling API inadvertently." —
locate the exact sentence in the knowledge/good_practices/index.md content (the
line containing the phrase "public styling API by accident") and update the
wording to use "inadvertently" for a tighter technical tone.

In `@src/components/ui/Accordion/tests/Accordion.test.tsx`:
- Around line 63-70: The tests currently assert empty className (e.g.,
screen.getByTestId('accordion-root').className === ''), which doesn't
distinguish between missing class attribute and an explicit class=""; update
each assertion for the tested elements (accordion-root, accordion-item,
accordion-header, accordion-trigger, accordion-content, and
content.firstElementChild) to assert the class attribute is absent — e.g., use
element.getAttribute('class') toBeNull() or
expect(element.hasAttribute('class')).toBe(false) — so the test fails if an
empty class="" is rendered.
🪄 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: 63cbc0c8-e328-431f-b514-6a490e13a424

📥 Commits

Reviewing files that changed from the base of the PR and between a380679 and 06b871e.

📒 Files selected for processing (14)
  • knowledge/good_practices/index.md
  • src/components/tools/SandboxEditor/SandboxEditor.tsx
  • src/components/ui/Accordion/tests/Accordion.test.tsx
  • src/components/ui/Avatar/fragments/AvatarImage.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsContent.tsx
  • src/components/ui/Combobox/fragments/ComboboxIndicator.tsx
  • src/components/ui/Combobox/fragments/ComboboxSearch.tsx
  • src/components/ui/Combobox/fragments/ComboboxTrigger.tsx
  • src/components/ui/Select/fragments/SelectGroup.tsx
  • src/components/ui/Skeleton/Skeleton.tsx
  • src/components/ui/Slider/fragments/SliderRange.tsx
  • src/components/ui/Slider/fragments/SliderTrack.tsx
  • src/components/ui/Spinner/Spinner.tsx
  • src/core/customClassSwitcher/index.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/components/tools/SandboxEditor/SandboxEditor.tsx
  • src/components/ui/Combobox/fragments/ComboboxIndicator.tsx
  • src/components/ui/CheckboxCards/fragments/CheckboxCardsContent.tsx
  • src/components/ui/Avatar/fragments/AvatarImage.tsx
  • src/components/ui/Select/fragments/SelectGroup.tsx
  • src/components/ui/Slider/fragments/SliderTrack.tsx
  • src/components/ui/Skeleton/Skeleton.tsx
  • src/components/ui/Combobox/fragments/ComboboxTrigger.tsx
  • src/core/customClassSwitcher/index.ts
  • src/components/ui/Spinner/Spinner.tsx

Comment thread knowledge/good_practices/index.md
@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 81.15% -0.16%
Branches 60.49% -2.01%
Functions 68.76% -0.29%
Lines 82.53% -0.12%

Coverage decreased for at least one metric. Please add or update tests to improve coverage.

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

@kotAPI kotAPI merged commit 7be9d31 into main Apr 16, 2026
10 checks passed
@kotAPI kotAPI deleted the kotapi/refactor-unstyled-first-behaviiour branch April 16, 2026 06:05
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