Reputation: 363
I am trying to join tailwind classes and apply them to a button using clsx. One class is applied by default 'rounded-none'
and the other one is passed in as a prop
const Button = ({
children, ...props
}): JSX.Element => {
return (
<ADButton
className={clsx('rounded-none', props.className)}
{...props}
>
{children}
</ADButton>
);
};
Let's say I have added padding-top: 0px;
to the button like shown below
<Button
color="primary"
className="pt-0"
>
{t('btn.add')}
</Button>
The joined className should look like 'rounded-none pt-0'
. If no className prop is passed, then just apply ‘rounded-none’
The problem am having right now is ‘rounded-none’ only gets applied to buttons without className prop. On buttons with className prop, only className prop gets applied but not ‘rounded-none’. How can I fix this so that both classes are joined and applied to the button?
Upvotes: 12
Views: 37859
Reputation: 1441
You could use a merge of clsx and twMerge twMerge to efficiently merge Tailwind CSS classes in JS without style conflict. Be carefull to dont override your previous classes
import clsx, { ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes))
Upvotes: 20
Reputation: 25070
You can combine both clsx
and twMerge
Utility Function /lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Use it as page.tsx
import { cn } from "@/lib/utils";
export default function Component(){
return <div className={cn(
"bg-black font-sans",
"bg-white h-full",
{
"px-5":pending, // if pending is true apply padding
}
)}
/>
}
For explanation refer:
Upvotes: 6
Reputation: 21
That's because you put the {...props}
after the className
attribute. The className
gets overridden by the className in the props
.
The simple solution is to move the {...props}
before the className
.
const Button = ({
children, ...props
}): JSX.Element => {
return (
<ADButton
{...props}
className={clsx('rounded-none', props.className)}
>
{children}
</ADButton>
);
};
Upvotes: 2
Reputation: 31
You shouldn't use reserved names. Even if className isn't properly 'reserved' you could avoid conflict by renaming your props and make things easier to debug : "classes", "appendClasses", "additionnalClasses", "optClasses" etc...
I recommend you a recent library - CSS COMPONENTS - for easily create components and variants. You can use it with your favorite CSS library, such as tailwind. I actually find it perfect for tailwindCSS.
All the best
Upvotes: 0
Reputation: 818
You're having this issue because the default prop of ADButton
is set to className
and the extra prop you're passing to the Button component is set to className
as well. In effect, you are overriding the default className
with your newly passed className
prop. If there are are 2 similarly named props, React will choose the one that is declared later in the event of a conflict.
So this:
<ADButton
className={clsx('rounded-none', 'pt-0')}
// I am declared later so I win
className='pt-0'
>
{children}
</ADButton>
becomes:
<ADButton
className="pt-0"
>
{children}
</ADButton>
Here's one solution:
const Button = ({
children, ...props
}) => {
const { classNameDestructured = "", ...rest } = props;
return (
<ADButton
className={clsx('rounded-none', classNameDestructured)}
{...rest}
>
{children}
</ADButton>
);
};
You destructure props
and set a default for classNameDestructured
. This allows you to declare Button
without additional props:
<Button>
{t('btn.add')}
</Button>
Then you pass classNameDestructured
as an argument to your clsx()
function. Your additional classes are then joined and applied to the button.
This works because className
isn't declared twice as a prop in ADButton
anymore so we've eliminated the overriding conflict.
Upvotes: 6