CSS Modules, React and Overriding CSS Classes - css

I am using a React component library (React Toolbox), which is outputting this class in their Tab component using CSS Modules and Webpack: label___1nGy active___1Jur tab___Le7N The tab is a className prop I am passing down. The label and active classes are coming from the library. I want to apply a different set of styles on active, something like .tab.active where tab is referring to the styles I have created and active matches the generated selector the library has created but cannot figure out how to do this with css-modules. I need to override this dynamically selector: .label___1nGy.active___1Jur.
[]]2
[]3

Old post but still relevant, so adding an answer to help those with similar issue
While not inherently possible in CSS modules alone, the author of the react-toolbox library has actually solved this particular problem very nicely
Read their github docs which are more in depth on the subject at https://github.com/react-toolbox/react-toolbox#customizing-components
A list of the themeable classes for a particular component is given on the component demo page on their site too
In your case as well as passing a className for tab, you would also create a theme with classes that overrides that desired parts of the default theme and pass that as the theme prop. For example something alog the lines of...
MyComponentWithTabs.css
.tab {
color: white;
}
MyTabTheme.css
.active {
color: hotpink;
}
MyComponentWithTabs.js
import styles from './MyComponentWithTabs.css'
import tabTheme from './MyTabTheme.css'
// blah blah...
return <Tab key={index} className={styles.tab} theme={tabTheme} />
Under the surface this uses a decorator they have abstracted into separate library react-css-themr, I recommend giving that a read too as it explains how the defaults are composed with your overrides in much greater depth, including the different merging strategies they use

I had a similar case, and I solved it like so:
import classNames from 'classnames';
...
const activeClassName = {};
activeClassName[`${styles.active}`] = this.props.isActive;
const elementClassNames = classNames(styles.element, activeClassName);
return <div className={elementClassNames} />
I'm using the classnames package to dynamically add the active class based off of the isActive prop. Instead of an isActive prop you can provide any boolean value.
A more terse way of doing this may be:
const elementClassNames = classNames(styles.element, {[styles.active]: this.props.isActive});

CSS modules won't allow you to safely override the active className (largely by design). Really it should be exposed via an API, e.g. 'activeClassName'.
If the maintainers disagree or you need this quick then you can quite easily add your own active className because your implementing component is managing the index state:
import {Tab, Tabs} from 'react-toolbox';
import styles from './MyTabs.css';
class MyTabs extends React.Component {
state = {
index: 1
};
handleTabChange(index) {
this.setState({ index });
}
render() {
const { index } = this.state;
return (
<Tabs index={index} onChange={this.handleTabChange}>
<Tab label="Tab0" className={index === 0 ? styles.active : null}>
Tab 0 content
</Tab>
<Tab label="Tab1" className={index === 1 ? styles.active : null}>
Tab 1 content
</Tab>
</Tabs>
);
}
}
Disclaimer: Code is untested.

Group style loader
You can use the group-style-lader to override the style of the components. This loader gives you an easy and intuitive way of override the style of the components.
Configure the loader
Install the loader
npm install --save-dev group-style-loader
Configure the loader in your webpack settings
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
'group-style-loader',
'style-loader',
{
loader: "css-loader",
options: {
modules: true
}
}
]
}
]
}
};
You only need to put it, before the style-loader or the mini-css-extract-plugin loader.
Override the style of the components
The next example show as you can to override the style of the Card component from the App component.
You can define the style of your component as you are used to.
card.css
.container {
width: 300px;
height: 400px;
border-radius: 8px;
}
.title {
font-weight: bold;
font-size: 24px;
}
.summary {
margin-top: 24px;
font-size: 20px;
}
The unique difference is that in the Card component, you going to use the mergeStyle function to merge the custom style with the default style of the component.
card.jsx
import React from 'react';
import { mergeStyle } from './card.css';
export function Card({ customStyle }) {
const style = mergeStyle(customStyle);
return (
<div className={style.container}>
<div className={style.title}>Title</div>
<div className={style.summary}>
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor.
</div>
</div>
)
}
Then, in the App component, to override the style of the Card component you need to define the custom style of the Card component using the separator _. All the classes using this separator going to be grouped in the card property of the style object.
app.jsx
.title {
font-size: 32px;
font-weight: bold;
margin: 44px;
}
.list {
display: flex;
}
.card_title {
color: blue;
}
.card_summary {
color: green;
}
Finally, you only need to pass the custom style of the Card component through the customStyle property of it.
app.jsx
import React from 'react';
import { Card } from './card';
import { style } from './app.css';
export function App() {
return (
<div>
<h1 className={style.title}>Group style</h1>
<div className={style.list}>
<Card/>
<Card customStyle={style.card}/>
</div>
</div>
);
}
In the previous example, you have two Cards components, the first uses its default style, and the second uses the customs tyle that we have defined.
You can see a full explanation of how to use the group-style-loader in its repository.

