Skip to content

Conversation

@ITBoomBKStudio
Copy link

@ITBoomBKStudio ITBoomBKStudio commented Oct 24, 2025

Closes #5778

📝 Description

Supersedes #5795.
Builds on the initial work by @IsDyh01 to address the incorrect return type in extendVariants, and additionally fixes long-standing typing issues with CompoundVariants (observed since early NextUI days).

This PR:

  • Ports and refines the extendVariants type fix.
  • Corrects type inference for CompoundVariants when composing/extending variants.

Credits:
Original approach by @IsDyh01 (commits preserved via cherry-pick / attribution).
Authored by @ITBoomBKStudio.

⚽️ Current behavior (updates)

  • extendVariants can yield an incorrect component return type, leading to type errors when using the extended variant API.
  • CompoundVariants types don’t consistently infer the resulting props when variants are composed or extended; edge cases mis-type or drop constraints, forcing manual casts.

🚀 New behavior

  • extendVariants now preserves and exposes the correct component/props type in the returned value.

  • CompoundVariants inference is stable when:

    • composing existing variants,
    • extending variant configs, and
    • combining multiple variant keys (no manual as needed).
  • Added unit tests covering:

    • return type of extendVariants,
    • inference across composed/extended compound variants,
    • representative edge cases seen in real projects.

No runtime changes; all changes are type-level and test-only.

💣 Is this a breaking change (Yes/No):

No.
The change improves type inference without removing or renaming public APIs. Existing correct usages continue to type-check. In cases where code previously relied on loose/incorrect inference, the compiler now catches mistakes earlier (desired tightening, not a breaking API change).

