frre tyy
frre tyy

Reputation: 363

How to properly join tailwind css classes using clsx?

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

Answers (5)

Daher
Daher

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

krishnaacharyaa
krishnaacharyaa

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

Fariz Sofyan
Fariz Sofyan

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

Maxiim3
Maxiim3

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

neldeles
neldeles

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

Related Questions