Using :global(.classname) you can override the external classnames.
Even 3rd party library css can be override.
:global(.active) {
color: hotpink;
}

trick in fileName.css
add className-active after declaring className
.className{
background: white;
}
.className-active{
background: black;
}
<div className={'className className-active'} />
<div className={'className-active className'} />
divs always will be black

Related

How to extend styles in react component?

I have a task when I need to extend the styles of a certain element. I take the basic styles through the module, and the additional ones will need to be done inside the function that will be in the component.
How can I extend the styles inside the component if I have already added styles from the module there?
import style from './styles.module.css';
const optionalStyles = {
margin: "30px"
}
<p className={`${style.subtitle} ${optionalStyles}`}>42</p>
<p style={optionalStyles} className={`${style.subtitle}`}>42</p>
You can:
Create another class inside your styles.module (or any other module) and add it conditionally:
import style from './styles.module.css';
<p className={`${style.subtitle} ${someCondition ? style.otherStyles : ''}`}>42</p>
Use inline styles:
import style from './styles.module.css';
const optionalStyles = {
margin: "30px"
}
<p className={style.subtitle} style={someCondition ? optionalStyles : {}}>42</p>

Is there some way with which i can apply css dynamically to my child component?

I have a component which is reusable. This component is called from parent component multiply times and sometimes the background page of the parent component is white and sometimes is black.
My child component generates form tags dynamycally - inputs,selects, textarea.
That means i can't have fixed styles in my css in my component for my content.
So when when the background page is white - i have one style for my inputs - for example black background. When the background page is black i have another style for my inputs - for example white bacgrkound.
To solve this is issue:
i tried
Adding input property in my child component ts file
#Input()
public cssTemplate;
in html
<div [ngClass]="{'form-group-white-bg': cssTemplate == 'white', 'form-group-dark-bg': cssTemplate == 'black'}">
<label for=""></label>
....
In the CHILD component i am sending value for input property depending on where the child component is called
if it is called on page with white background
<app-form-group cssTemplate="black" formControlName="address">
</app-form-group>
if it is called on black bacgrkound
<app-form-group cssTemplate="white" formControlName="address" [data]="{ field: 'address', label: 'Address' }">
</app-form-group>
but the problem here is that sometimes on my parent component this component is called multiply times
on one page can be called 12 times where i need 10 inputs and 2 selects
on other page can be called 15 times etc.
That means that i need to repat my self 15 times
<app-form-group cssTemplate="white" formControlName="address">
</app-form-group>
<app-form-group cssTemplate="white" formControlName="someItherControlName">
</app-form-group>
and everywhere to put cssTemplate="white".
ngFor is not an optin because this child component is called multiply times but not on same place in the HTML structure in the parent.
How can i solve this DRY?
you can add styles in your styles.css (the styles general for all the application). If e.g. you has
.white h1{
color:red;
}
.black h1{
color:green;
}
You can use [ngClass] in the "parent"
<div [ngClass]="toogle?'white':'black'">
<hello name="{{ name }}"></hello>
</div>
<button (click)="toogle=!toogle">toogle</button>
See [stackblitz][1]
NOTE: I used the way [ngClass]="expresion" (where expresion use conditional operator) better that [ngClass]="{'classname1':condition;'classname2':!condition}"
Update about your comment "how can i prevent repeating my self on child call", really I don't understand so much. I don't know if you want to make a directive like, e.g.
#Directive({
selector: 'hello', //<--the selector is the selector of the component
exportAs: 'helloDiv'
})
export class HelloDirective implements OnInit{
constructor(#Self() private component:HelloComponent,private dataService:DataService){
}
ngOnInit(){
console.log(this.component.name)
this.dataService.theme.subscribe(res=>{
this.component.theme=res;
})
}
}
This allow to "extends" the component -in the stackblitz the variable "theme" change-
[1]: https://stackblitz.com/edit/angular-ivy-sjwxyq?file=src%2Fapp%2Fapp.component.html
You can use an input property to create a css class map to pass on to ngClass. This object should be an object of string arrays.
It can be pretty much as complex and contain as many classes and rules as you need it too
#Input() color: 'white' | 'red' | 'hotpink' = 'white';
classMap: any;
ngOnInit() {
this.updateClassMap();
}
updateClassMap() {
this.classMap = {
[this.color]: !!this.color, // Add this class if not null
};
}
Then in the Html simply pass it to ngClass
<div [ngClass]="classMap">
Styling Child Components depending on Parent Component
There are two approaches I commonly take in this scenario
:ng-deep - create a style rule based on a class which is set in your parent component
utilize #ContentChildren() to set a property directly on your child components and call detectChanges() manually after the change.
To adopt the first solution you need to exercise greater care in your css naming rules, as using ng-deep obviously breaks the isolation of those style rules.
To adopt the second approach needs some considering due to it technically circumventing the standard input/output flow in Angular and thus can be a bit of a surprise "undocumented behavior" for any other maintainers of the application.
I'm a bit on the fence whether I prefer one approach over the other. The first approach seems more trivial to me, but it can also cause unintended style rule overwrites, while the second approach involves a lot more scripting and seems a bit of a hack.
Approach 1: ng-deep
Give your parent component an input and update a class on a block-element wrapping your <ng-content>.
create your desired style rules in your child component.
// parent component
#Component(...)
export class FooParent {
#Input() bgStyle: 'light' | 'dark' = 'light';
}
<!-- parent component template -->
<div class="parent" [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}">
<ng-content></ng-content>
</div>
// child.css
::ng-deep .light .child-container {
background-color: lightblue;
}
::ng-deep .dark .child-container {
background-color: royalblue;
}
My targeted element in the example is .child-container, you would write a similar style rule for each element you want to affect.
Approach 2: Using ContentChildren to pass along a value
Add a #ContentChildren() decorator to your parent component which selects for your child components.
inject a ChangeDetectorRef
implement ngAfterViewInit to loop through each child and set the value
call detectChanges() once done.
add the ngClass directive as normally in your child component.
Parent
#Component({
selector: 'parent',
templateUrl: 'parent.component.html',
styleUrls: ['parent.component.scss']
})
export class ParentComponent implements AfterViewInit, OnChanges {
#Input() bgStyle: 'light' | 'dark' = 'light';
#ContentChildren(ChildComponent) childComponents!: QueryList<ChildComponent>;
constructor(private change: ChangeDetectorRef) {
}
ngOnChanges(changes: SimpleChanges) {
if ('bgStyle' in changes) {
this.updateChildComponents();
}
}
updateChildComponents() {
this.childComponents.forEach(child => {
child.bgStyle = this.bgStyle;
});
this.change.detectChanges();
}
ngAfterViewInit() {
this.updateChildComponents();
}
}
<!-- parent.component.html -->
<ng-content></ng-content>
Child
#Component({
selector: 'child',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
bgStyle: 'light' | 'dark' = 'light';
constructor() {
}
ngOnInit(): void {
}
}
<!-- child.component.html -->
<div [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}" class="child-container"></div>
// child.component.css - you would apply styles as you needed obviously.
.child-container {
width: 40px;
height: 40px;
margin: .5rem;
}
.light.child-container {
background-color: lightblue;
}
.dark.child-container {
background-color: royalblue;
}
Usage
<!-- any other template -->
<parent>
<child></child>
<child></child>
<child></child>
</parent>
Note: If you are creating the ChildComponent directly in the ParentComponent's own template you need to use #ViewChildren instead of #ContentChildren

