Reputation: 1235
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:
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.
Upvotes: 15
Views: 40945
Reputation: 594
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.
Upvotes: 0
Reputation: 89394
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.
Upvotes: 10
Reputation: 1719
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]'
Upvotes: 4
Reputation: 6235
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
Upvotes: 3
Reputation: 50338
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.
Upvotes: 11
Reputation: 652
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.
Upvotes: 0
Reputation: 83
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(" ");
}
Upvotes: 2