Design system: styles override using TailwindCSS - javascript

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.

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.

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(" ");
}

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

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.

Related

Override Mantine styles to only one element

In my React project, I am using Mantine's library accordion in two different components: Search.jsx and Profile.jsx.
The point is I need to accurately customize its styles only in Profile so I inspected the element and discovered that the default Mantine's class is named mantine-bgzycs. I applied styles to that class in Profile.css and it worked as I wanted.
The problem is that affects to Search's accordion element too.
When I inspect I can see also a Mantine's default ID but it changes dinamically.
I tried to envolve the elemen with a div and apply styles but most of them are overwritten by Mantine.
QUESTIONS:
Is there a way to apply styles only to one element of the same class?
Is there a way to restrict styles between React components?
Is there a way to access to Mantine's source and edit accurately an element styles?
Thanks very much in advance!
There is more than one way to achieve what you're after. My preferred approach is documented in the Styles API panel of the Accordion documentation and in the theming documentation under create styles and Styles API
Here is an example:
import { createStyles, Accordion } from '#mantine/core';
// define custom classes - includes access to theme object
const useStyles = createStyles((theme) => ({
item: {
backgroundColor: "red",
},
}));
function Demo() {
const { classes } = useStyles();
return (
<Accordion
// apply custom classes to target Styles API properties
classNames={{ item: classes.item }}>
<Accordion.Item label="Customization">
Blah, blah, blah
</Accordion.Item>
</Accordion>
);
}

Appending styling in reactjs whilst using tailwind

I want to animate an element when the user clicks on a button.
The element displays a count of how many times the button has been clicked. This is managed through a state that is incremented on every button click.
Whenever the button is clicked, the displayed number should bounce/animate, to emphasize its change.
In essence I want to conditionally append a CSS class. There's many examples of how to do this, but almost all of them only have a single class applied to them, not lots of classes.
I use tailwindcss for styling. This means lots of CSS classes on lots of elements.
What I have considered
String concatenation
Having a classNames array, where the animation class can be added and removed easily.
const [count, setCount] = useState(0);
const [classNames, setClassNames] = useState([
'text-2xl', 'mb-2', 'font-bold', 'text-black', 'dark:text-white'
]);
const countElement = <p className={classNames.join(' ')}>{count}</p>;
const handleClick = () => {
setCount(count + 1);
// Append the animation class.
setClassNames(classNames.concat('animate-bounce'));
// Remove the animation class after the animation is done.
setTimeout(() => {
setClassNames(classNames.filter(className =>
className !== 'animate-bounce'));
}, 1000);
};
return <>
{count > 0 ? countElement : null}
<button className="px-4 py-3 mb-2 rounded-lg font-semibold transition-colors duration-300
bg-cardinal-default hover:bg-cardinal-dark active:bg-cardinal-darkest
text-black dark:text-white"
onClick={handleClick}>Click me</button>
</>;
Though with tailwindcss this is kind of ugly as it separates all of the styling from its actual element. Not exactly ideal. It feels very hacky.
Animate an inner element
const [isBouncing, setIsBouncing] = useState(false);
const countElement = (
<p className="text-2xl mb-2 font-bold text-black dark:text-white">
<span className={isBouncing ? 'inline-block animate-spin' : ''}>{count}</span>
</p>
);
...
setCount(count + 1);
setIsBouncing(true);
setTimeout(() => setIsBouncing(false), 1000);
...
This is better, yet it still requires me to set inline-block
Is there a better way of doing this? Or have I exhausted all worthwhile solutions?
You could use a css-in-js solution like styled-components which will make it really easy to enable/disable styles.
For tailwind there is a library to help with css-in-js called twin
This allows you to conditionally enable styles:
const Button = styled.button`
${tw`your tailwind styles here`}
${props => props.shouldAnimate ? `some animation css here` : "" }
`
TailwindCSS is a utility first CSS Framework, so there's probably no other option other than appending classes to your elements.
If you were to use an animation library, like Framer Motion, it wouldn't be necessary to append Tailwind classes. On the other hand, you would need to create the animation yourself, which is quite easy.
Regarding your considerations, the first alternative seems indeed very hacky, whereas the second one doesn't and should work as well.
Nonetheless, it's up to you to decide which method suits you best.

Angular 11 - Add icons with different styles to a custom button component

