I was refactoring a component last night. It was a simple button, nothing fancy. But as I stared at the file, something bothered me. It wasn’t the logic. It wasn’t the styles.
It was the imports.
import { useState } from 'react';
import clsx from 'clsx'; // <--- This one.
import styles from './Button.module.css';
We have become addicted to imports. We’ve accepted that to do something as fundamental as toggling a CSS class based on a condition, we need to pull in a utility library and wrap our strings in a function call.
Why?
Why do we treat className like a second-class citizen that only understands strings, forcing us to do the heavy lifting?
I realized that bare code is a luxury. The less I have to import, the less visual noise I have to filter out, and the more I can focus on what the component actually does.
So, I decided to fix it. I didn’t want a wrapper component. I didn’t want a complex build step. I wanted the React runtime itself to be smarter.
Meet clsx-react.
The Problem: The “Utility” Tax
We’ve all written this code thousands of times:
// The "Standard" Way
import clsx from 'clsx';
export const Card = ({ isActive, isDisabled, children }) => {
return (
<div
className={clsx(
'p-4 rounded-lg border',
isActive ? 'border-blue-500' : 'border-gray-200',
isDisabled && 'opacity-50 cursor-not-allowed'
)}
>
{children}
</div>
);
};
It works. It’s fine. But it’s noisy. You have to remember to import clsx. You have to remember the syntax. It breaks the visual flow of the JSX.
The Solution: Native Syntax
What if className just... understood? What if it accepted arrays and objects natively, just like clsx does, but without the import?
With clsx-react, that same component looks like this:
// The "Pure" Way
// No import. No wrapper function. Just data.
export const Card = ({ isActive, isDisabled, children }) => {
return (
<div
className={[
'p-4 rounded-lg border',
isActive ? 'border-blue-500' : 'border-gray-200',
{ 'opacity-50 cursor-not-allowed': isDisabled }
]}
>
{children}
</div>
);
};
Look at that. It’s clean. It reads like a configuration, not a function call. The className prop is no longer a dumb string receiver; it’s an intelligent API.
How It Works (No Magic, Just Standards)
This isn’t a hack. It leverages the standard jsxImportSource capability of modern bundlers (Vite, TypeScript, etc.).
Instead of React’s default runtime creating the element, clsx-react sits in the middle. It intercepts the props, sees an array or object in className, resolves it (using the tiny clsx logic under the hood), and passes a clean string to React.
It happens at the runtime level. Your component code remains pure.
How to Get It
It is zero-dependency (other than the runtime itself) and super lightweight.
Install it:
npm install clsx-react
Tell your compiler to use it:
// tsconfig.json / jsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "clsx-react"
}
}
// or Vite config
export default defineConfig({
plugins: [
react({ jsxImportSource: 'clsx-react' })
]
})
// or Vite config / esbuild
export default defineConfig({
plugins: [react()],
esbuild: {
jsxImportSource: 'clsx-react',
},
});
// or Babel / Webpack
{
"presets": [
[
"@babel/preset-react",
{
"runtime": "automatic",
"importSource": "clsx-react"
}
]
]
}
Conclusion
In a modern developer stack, we often mistake adding tools for adding value. Sometimes, real value comes from removing friction.
By moving the class logic into the runtime, we remove one import from every single file. We remove the visual clutter of function calls in our JSX. We get back to the luxury of writing bare, simple code.
Give it a try. Once you stop importing clsx, you won’t want to go back.
Top comments (1)
I built this because I'm lazy and I love clean code. Let me know what you think!"