15

I am trying to create a Design System using ReactJS and TailwindCSS.

I created a default Button component with basic styling as follow:

import React from "react";
import classNames from "classnames";

const Button = React.forwardRef(
  ({ children, className = "", onClick }, ref) => {
    const buttonClasses = classNames(
      className,
      "w-24 py-3 bg-red-500 text-white font-bold rounded-full"
    );

    const commonProps = {
      className: buttonClasses,
      onClick,
      ref
    };

    return React.createElement(
      "button",
      { ...commonProps, type: "button" },
      children
    );
  }
);

export default Button;

I then use the Button in my page like:

import Button from "../src/components/Button";

export default function IndexPage() {
  return (
    <div>
      <Button onClick={() => console.log("TODO")}>Vanilla Button</Button>
      <div className="h-2" />
      <Button
        className="w-6 py-2 bg-blue-500 rounded-sm"
        onClick={() => console.log("TODO")}
      >
        Custom Button
      </Button>
    </div>
  );
}

This is what is displayed:

preview

Some attributes are overridden like the background-color but some aren't (the rest).

The reason is the classes provided by TailwindCSS are written in an order where bg-blue-500 is placed after bg-red-500, therefore overriding it. On the other hand, the other classes provided in the custom button are written before the classes on the base button, therefore not overriding the styles.

This behavior is happening with TailwindCSS but might occurs with any other styling approach as far as the class order can produce this scenario.

Do you have any workaround / solution to enable this kind of customisation?

Here is a full CodeSanbox if needed.

7 Answers 7

10

One approach is to extract classes from your component using Tailwind's @apply in your components layer.

/* main.css */

@layer components {
    .base-button {
        @apply w-24 py-3 bg-red-500 text-white font-bold rounded-full;
    }
}
// Button.js

const Button = React.forwardRef(({ children, className = "", onClick }, ref) => {
    const buttonClasses = classNames("base-button", className);

    // ...
);

This will extract the styles into the new base-button class, meaning they can easily be overwritten by the utility classes you pass to the Button component.

3
  • Thank I found the use of @apply in the doc this morning but didn't had the time to try it out until now. Your answer is confirming that is should be the right way to handle this. Testing it right away. Commented Mar 11, 2021 at 19:32
  • 1
    Confirming that it is working as expected and seems the right approach to solve this ty. Commented Mar 11, 2021 at 20:14
  • Any way of doing this with Styled Components in a Next app? Tried using the directive from within the parent styled component and it doesn't do anything
    – GMaiolo
    Commented Dec 28, 2021 at 17:50
8

Arbitrary variants can be used to increase the specificity of the generated selector to allow later classes to always be applied.

In this scenario, for instance, the [&&]:py-2 class can be used to overwrite the styles from the py-3 class. To overwrite this again, just add more ampersands (&), e.g. adding the [&&&]:py-0 class after the previous two classes would remove the vertical padding.

Finally, in special cases, the !important modifier can be applied to override nearly anything else. This can be done by adding ! in front of the class name, e.g. !py-2. Use this sparingly, as it can make styles difficult to maintain and modify later on.

For an explanation of why this phenomenon occurs, see the Multiple Same CSS Classes issue.

1
  • This is max dope. Now we can control specificity just by adding more &s!
    – geoyws
    Commented Sep 8, 2023 at 2:28
2

Another approach to create reusable React components using Tailwind is as follows..

Read this gist

https://gist.github.com/RobinMalfait/490a0560a7cfde985d435ad93f8094c5

for an excellent example.

Avoid using className as a prop. Otherwise, it'd be difficult for you to know what state your component is in. If you want to add an extra class, you can easily extend.

You need a helper for combining classname strings conditionally. Robert, the writer of this gist, shared the helper function also with us:

export function classNames(...classes: (false | null | undefined | string)[]) {
  return classes.filter(Boolean).join(" ");
}
1
  • 1
    The classNames package on npm already does this and is being used in the original post. Commented Dec 25, 2021 at 4:39
2

To have Tailwind CSS override material theming (or something else for that matter) one could apply !important to all tailwind utilities with configuration to module.exports.

The important option lets you control whether or not Tailwind’s utilities should be marked with !important. This can be really useful when using Tailwind with existing CSS that has high specificity selectors.

To generate utilities as !important, set the important key in your configuration options to true:

tailwind.config.js

module.exports = {
  important: true
}

https://tailwindcss.com/docs/configuration#important

1
  • This has an effect for my context: a browser extension that dynamically injects new HTML+tailwind onto webpages. For many webpage's, their original CSS (like Bootstrap) was overriding much of the core Tailwind classes. This solution seems to fix some problematic webpage instances. Still though, some webpage's custom classes are not being overridden by TW core classes =(.
    – Kalnode
    Commented Feb 10, 2023 at 22:41
2

The package tailwind-merge handles that.

import { twMerge } from 'tailwind-merge'

twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
0
0

To solve, I recommend doing what Bootstrap does. Use a default class for your default button like:

.button {
  width: 2rem;
  background-color: red;
  border-radius: 0.25rem;
}

Then when customizing a button you should apply classes that either come after the button class in your CSS file, or come in a different CSS file that is called after your default CSS file, or use the !important declaration.

Old answer Use your browser developer tools to observe how your browser is loading CSS styles on an element. For example, in Chrome, right-click on the custom button and select "Inspect". A DevTools window will open and the element will be highlighted in the DOM.

On the right, you should have a Styles pane. There, you'll see a list of all the CSS styles being applied to the element. Styles with strikethroughs are being overridden by styles called by other CSS classes or inline styles.

In your case, the custom button has both the "CommonProps" classes and the classes you're adding in IndexPage. For example, both class w-6 and class w-24.

Class w-24 is overriding class w-6 because of CSS precedence. Read more about CSS precedence here. Check out rule #3 in the accepted answer. I think that's what's happening to you.

To solve, you may want to remove some classes from commonProps. Or use the !important declaration on some classes. This is the part of your design system that you need to think through. Look at how other systems like Bootstrap have done it.

5
  • Thank you for you quick answer. Indeed I know that the problem is linked to the CSS precedence, that is what I am also explaining in the question itself. Thank you anyway for the effort. Commented Mar 10, 2021 at 21:34
  • @Florian_L Sorry, I didn't read your whole question and thought I knew it all. Updated my answer. Commented Mar 10, 2021 at 21:35
  • yes I know how to solve this ordering issue with a regular css approach. However, when using tailwindcss.com, as an utility first framework, you are not the one writing the CSS but more composing your style using the defined classes. Want I want to know is if there is any option for me accomplishing what I with TailwindCSS using at 100%. Commented Mar 10, 2021 at 21:47
  • @Florian_L I don't think you can have base styles just using Tailwind. Check out the docs on that subject: tailwindcss.com/docs/adding-base-styles They recommend using their Preflight style sheet for that. Commented Mar 12, 2021 at 18:05
  • Yes I agree. The way to go was to use tailwindcss.com/docs/extracting-components Commented Mar 12, 2021 at 18:06
0

The tailwind-unimportant plugin for Tailwind solves this problem if you don't want to use tw-merge or aren't using JavaScript. It adds a variant that reduces the specificity of the component classes, instead of having to use important or otherwise increase specificity in your pages.

Your button classes would change to:

-:w-24 -:py-3 -:bg-red-500 -:text-white -:font-bold -:rounded-full

Then when you add your classes in the page, they would be applied in preference to the "unimportant" classes.

Not the answer you're looking for? Browse other questions tagged or ask your own question.