Custom components don't accept ClassValue in className — only intrinsic elements get widened types
Problem
reclassify widens className types for intrinsic HTML elements via JSX.IntrinsicElements, but custom React components still expect className: string. This means passing arrays/objects to any custom component is a TypeScript error, even when it works perfectly at runtime.
This is especially painful with component libraries like shadcn/ui + Radix UI, where wrapper components derive their props from React.ComponentProps<typeof RadixPrimitive.Something> or React.ComponentProps<"button">. These resolve through React's own HTMLAttributes/ButtonHTMLAttributes where className is still string.
Example
// ✅ Works — intrinsic element, reclassify widens the type
<div className={['px-2', isActive && 'bg-blue-500']} />
// ❌ TypeScript error — Button is a custom component
// Type '(string | false)[]' is not assignable to type 'string'
<Button className={['px-2', isActive && 'bg-blue-500']} />
Where Button is a typical shadcn component:
function Button({ className, ...props }: React.ComponentProps<"button">) {
return <button className={cn(buttonVariants(), className)} {...props} />
}
At runtime this works fine because cn() (which uses classify()) handles arrays. But TypeScript rejects it.
Current workaround
We had to manually widen className in two places:
- React module augmentation for standard HTML attribute interfaces:
declare module 'react' {
interface HTMLAttributes<T> { className?: ClassValue }
interface ButtonHTMLAttributes<T> { className?: ClassValue }
interface InputHTMLAttributes<T> { className?: ClassValue }
// ... 10+ more interfaces
}
- Changing each shadcn component's props individually:
// Before
function Badge({ className, ...props }: React.ComponentProps<"span">) {
// After
function Badge({ className, ...props }: Omit<React.ComponentProps<"span">, 'className'> & { className?: ClassValue }) {
This is tedious and doesn't scale — every new shadcn component or Radix primitive wrapper needs the same treatment.
Suggestion
reclassify could ship a global type augmentation (opt-in or default) that widens className on React's base HTMLAttributes<T> and SVGAttributes<T>. Since reclassify already widens JSX.IntrinsicElements, extending the underlying React attribute interfaces would make the types consistent for custom components that derive their props from React.ComponentProps<"element">.
Something like a reclassify/types/global entry point:
// In user's project, or auto-included by reclassify:
import type { ClassValue } from 'reclassify';
declare module 'react' {
interface HTMLAttributes<T> {
className?: ClassValue;
}
interface SVGAttributes<T> {
className?: ClassValue;
}
}
This alone would fix ~80% of cases. The remaining Radix-specific props would still need individual fixes, but that's a much smaller surface.
Environment
- reclassify: latest
- React: 19.2.3
- Next.js: 16.1.0
- TypeScript: 5.x
- shadcn/ui + Radix UI
Custom components don't accept ClassValue in className — only intrinsic elements get widened types
Problem
reclassify widens
classNametypes for intrinsic HTML elements viaJSX.IntrinsicElements, but custom React components still expectclassName: string. This means passing arrays/objects to any custom component is a TypeScript error, even when it works perfectly at runtime.This is especially painful with component libraries like shadcn/ui + Radix UI, where wrapper components derive their props from
React.ComponentProps<typeof RadixPrimitive.Something>orReact.ComponentProps<"button">. These resolve through React's ownHTMLAttributes/ButtonHTMLAttributeswhereclassNameis stillstring.Example
Where Button is a typical shadcn component:
At runtime this works fine because
cn()(which usesclassify()) handles arrays. But TypeScript rejects it.Current workaround
We had to manually widen
classNamein two places:This is tedious and doesn't scale — every new shadcn component or Radix primitive wrapper needs the same treatment.
Suggestion
reclassify could ship a global type augmentation (opt-in or default) that widens
classNameon React's baseHTMLAttributes<T>andSVGAttributes<T>. Since reclassify already widensJSX.IntrinsicElements, extending the underlying React attribute interfaces would make the types consistent for custom components that derive their props fromReact.ComponentProps<"element">.Something like a
reclassify/types/globalentry point:This alone would fix ~80% of cases. The remaining Radix-specific props would still need individual fixes, but that's a much smaller surface.
Environment