In my Angular 11 App I've implemented a custom button component.
For styling I use TailwindCSS and TailwindUI.
The button can have multiple colors(red, blue, gray, etc) and also different sizes (xs, sm, md, lg, xl).
I want to create a variant for these buttons: a button with a leading icon, as shown here:
https://tailwindui.com/components/application-ui/elements/buttons#component-8a4b7248253ad4c9ee892c655d7ff5ec.
For the icons, I use the following library:
https://ng-heroicons.dimaslz.dev/
An icon is a component, like: <mail-solid-icon></mail-solid-icon>, <bookmark-solid-icon></bookmark-solid-icon> etc.
Because of the different sizes (xs, sm, md, lg, xl) of the button, I need to add custom Tailwind classes to the icon. For example:
<app-button [size]="'xs'">
<mail-solid-iconclass="-ml-0.5 mr-2" [size]="16"></mail-solid-icon>
Button text
</app-button>
<app-button [size]="'xl'">
<bookmark-solid-icon class="-ml-1 mr-3 h-5 w-5" [size]="20"></bookmark-solid-icon>
Button text
Desired result:
I want to be able to provide only the icon component and then, in button's component class, to add the classes -ml-0.5 mr-2, or -ml-1 mr-3 h-5 w-5; as well as the size property.
Example of use in a template:
<app-button [size]="'xl'">
<bookmark-solid-icon></bookmark-solid-icon>
Button text
</app-button>
Output:
<app-button [size]="'xl'">
<bookmark-solid-icon class="-ml-1 mr-3 h-5 w-5" [size]="20"></bookmark-solid-icon>
Button text
</app-button>
I've tried to use a custom directive and get it using #ContentChild, but I can't add the classes to it.
Thanks!
Stackblitz example:
https://stackblitz.com/edit/angular-ivy-6gpcby?file=src%2Fapp%2Fbutton%2Fbutton.component.ts
This is the sample code that may help you to achieve what you want to do: https://stackblitz.com/edit/angular-ivy-xymefd?file=src%2Fapp%2Fbutton%2Fbutton.component.ts
What I changed from your code are:
Add ButtonIconDirective to AppModule declarations.
Use appButtonIcon instead of docsButtonIcon.
Inject ElementRef into ButtonIconDirective. (We will use ElementRef in ButtonComponent).
Append a template variable onto <bookmark-solid-icon> to get its component instance.
Rewrite ngAfterContentInit in ButtonComponent method to update size and add new CSS classes at runtime:
public ngAfterContentInit(): void {
console.log( 'this.icon: ', !!this.icon);
if (this.icon) {
// reference to ng-heroicons component source code
this.icon.style = '';
this.icon.size = 50;
this.icon.renderStyle();
this.renderer.addClass(this.iconi.er.nativeElement, '-ml-1');
this.renderer.addClass(this.iconi.er.nativeElement, 'mr-3');
this.renderer.addClass(this.iconi.er.nativeElement, 'h-5');
this.renderer.addClass(this.iconi.er.nativeElement, 'w-5');
console.log(this.icon.style);
}
}

Is there a way to increase specificity by adding the element with Emotion?

We have a styled component that and when it compiles it adds a class name such as this:
<div class="app">
<a class="css-hash">link</a>
</div>
This is all good, except for instances where someone has CSS declared similar to the following:
.app a { color: red }
The .css-hash is no longer picked up if it has a color rule. This could be resolved if we could have emotion create the CSS in a manner like "a.css-hash". In that case whatever we have as part of emotion's .css-hash would be applied over other styles that are declared elsewhere in the page.
It's possible that we're approaching this incorrectly. I tried searching for a solution in various places, but with no luck.
You can use the & selector more than once to increase specificity:
import { css } from '#emotion/css'
const myClassName = css({
// Using `&` twice to increase specificity
'&&': {
backgroundColor: 'red'
}
})
const MyComponent = () => {
return <div className={myClassName}>hello</div>
}
& will simply copy your classname, so if your class name is .css-hash, then && will become .css-hash.css-hash. You can use more & to further increase specificity.
See Sass Ampersand docs.

Styling react-toolbox elements with className

I seem to be having some issues in regards to styling the components without using a theme. I just want to change a couple of colors without needing to create a new theme per element.
In this case, I just want to change the color of the bar to a brownish color and right now I have an input class as follows:
import style from './style.scss'
const TextInput = (props) => {
<Input className={style.textInput} {...props} />
}
And in my style.scss file:
.textInput {
.bar {
background-color: #663300;
}
}
Any help would be appreciated.
ClassName doesn't work like that.
You can't pass a Css style to a className, that is wrong.
Either pass the class names you want to apply as a string (in your case I guess it would be className="textInput bar") or you can create a className with classNames library (in any case the final result will be a string).
Just make sure you're styles are included in the page that the component is going to render and react will be smart enough to render the correct css class for each component.
As you can check in here ClassName is a string

Categories