CSS Specificity with CSS Module
Asked Answered
V

4

6

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?

Valentin answered 4/7, 2018 at 19:53 Comment(2)
What is s.testCard? What is cx(className, s.card)? Does the browser's console have any errors? What does the outputted HTML for a card look like?Balinese
Sorry I should have clarified, I use import cx from 'classnames'; It's basically a utility function that concatenates class names.Valentin
V
7

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.

Valentin answered 4/7, 2018 at 21:46 Comment(0)
S
3

Just make the testCard rule more specific by combining classes

.card {display: block;}

.card.testCard { display: none; }
Suppurate answered 4/7, 2018 at 20:9 Comment(2)
That actually works quite well, and is consistent with/without javascript. Thank you for that. However, is there an alternative where I don't need to know the class name used by the imported component? Especially because I use css-modules and to refer to .card, I would need to import the Card.css file.Valentin
I posted a modified version of your solution that can also work with CSS Modules and styled components. Thank you for your answer.Valentin
Z
0

Css specificity is calculated based on the selectors used. Elements selectors have a value of 1 Class selectors have a value of 10 ID selectors have a value of 100 Inline css have a value of 1000.

So if you specify more using these rules above you can achieve which rule is applied.

For example. in the case of making the selector

.card vs .card.testCard

.root > .testCard

(using examples posted here)

The calculated specificity values are:

  • .card = 10
  • .card.testCard = 100
  • .root > .testCard = 100

so using more specified selectors by adding either class, id or elements increase the specificity points and apply the rule with the highest specificity value.

Caveat: if the selectors that point to the same element have the same value then the latest rules win. so in the example here

<style>
.card.testCard { color: red; }
.root > .testCard { color: green; }
</style>

The style chosen here will be color of green even though both have the same specificty because latest rules win.

Zlatoust answered 15/5, 2023 at 15:39 Comment(0)
T
0

You can use @layer from css.

Define a layer for your base UI components in your global.css

@layer components;

Then:

Card.css

@layer components {
  .card {
    display: block;
  }
}

Now your testCard class in Home.css will work as you expect. As your testCard is in an anonymous layer (it is not in any defined @layer), it will have more precedence. You can check it in this example.

Tress answered 6/3 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.