ReactJS: Extend, Override CSS class

I want to
import a style class from a CSS file
dynamically extend or override the style
Apply the new style to react component.
Pseudocode of what I want to achieve
import 'common.css'
function MyComponent() {
if (somethingHappened) {
style = extendOverrideStyle(styleClassFromCSS)
} else {
style = styleClassFromCSS
}
return (
<SomeComponent className=style />
)
}
How can I do this?
Update:
Check my answer in the replies for how I finally did this.
Just create a new class in your CSS file to override the old CSS.
Then do it as
function MyComponent() {
return (
<div className={somethingHappened ? 'firstclass overwriteclass' :
'firstclass'}>
<SomeComponent />
</div>
)
}
You can't set style in Component Directly.
First you need to pass the style as prop to your Component and set style inside that component
<SomeComponent prop_class={your_style}/>
Inside SomeComponent
<div className={this.props.your_style}>
</div>
Answer (implementation somewhat similar to #Rohan's answer):
The main css class is the base style rules and .main.somethingHappened extends/overrides it. The main css class is applied to myComponent initially. When somethingHappened is true, apply both css classes main somethingHappened to myComponent.
mystyle.css
.main {
...
margin: 10px;
}
.main.somethingHappened {
margin: 5px
padding: 5px;
}
myComponent.js
(uses classnames library. check #Rohan's approach to avoid classnames library.)
import 'common.css'
import classnames from 'classnames'
function MyComponent() {
let didItHappen = ...
return (
<SomeComponent
className={classnames('main', {'somethingHappened': didItHappen})}
/>
)
}

Set the ClassName with state value in modular css in react

I am using modular css in react. I have to addClass to a <h2> how do I do that the problem is I am using modular css I know how to do it in normal css.Please Help.
Here is my component
import React, { Component } from 'react';
import style from '../css/MessageHeader.css';
class MessageHeader extends Component {
constructor(props) {
super(props);
this.state = {
name : "name"
}
}
render(){
return(
<div className={style.container}>
<div className={style.iconWrapper}>
<i className ="fas fa-angle-left"></i>
</div>
<div className={style.profileWrapper}>
<h2 className={this.state.name}>john appleseed </h2> //this is how I would in normal css
</div>
</div>
);
}
}
export default MessageHeader;
This would help you add the className this way. Variable is any variable in state with classname
<h2 className={`${this.state.variable}`}></h2>
While you can use a global class, I'm assuming you also want the h2 classes to be scoped to that component. Just like your other classes, you need to reference the style import - but use a dynamic key based on the state, like so:
<h2 className={style[this.state.name]}>john appleseed </h2>
Then your css module MessageHeader.css should contain a class for all names that you want to custom styling for.
Example:
.container {
// some styles here
}
.johnAppleseed {
color: yellow;
}
// You can also specify a name as a child of the container just like normal css,
// although it's not really necessary
.container .bobBobbert {
color: red;
}
Keep in mind that if your name in the state has spaces, that isn't going to work for a css class name, so you'll have to do some transformation (e.g. this.state.name.replace(...) to remove spaces and/or punctuation.

CSS Specificity with CSS Module

First, let me say I understand that I have a custom component "Card" that I use in one of my routes, "Home".
Card.js
import s from 'Card.css';
class Card {
...
render(){
return (<div className={cx(className, s.card)}>
{this.props.children}
</div>);
}
}
Card.css
.card {
display: block;
}
Home.js
<Card className={s.testCard}>
...
</Card>
Home.css
.testCard { display: none; }
A problem I faced here, is that the card remained visible even though I set the display to none, because of seemingly random CSS ordering in the browser. This ordering did not change even if Javascript was disabled.
To get .testCard to correctly load after .card, I used "composes:":
Home.css
.testCard {
composes: card from 'components/Card.css';
display: none;
}
Apparently this causes css-loader to recognize that .testCard should load after .card. Except, if I disable Javascript, the page reverts back to the old behavior: the .card is loaded after .testCard and it becomes visible again.
What is the recommended way to get our page to prioritize .testCard over card that behaves consistently with or without Javascript enabled?
As I'm using CSS modules, charlietfl solution wouldn't really work as is. .card is automatically mangled to a name like .Card-card-l2hne by the css-loader, so referencing it from a different component wouldn't work. If I import it into the CSS file of Home.css, that also doesn't work, because it creates a new class with a name like .Home-card-lnfq, instead of referring to .Card-card-l2hna.
I don't really think there's a great way to fix this so I've resorted to being more specific using a parent class instead.
As an example, Home.js:
import s from 'Home.css';
import Card from './components/Card';
class Home {
...
render(){
return (
<div className={s.root}>
<Card className={s.testCard}>Hi</Card>
</div>
);
}
}
Home.css
.root {
margin: 10px;
}
.root > .testCard {
display: none;
}
This way, we don't need to know what class names component Card is using internally, especially since in cases like CSS Modules or styled components, the class name is some unique generated name.
I don't think I would have come to this solution if it wasn't for charlieftl's solution, so thank you very much for that.
Just make the testCard rule more specific by combining classes
.card {display: block;}
.card.testCard { display: none; }

Resources