Imagine if every function component in your project automatically received a className
prop. What if this className
prop was automatically forwarded to the JSX elements returned by the component? What if you never had to manually merge class names with a utility function (e.g. cn
, clsx
, etc.) ever again?
This Vite plugin gives you that.
Imagine you have a component like this:
import { cn } from '@lib/utils'
export function Button({ className }) {
return <div className={cn('btn', className)} />
}
With this plugin, you can now write:
export function Button() {
return <div className="btn" />
}
Note that only .jsx
and .tsx
files are transformed. Any JSX that's been compiled to JS will not be transformed. This can lead to false positives in the type definitions, since the className
prop will be added to the React.Attributes
interface. It's recommended for JSX libraries to use "jsx": "preserve"
in their tsconfig.json
file, rather than compiling JSX to JS.
pnpm add vite-react-classname -D
import reactClassName from 'vite-react-classname'
export default defineConfig({
plugins: [reactClassName()],
})
skipNodeModules?: boolean
Whether to skip transforming components innode_modules
. Note that only uncompiled JSX is transformed (notReact.createElement
orjsx
calls). Defaults tofalse
.
Add the following "triple-slash directive" to a module in your project (usually the entry module):
/// <reference types="vite-react-classname/types/react" />
This will add the className
prop to the React.Attributes
interface.
This plugin also adds a class
prop to every component. This prop gets transformed into a className
prop at compile time. This has 2 main benefits:
-
It's more concise than
className
-
It supports an inline array expression:
function Component() { const [foo, setFoo] = useState(false) return <div class={['btn', foo && 'foo']}> }
The
class
array must be a static array. It's transformed into a$join
function call at compile time, which filters out falsy values, flattens nested arrays (which can be dynamic), and joins the class names with a space.
Warning
You cannot use both class
and className
on the same JSX element.
- Most function components (arrow syntax,
function
keyword), but not method definition syntax. - Class components are NOT supported (PR welcome).
Not really. It works with any JSX library, but currently, the package only ships with a react
type definition. That doesn't mean you can't use it with other libraries; you'll just have to add your own type definitions. PRs welcome!
- When props destructuring is encountered, and the
className
prop is explicitly declared, it's assumed theclassName
prop is being forwarded. No transformation is done in this case.// ❌ Component is not transformed function Example({ className, ...props }) { return <div className={cn('btn', className)} {...props} /> }
- When the
props
variable is spread into a JSX element, it's assumed theclassName
prop is being forwarded. No transformation is done in this case.// ❌ Component is not transformed function SpreadExample(props) { return <div {...props} /> } // ✅ Component is transformed function AnotherExample(props) { return <div {...props} className="btn" /> }
- Context providers are ignored. Their immediate JSX child is transformed instead.
// ✅ The props argument has className added function MyComponent({ xyz }) { return ( // ❌ Provider is not transformed <MyContextProvider value={xyz}> // ✅ …but its child is forwarded the className value <div className="inset-0 bg-red-500" /> </MyContextProvider> ) }
Here are some ideas for improvements:
- Add support for class components
- Add support for other JSX libraries
Contributions are welcome!