📝 Additional Information

  • Motivation: This aligns Typescript inference with real-world usage patterns for extended/compound variants; it removes long-standing friction that required casts/workarounds.
  • Attribution: Built on @IsDyh01’s PR (fix(extendVariants): return component type error #5795). Happy to adjust structure (split PRs or tweak tests) per maintainer preference.

Summary by CodeRabbit

  • Refactor
    • Improved type-level handling for variants and component props to support slot-aware relationships and more flexible typings; no runtime changes.
  • New Features
    • Exported slot-aware variant prop shape so variants can target component slots.
  • Tests
    • Test helpers now accept optional style input with default compound-variant data; added tests ensuring compound-variant styles propagate to slots.
  • Chores
    • Added a changeset preparing a major release.

@changeset-bot
Copy link

changeset-bot bot commented Oct 24, 2025

🦋 Changeset detected

Latest commit: d18110f

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

This PR includes changesets to release 37 packages
Name Type
@heroui/system-rsc Major
@heroui/code Patch
@heroui/divider Patch
@heroui/kbd Patch
@heroui/spacer Patch
@heroui/spinner Patch
@heroui/system Patch
@heroui/react Patch
@heroui/accordion Patch
@heroui/listbox Patch
@heroui/menu Patch
@heroui/table Patch
@heroui/button Patch
@heroui/select Patch
@heroui/toast Patch
@heroui/alert Patch
@heroui/autocomplete Patch
@heroui/calendar Patch
@heroui/checkbox Patch
@heroui/date-input Patch
@heroui/date-picker Patch
@heroui/drawer Patch
@heroui/dropdown Patch
@heroui/form Patch
@heroui/input-otp Patch
@heroui/input Patch
@heroui/modal Patch
@heroui/navbar Patch
@heroui/number-input Patch
@heroui/popover Patch
@heroui/radio Patch
@heroui/slider Patch
@heroui/snippet Patch
@heroui/tabs Patch
@heroui/tooltip Patch
@heroui/aria-utils Patch
@heroui/framer-utils 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

@vercel
Copy link

vercel bot commented Oct 24, 2025

@ITBoomBKStudio is attempting to deploy a commit to the HeroUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Walkthrough

Updates TypeScript typings to make compound variants slots-aware by adding a third generic to CompoundVariants, adjusts ExtendVariants constraints to use ComponentSlots<CP>, and refactors props mapping to unify keys from component slots and variants; tests and exports updated accordingly.

Changes

Cohort / File(s) Summary
Type definition refinement
packages/core/system-rsc/src/extend-variants.d.ts
Adds third generic S to CompoundVariants<V, SV, S> and changes its class element type to include GetSuggestedValues<S>. Updates ExtendVariants constraint to CV extends CompoundVariants<V, SV, ComponentSlots<CP>>. Refactors PropsWithoutRef mapping to `[key in Exclude<keyof CP
Tests & exports
packages/core/system-rsc/__tests__/extend-variants.test.tsx
Exports ExtendVariantWithSlotsProps. Changes test helper signature to createExtendSlotsComponent(styles: ExtendVariantWithSlotsProps = {}), applies styles?.compoundVariants ?? [...] default compoundVariants, and adds/adjusts tests asserting compound-variant styles for components with slots.
Release metadata
.changeset/nine-apes-remain.md
Adds a changeset for @heroui/system-rsc marking a major release and noting a type-inference fix for extendVariants/CompoundVariants.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review type interactions between CompoundVariants<V, SV, S>, GetSuggestedValues<S>, and ClassProp for inference edge cases.
  • Verify ExtendVariants PropsWithoutRef union-key mapping preserves optionality and expected typings for existing slot and variant usages.
  • Check updated tests cover both slot-aware and original compound-variant shapes.

Possibly related PRs

Suggested reviewers

  • jrgarciadev

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix(system-rsc): extendVariants Fix/compound variants types" is directly related to the main changes in the pull request. It identifies the package (system-rsc), the key components being fixed (extendVariants and compound variants types), and the nature of the change (fix). The title clearly communicates that this is a type-level fix addressing issues with variant type inference. While the phrasing could be slightly more polished (the "Fix/" prefix appears somewhat redundant), it effectively summarizes the primary change from the developer's perspective.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly scoped to addressing the type inference issues in extendVariants and CompoundVariants as defined by issue #5778. The modifications to extend-variants.d.ts focus on updating type definitions for CompoundVariants and ExtendVariants; the test additions in extend-variants.test.tsx validate the fixes; and the changeset documents the change appropriately. There are no unrelated refactoring, unrelated feature additions, or modifications to out-of-scope components. The PR description confirms "No runtime changes; all changes are type-level and test-only," which aligns with the linked issue's requirements.
Description Check ✅ Passed The pull request description comprehensively follows the required template structure with all major sections present and substantively filled. It includes a linked issue reference ("Closes #5778"), a clear description with context and credits, a well-documented current behavior section describing the regression and type inference problems, a new behavior section detailing the fixes and test coverage, an explicit statement that this is not a breaking change with justification, and additional information about motivation and attribution. The description provides sufficient context for reviewers to understand the scope, impact, and intent of the changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e64ece and d18110f.

📒 Files selected for processing (1)
  • packages/core/system-rsc/src/extend-variants.d.ts (5 hunks)
🔇 Additional comments (5)
packages/core/system-rsc/src/extend-variants.d.ts (5)

2-2: LGTM: Import addition is correct.

The ForwardRefExoticComponent import is properly used in the ExtendVariants return type at line 100.


9-9: LGTM: Slot-aware typing implemented correctly.

The new GetSuggestedValues<S> helper (line 18) and its usage in Variants<S> (line 9) correctly make the variant system slot-aware. The conditional type properly narrows to ClassValue when no slots exist and expands to include SlotsClassValue<S> when slots are present.

Also applies to: 18-18


46-46: LGTM: Core slot-awareness fix for CompoundVariants.

The addition of the third type parameter S to CompoundVariants and the use of GetSuggestedValues<S> enables compound variants to handle slot-based class values correctly. This is the foundational change that addresses the type inference issues described in the linked issue #5778.


90-90: Design choice: Using ComponentSlots for full slot surface.

The constraint uses ComponentSlots<CP> rather than the bound generic S to preserve slot suggestions and validation from the base component. This design choice prioritizes ergonomics for compound variant authoring, as documented in the PR discussion.

Based on PR objectives.


101-105: Props mapping is technically correct; optionality is an intentional design choice.

The mapped type correctly handles key unions and type resolution:

  • Keys present in both CP and V get union types: CP[key] | StringToBoolean<keyof V[key]>
  • The use of NonNullable on line 104 is appropriate since V[key] may be undefined

The ?: modifier making all keys optional is an intentional design decision (documented in PR discussion) to favor ergonomics and reduce boilerplate when composing variants, at the cost of not preserving required-prop enforcement from base components.

Based on PR objectives.


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.

Copy link
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)
packages/core/system-rsc/src/extend-variants.d.ts (2)

51-54: CompoundVariants slots support looks good; consider a small readability tweak.

You can express the value type via the existing helper to reduce repetition.

Apply:

 type CompoundVariants<V, SV, S> = Array<
   VariantValue<V, SV> &
-    ClassProp<S extends undefined ? ClassValue : ClassValue | SlotsClassValue<S>>
+    ClassProp<ClassValue | GetSuggestedValues<S>>
 >;

98-99: Use the already-bound S instead of recomputing ComponentSlots.

Keeps generics consistent and may help inference.

-    CV extends CompoundVariants<V, SV, ComponentSlots<CP>>,
+    CV extends CompoundVariants<V, SV, S>,
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 328c57d and 3858ffd.

📒 Files selected for processing (1)
  • packages/core/system-rsc/src/extend-variants.d.ts (3 hunks)

Replaces the conditional ClassProp logic with a simpler,
consistent form to fix incorrect slot value inference.

Before:
  ClassProp<S extends undefined ? ClassValue : ClassValue | SlotsClassValue<S>>

After:
  ClassProp<ClassValue | GetSuggestedValues<S>>

This ensures GetSuggestedValues<S> is used for slot-aware variants
and avoids duplicated conditional branches for undefined slots.
@ITBoomBKStudio
Copy link
Author

Actionable comments posted: 1

🧹 Nitpick comments (2)

packages/core/system-rsc/src/extend-variants.d.ts (2)> 51-54: CompoundVariants slots support looks good; consider a small readability tweak.

You can express the value type via the existing helper to reduce repetition.
Apply:

 type CompoundVariants<V, SV, S> = Array<
   VariantValue<V, SV> &
-    ClassProp<S extends undefined ? ClassValue : ClassValue | SlotsClassValue<S>>
+    ClassProp<ClassValue | GetSuggestedValues<S>>
 >;

98-99: Use the already-bound S instead of recomputing ComponentSlots.
Keeps generics consistent and may help inference.

-    CV extends CompoundVariants<V, SV, ComponentSlots<CP>>,
+    CV extends CompoundVariants<V, SV, S>,

📜 Review details

I’m keeping CV extends CompoundVariants<V, SV, ComponentSlots<CP>> on purpose.
Using S here narrows slot suggestions and validation to only the user-provided slots, while the extended component should expose all base component slots. ComponentSlots reflects the full slot surface of the base component and keeps compoundVariants authoring ergonomic and type-safe.

@ITBoomBKStudio ITBoomBKStudio changed the title Fix/compound variants types [BUG] - extendVariants Fix/compound variants types Oct 24, 2025
@ITBoomBKStudio ITBoomBKStudio changed the title [BUG] - extendVariants Fix/compound variants types extendVariants Fix/compound variants types Oct 24, 2025
…NonNullable

Simplifies the return type of ExtendVariants to ensure no required props
are enforced at the HOC level. This aligns with the intended API contract
where extended components expose all props as optional.

- All keys (CP ∪ V) are optional
- Preserve CP type hints and booleanized V values
- Added NonNullable<V[key]> guard to prevent undefined indexing
@ITBoomBKStudio
Copy link
Author

Added the final compoundVariants test - all cases now covered and passing
image

Copy link
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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6336fa8 and b770b54.

📒 Files selected for processing (1)
  • packages/core/system-rsc/__tests__/extend-variants.test.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/core/system-rsc/__tests__/extend-variants.test.tsx (4)
packages/core/system-rsc/src/extend-variants.js (1)
  • styles (105-105)
packages/core/system-rsc/src/extend-variants.d.ts (1)
  • ExtendVariantWithSlotsProps (76-80)
packages/core/system/src/index.ts (1)
  • ExtendVariantWithSlotsProps (16-16)
packages/core/system-rsc/src/index.ts (1)
  • ExtendVariantWithSlotsProps (29-29)
🔇 Additional comments (3)
packages/core/system-rsc/__tests__/extend-variants.test.tsx (3)

1-1: LGTM!

The import of ExtendVariantWithSlotsProps is necessary to support the updated type signature for the slots-based test helper and aligns with the PR's objective to expose slots-aware compound variant types.


40-40: LGTM!

The parameter type change from ExtendVariantProps to ExtendVariantWithSlotsProps correctly reflects that slots-based components require compound variants to support per-slot class definitions (e.g., class: { header: "..." }), which is demonstrated in the new test at lines 274-276.


264-292: Well-structured test for slots-aware compound variants.

This test effectively validates both forms of compound variant classes:

  • String class applied to the base slot (line 270)
  • Record class applied to specific slots like header (lines 274-276)

The test aligns well with the PR objective to ensure proper type inference for slots-aware compound variants.

However, the same concern applies here: both compound variants reference radius: "md" (lines 269, 273), which is not defined in the extended radius variants. If this is intentional to test compound variants referencing base component variants not overridden in the extension, consider adding a comment explaining this edge case.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 25, 2025

Open in StackBlitz

@heroui/accordion

npm i https://pkg.pr.new/@heroui/accordion@5847

@heroui/alert

npm i https://pkg.pr.new/@heroui/alert@5847

@heroui/autocomplete

npm i https://pkg.pr.new/@heroui/autocomplete@5847

@heroui/avatar

npm i https://pkg.pr.new/@heroui/avatar@5847

@heroui/badge

npm i https://pkg.pr.new/@heroui/badge@5847

@heroui/breadcrumbs

npm i https://pkg.pr.new/@heroui/breadcrumbs@5847

@heroui/button

npm i https://pkg.pr.new/@heroui/button@5847

@heroui/calendar

npm i https://pkg.pr.new/@heroui/calendar@5847

@heroui/card

npm i https://pkg.pr.new/@heroui/card@5847

@heroui/checkbox

npm i https://pkg.pr.new/@heroui/checkbox@5847

@heroui/chip

npm i https://pkg.pr.new/@heroui/chip@5847

@heroui/code

npm i https://pkg.pr.new/@heroui/code@5847

@heroui/date-input

npm i https://pkg.pr.new/@heroui/date-input@5847

@heroui/date-picker

npm i https://pkg.pr.new/@heroui/date-picker@5847

@heroui/divider

npm i https://pkg.pr.new/@heroui/divider@5847

@heroui/drawer

npm i https://pkg.pr.new/@heroui/drawer@5847

@heroui/dropdown

npm i https://pkg.pr.new/@heroui/dropdown@5847

@heroui/form

npm i https://pkg.pr.new/@heroui/form@5847

@heroui/image

npm i https://pkg.pr.new/@heroui/image@5847

@heroui/input

npm i https://pkg.pr.new/@heroui/input@5847

@heroui/input-otp

npm i https://pkg.pr.new/@heroui/input-otp@5847

@heroui/kbd

npm i https://pkg.pr.new/@heroui/kbd@5847

@heroui/link

npm i https://pkg.pr.new/@heroui/link@5847

@heroui/listbox

npm i https://pkg.pr.new/@heroui/listbox@5847

@heroui/menu

npm i https://pkg.pr.new/@heroui/menu@5847

@heroui/modal

npm i https://pkg.pr.new/@heroui/modal@5847

@heroui/navbar

npm i https://pkg.pr.new/@heroui/navbar@5847

@heroui/number-input

npm i https://pkg.pr.new/@heroui/number-input@5847

@heroui/pagination

npm i https://pkg.pr.new/@heroui/pagination@5847

@heroui/popover

npm i https://pkg.pr.new/@heroui/popover@5847

@heroui/progress

npm i https://pkg.pr.new/@heroui/progress@5847

@heroui/radio

npm i https://pkg.pr.new/@heroui/radio@5847

@heroui/ripple

npm i https://pkg.pr.new/@heroui/ripple@5847

@heroui/scroll-shadow

npm i https://pkg.pr.new/@heroui/scroll-shadow@5847

@heroui/select

npm i https://pkg.pr.new/@heroui/select@5847

@heroui/skeleton

npm i https://pkg.pr.new/@heroui/skeleton@5847

@heroui/slider

npm i https://pkg.pr.new/@heroui/slider@5847

@heroui/snippet

npm i https://pkg.pr.new/@heroui/snippet@5847

@heroui/spacer

npm i https://pkg.pr.new/@heroui/spacer@5847

@heroui/spinner

npm i https://pkg.pr.new/@heroui/spinner@5847

@heroui/switch

npm i https://pkg.pr.new/@heroui/switch@5847

@heroui/table

npm i https://pkg.pr.new/@heroui/table@5847

@heroui/tabs

npm i https://pkg.pr.new/@heroui/tabs@5847

@heroui/toast

npm i https://pkg.pr.new/@heroui/toast@5847

@heroui/tooltip

npm i https://pkg.pr.new/@heroui/tooltip@5847

@heroui/user

npm i https://pkg.pr.new/@heroui/user@5847

@heroui/react

npm i https://pkg.pr.new/@heroui/react@5847

@heroui/system

npm i https://pkg.pr.new/@heroui/system@5847

@heroui/system-rsc

npm i https://pkg.pr.new/@heroui/system-rsc@5847

@heroui/theme

npm i https://pkg.pr.new/@heroui/theme@5847

@heroui/use-aria-accordion

npm i https://pkg.pr.new/@heroui/use-aria-accordion@5847

@heroui/use-aria-accordion-item

npm i https://pkg.pr.new/@heroui/use-aria-accordion-item@5847

@heroui/use-aria-button

npm i https://pkg.pr.new/@heroui/use-aria-button@5847

@heroui/use-aria-link

npm i https://pkg.pr.new/@heroui/use-aria-link@5847

@heroui/use-aria-modal-overlay

npm i https://pkg.pr.new/@heroui/use-aria-modal-overlay@5847

@heroui/use-aria-multiselect

npm i https://pkg.pr.new/@heroui/use-aria-multiselect@5847

@heroui/use-aria-overlay

npm i https://pkg.pr.new/@heroui/use-aria-overlay@5847

@heroui/use-callback-ref

npm i https://pkg.pr.new/@heroui/use-callback-ref@5847

@heroui/use-clipboard

npm i https://pkg.pr.new/@heroui/use-clipboard@5847

@heroui/use-data-scroll-overflow

npm i https://pkg.pr.new/@heroui/use-data-scroll-overflow@5847

@heroui/use-disclosure

npm i https://pkg.pr.new/@heroui/use-disclosure@5847

@heroui/use-draggable

npm i https://pkg.pr.new/@heroui/use-draggable@5847

@heroui/use-form-reset

npm i https://pkg.pr.new/@heroui/use-form-reset@5847

@heroui/use-image

npm i https://pkg.pr.new/@heroui/use-image@5847

@heroui/use-infinite-scroll

npm i https://pkg.pr.new/@heroui/use-infinite-scroll@5847

@heroui/use-intersection-observer

npm i https://pkg.pr.new/@heroui/use-intersection-observer@5847

@heroui/use-is-mobile

npm i https://pkg.pr.new/@heroui/use-is-mobile@5847

@heroui/use-is-mounted

npm i https://pkg.pr.new/@heroui/use-is-mounted@5847

@heroui/use-measure

npm i https://pkg.pr.new/@heroui/use-measure@5847

@heroui/use-pagination

npm i https://pkg.pr.new/@heroui/use-pagination@5847

@heroui/use-real-shape

npm i https://pkg.pr.new/@heroui/use-real-shape@5847

@heroui/use-ref-state

npm i https://pkg.pr.new/@heroui/use-ref-state@5847

@heroui/use-resize

npm i https://pkg.pr.new/@heroui/use-resize@5847

@heroui/use-safe-layout-effect

npm i https://pkg.pr.new/@heroui/use-safe-layout-effect@5847

@heroui/use-scroll-position

npm i https://pkg.pr.new/@heroui/use-scroll-position@5847

@heroui/use-ssr

npm i https://pkg.pr.new/@heroui/use-ssr@5847

@heroui/use-theme

npm i https://pkg.pr.new/@heroui/use-theme@5847

@heroui/use-update-effect

npm i https://pkg.pr.new/@heroui/use-update-effect@5847

@heroui/use-viewport-size

npm i https://pkg.pr.new/@heroui/use-viewport-size@5847

@heroui/aria-utils

npm i https://pkg.pr.new/@heroui/aria-utils@5847

@heroui/dom-animation

npm i https://pkg.pr.new/@heroui/dom-animation@5847

@heroui/framer-utils

npm i https://pkg.pr.new/@heroui/framer-utils@5847

@heroui/react-rsc-utils

npm i https://pkg.pr.new/@heroui/react-rsc-utils@5847

@heroui/react-utils

npm i https://pkg.pr.new/@heroui/react-utils@5847

@heroui/shared-icons

npm i https://pkg.pr.new/@heroui/shared-icons@5847

@heroui/shared-utils

npm i https://pkg.pr.new/@heroui/shared-utils@5847

@heroui/stories-utils

npm i https://pkg.pr.new/@heroui/stories-utils@5847

@heroui/test-utils

npm i https://pkg.pr.new/@heroui/test-utils@5847

commit: d18110f

Copy link
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: 0

🧹 Nitpick comments (1)
packages/core/system-rsc/__tests__/extend-variants.test.tsx (1)

76-88: Default compound variant fix addresses previous review.

The change from radius: "md" to radius: "none" correctly uses a value from the extended variant set. The fallback logic with styles?.compoundVariants ?? [...] is clean and well-structured.

Optional: Consider adding test coverage for the first compound variant.

The default compound variant at lines 77-81 (shadow: "none" + radius: "none") is never explicitly tested. The test at line 270-284 uses radius: "sm", so only the second compound variant (lines 82-87) is exercised. Adding a test case with radius="none" shadow="none" would verify that the fix for the previous review comment works correctly.

Example test:

test("should apply compound variant when shadow and radius are none", () => {
  const Card2 = createExtendSlotsComponent();
  const {getByTestId} = render(
    <Card2 radius="none" shadow="none">
      Card Content
    </Card2>,
  );
  const baseEl = getByTestId("base");
  expect(baseEl).toHaveClass("rounded-sm"); // from compound variant, overriding radius="none"'s "rounded-xs"
});
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b770b54 and 0021c8f.

📒 Files selected for processing (1)
  • packages/core/system-rsc/__tests__/extend-variants.test.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/core/system-rsc/__tests__/extend-variants.test.tsx (2)
packages/core/system-rsc/src/extend-variants.js (1)
  • styles (105-105)
packages/core/system-rsc/src/extend-variants.d.ts (1)
  • ExtendVariantWithSlotsProps (76-80)
🔇 Additional comments (3)
packages/core/system-rsc/__tests__/extend-variants.test.tsx (3)

1-1: LGTM! Import addition aligns with slots-aware type improvements.

The new ExtendVariantWithSlotsProps type is correctly imported and used on line 40 to support compound variants with slot-specific styling.


40-40: Correct type change for slots-aware compound variants.

Using ExtendVariantWithSlotsProps enables the test helper to accept compound variants with slot-specific class values, matching the updated type system.


270-314: Excellent test coverage for compound variants.

The two new test cases correctly verify:

  1. Default compound variants work alongside regular variants (lines 270-284)
  2. Custom compound variants passed via styles parameter override defaults (lines 286-314)

The tests validate both base-level and slot-specific compound variant application, ensuring the slots-aware type improvements function correctly at runtime.

@ITBoomBKStudio ITBoomBKStudio changed the title extendVariants Fix/compound variants types fix(system-rsc): extendVariants Fix/compound variants types Oct 26, 2025
Replace PropsWithoutRef with explicit Exclude<'ref'> in mapped keys and
intersect with RefAttributes<InferRef<C>>. This prevents @types/react’s
internal UNDEFINED_VOID_ONLY from leaking into the public .d.ts and fixes
declaration emit for components like extended Autocomplete.
Copy link
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: 0

🧹 Nitpick comments (1)
packages/core/system-rsc/src/extend-variants.d.ts (1)

103-107: Verify that making all keys optional is the intended behavior.

The current mapped type uses ?: on line 104, making all keys optional—including those that may be required in the base component props CP. A past review raised this concern and suggested preserving CP's original optionality by separating CP keys (which would retain their required/optional status) from V-only keys (which would be optional).

That review was marked as addressed by updating tests to provide required props. If the intention is for extended components to always have optional props, the current implementation is correct. However, if base component contracts should be preserved (required props remain required), consider this alternative:

{
  // CP keys preserve their original optionality
  [K in keyof CP]: K extends keyof V
    ? CP[K] | StringToBoolean<keyof NonNullable<V[K]>>
    : CP[K]
} & {
  // V-only keys are optional
  [K in Exclude<keyof V, keyof CP>]?: StringToBoolean<keyof NonNullable<V[K]>>
} & RefAttributes<InferRef<C>>

This would maintain stricter type-safety by preventing accidental omission of required base props while still allowing variant props to be optional.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73032ce and 8baeab2.

📒 Files selected for processing (1)
  • packages/core/system-rsc/src/extend-variants.d.ts (4 hunks)
🔇 Additional comments (3)
packages/core/system-rsc/src/extend-variants.d.ts (3)

2-2: LGTM! Missing import added.

The JSXElementConstructor import is correctly added and used in the ComponentProps helper (line 12) and ExtendVariants constraint (line 86).


46-48: Slots-aware compound variants implemented correctly.

The addition of the S generic parameter and use of GetSuggestedValues<S> properly extends compound variants to support both plain class values and slot-keyed class values, maintaining backward compatibility when slots are undefined.


92-92: Appropriate use of ComponentSlots<CP> for broader type coverage.

Using ComponentSlots<CP> instead of the bound generic S ensures compound variants have access to the full slot surface of the base component, maintaining type-safety and authoring ergonomics as noted in the PR objectives.

@ITBoomBKStudio
Copy link
Author

Actionable comments posted: 0

🧹 Nitpick comments (1)

packages/core/system-rsc/src/extend-variants.d.ts (1)> 103-107: Verify that making all keys optional is the intended behavior.

The current mapped type uses ?: on line 104, making all keys optional—including those that may be required in the base component props CP. A past review raised this concern and suggested preserving CP's original optionality by separating CP keys (which would retain their required/optional status) from V-only keys (which would be optional).
That review was marked as addressed by updating tests to provide required props. If the intention is for extended components to always have optional props, the current implementation is correct. However, if base component contracts should be preserved (required props remain required), consider this alternative:

{
  // CP keys preserve their original optionality
  [K in keyof CP]: K extends keyof V
    ? CP[K] | StringToBoolean<keyof NonNullable<V[K]>>
    : CP[K]
} & {
  // V-only keys are optional
  [K in Exclude<keyof V, keyof CP>]?: StringToBoolean<keyof NonNullable<V[K]>>
} & RefAttributes<InferRef<C>>

This would maintain stricter type-safety by preventing accidental omission of required base props while still allowing variant props to be optional.

📜 Review details

Yes, this behavior is intentional for components created with extendVariants.
The goal is to make extended components easier to use - we don’t want developers to re-declare every required prop (like color, size, or variant for a Button) when composing or testing variants.

extendVariants is designed as a styling HOC, not a strict type-safe wrapper.
In this context, optional props provide flexibility and reduce boilerplate:
shorter JSX = better developer experience.

If we preserved CP’s required props, every extended component instance would force full prop specification, which defeats the purpose of variant composition.

@ITBoomBKStudio
Copy link
Author

Hey @jrgarciadev, both #5847 and #5848 are ready. They fix system-rsc type & slot inference issues. Would be great to get your review when you have a moment 👀

@jrgarciadev
Copy link
Member

@wingkwong please check this PR

@wingkwong wingkwong added this to the v2.8.6 milestone Oct 28, 2025
@wingkwong wingkwong self-assigned this Oct 29, 2025
@vercel
Copy link

vercel bot commented Oct 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
heroui Ready Ready Preview Comment Oct 29, 2025 5:26pm
heroui-sb Ready Ready Preview Comment Oct 29, 2025 5:26pm

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

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

  1. The changes you added in packages/core/system-rsc/__tests__/extend-variants.test.tsx don't really handle the reported issue. i.e If I just run those new tests in canary branch, they are all passed.
  2. Please merge your PR 5848 changes into this PR so that I could test them in one go.
  3. Please also handle the following cases.

The type error is on to.

export const CustomButton = extendVariants(Button, {});
<Button as={Link} to="/sign-in">
  Sign In
</Button>

The type error is on defaultVariants.classNames.tabList

const CustomTabs = extendVariants(Tabs, {
  slots: {
    tabList: 'bg-white p-0',
    base: 'w-full bg-white rounded-lg overflow-hidden',
    tab: 'py-6',
    tabContent: 'font-medium text-gray-800',
  },
  defaultVariants: {
    variant: 'underlined',
    color: 'primary',
    classNames: {
      tabList: 'bg-white p-0',
      base: 'w-full bg-white rounded-lg overflow-hidden',
      tab: 'py-6',
      tabContent: 'font-medium text-gray-800',
    },
  },
});

@@ -0,0 +1,5 @@
---
"@heroui/system-rsc": major
Copy link
Member

Choose a reason for hiding this comment

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

use patch instead of major

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] - extendVariants does not show variants

4 participants