selectors – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Tue, 23 Jul 2024 13:26:39 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 selectors – CSS-Tricks https://css-tricks.com 32 32 45537868 CSS Selectors https://css-tricks.com/css-selectors/ https://css-tricks.com/css-selectors/#comments Mon, 15 Jul 2024 16:13:15 +0000 https://css-tricks.com/?p=378745 A complete guide covering all of the various methods we have to select elements in CSS and how to use them for applying styles.


CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>

Overview

CSS is really good at many things, but it’s really, really good at two specific things: selecting elements and styling them. That’s the raison d’être for CSS and why it’s a core web language. In this guide, we will cover the different ways to select elements — because the styles we write are pretty much useless without the ability to select which elements to apply them to.

The source of truth for CSS selectors is documented in the Selectors Module Level 4 specification. With one exception (which we’ll get to), all of the selectors covered here are well-covered by browsers across the board, and most certainly by all modern browsers.

In addition to selectors, this guide also looks at CSS combinators. If selectors identify what we are selecting, you might think of combinators as how the styles are applied. Combinators are like additional instructions we give CSS to select a very particular element on the page, not totally unlike the way we can use filters in search engines to find the exact result we want.

Quick reference

Common Selectors

/* Universal */
* {
  box-sizing: border-box;
}

/* Type or Tag */
p {
  margin-block: 1.5rem;
}

/* Classname */
.class {
  text-decoration: underline;
}

/* ID */
#id {
  font-family: monospace;
}

/* Relational */
li:has(a) {
  display: flex;
}

Common Combinators

/* Descendant */
header h1 {
  /* Selects all Heading 1 elements in a Header element. */
}

/* Child */
header > h1 {
  /* Selects all Heading 1 elements that are children of Header elements. */
}

/* General sibling */
h1 ~ p {
  /* Selects a Paragraph as long as it follows a Heading 1. */
}

/* Adjacent sibling */
h1 + p {
  /* Selects a Paragraph if it immediately follows a Heading 1 */
}

/* Chained */
h1, p { 
  /* Selects both elements. */
}

General Selectors

When we talk about CSS selectors, we’re talking about the first part of a CSS ruleset:

/* CSS Ruleset */
selector {
  /* Style rule */
  property: value;
}

See that selector? That can be as simple as the HTML tag we want to select. For example, let’s select all <article> elements on a given page.

/* Select all <article> elements... */
article {
  /* ... and apply this background-color on them */
  background-color: hsl(25 100% 50%);
}

That’s the general process of selecting elements to apply styles to them. Selecting an element by its HTML tag is merely one selector type of several. Let’s see what those are in the following section.

Element selectors

Element selectors are exactly the type of selector we looked at in that last example: Select the element’s HTML tag and start styling!

That’s great and all, but consider this: Do you actually want to select all of the <article> elements on the page? That’s what we’re doing when we select an element by its tag — any and all HTML elements matching that tag get the styles. The following demo selects all <article> elements on the page, then applies a white (#fff) background to them. Notice how all three articles get the white background even though we only wrote one selector.

I’ve tried to make it so the relevant for code for this and other demos in this guide is provided at the top of the CSS tab. Anything in a @layer can be ignored. And if you’re new to @layer, you can learn all about it in our CSS Cascade Layers guide.

But maybe what we actually want is for the first element to have a different background — maybe it’s a featured piece of content and we need to make it stand out from the other articles. That requires us to be more specific in the type of selector we use to apply the styles.

Let’s turn our attention to other selector types that allow us to be more specific about what we’re selecting.

ID selectors

ID selectors are one way we can select one element without selecting another of the same element type. Let’s say we were to update the HTML in our <article> example so that the first article is “tagged” with an ID:

<article id="featured">
  <!-- Article 1 -->
</article>

<article>
  <!-- Article 2 -->
</article>

<article>
  <!-- Article 3 -->
</article>

Now we can use that ID to differentiate that first article from the others and apply styles specifically to it. We prepend a hashtag character (#) to the ID name when writing our CSS selector to properly select it.

/* Selects all <article> elements */
article {
  background: #fff;
}

/* Selects any element with id="featured" */
#featured {
  background: hsl(35 100% 90%);
  border-color: hsl(35 100% 50%);
}

There we go, that makes the first article pop a little more than the others!

Before you go running out and adding IDs all over your HTML, be aware that IDs are considered a heavy-handed approach to selecting. IDs are so specific, that it is tough to override them with other styles in your CSS. IDs have so much specificity power than any selector trying to override it needs at least an ID as well. Once you’ve reached near the top of the ladder of this specificity war, it tends to lead to using !important rules and such that are in turn nearly impossible to override.

Let’s rearrange our CSS from that last example to see that in action:

/* Selects any element with id="featured" */
#featured {
  background: hsl(35 100% 90%);
  border-color: hsl(35 100% 50%);
}

/* Selects all <article> elements */
article {
  background: #fff;
}

The ID selector now comes before the element selector. According to how the CSS Cascade determines styles, you might expect that the article elements all get a white background since that ruleset comes after the ID selector ruleset. But that’s not what happens.

So, you see how IDs might be a little too “specific” when it comes to selecting elements because it affects the order in which the CSS Cascade applies styles and that makes styles more difficult to manage and maintain.

The other reason to avoid IDs as selectors? We’re technically only allowed to use an ID once on a page, per ID. In other words, we can have one element with #featured but not two. That severely limits what we’re able to style if we need to extend those styles to other elements — not even getting into the difficulty of overriding the ID’s styles.

A better use case for IDs is for selecting items in JavaScript — not only does that prevent the sort of style conflict we saw above, but it helps maintain a separation of concerns between what we select in CSS for styling versus what we select in JavaScript for interaction.

Another thing about ID selectors: The ID establishes what we call an “anchor” which is a fancy term for saying we can link directly to an element on the page. For example, if we have an article with an ID assigned to it:

<article id="featured">...</article>

…then we can create a link to it like this:

<a href="featured">Jump to article below ⬇️</a>

<!-- muuuuuuch further down the page. -->

<article id="featured">...</article>

Clicking the link will navigate you to the element as though the link is anchored to that element. Try doing exactly that in the following demo:

This little HTML goodie opens up some pretty darn interesting possibilities when we sprinkle in a little CSS. Here are a few articles to explore those possibilities.

Class selectors

Class selectors might be the most commonly used type of CSS selector you will see around the web. Classes are ideal because they are slightly more specific than element selectors but without the heavy-handedness of IDs. You can read a deep explanation of how the CSS Cascade determines specificity, but the following is an abbreviated illustration focusing specifically (get it?!) on the selector types we’ve looked at so far.

Showing element, class, and ID selectors in a horizontal row from least specific to most specific.

That’s what makes class selectors so popular — they’re only slightly more specific than elements, but keep specificity low enough to be manageable if we need to override the styles in one ruleset with styles in another.

The only difference when writing a class is that we prepend a period (.) in front of the class name instead of the hashtag (#).

/* Selects all <article> elements */
article {
  background: #fff;
}

/* Selects any element with class="featured" */
.featured {
  background: hsl(35 100% 90%);
  border-color: hsl(35 100% 50%);
}

Here’s how our <article> example shapes up when we swap out #featured with .featured.

Same result, better specificity. And, yes, we can absolutely combine different selector types on the same element:

<article id="someID" class="featured">...</article>

Do you see all of the possibilities we have to select an <article>? We can select it by:

  • Its element type (article)
  • Its ID (#someID)
  • Its class (.featured)

The following articles will give you some clever ideas for using class selectors in CSS.

But we have even more ways to select elements like this, so let’s continue.

Attribute selectors

ID and class selectors technically fall into this attribute selectors category. We call them “attributes” because they are present in the HTML and give more context about the element. All of the following are attributes in HTML:

<!-- ID, Class, Data Attribute -->
<article id="#id" class=".class" data-attribute="attribute">
</article>

<!-- href, Title, Target -->
<a href="https://css-tricks.com" title="Visit CSS-Tricks" target="_blank"></a>

<!-- src, Width, Height, Loading -->
<img src="star.svg" width="250" height="250" loading="laxy" >

<!-- Type, ID, Name, Checked -->
<input type="checkbox" id="consent" name="consent" checked />

<!-- Class, Role, Aria Label -->
<div class="buttons" role="tablist" aria-label="Tab Buttons">

Anything with an equals sign (=) followed by a value in that example code is an attribute. So, we can technically style all links with an href attribute equal to https://css-tricks.com:

a[href="https://css-tricks.com"] {
  color: orangered;
}

Notice the syntax? We’re using square brackets ([]) to select an attribute instead of a period or hashtag as we do with classes and IDs, respectively.

The equals sign used in attributes suggests that there’s more we can do to select elements besides matching something that’s exactly equal to the value. That is indeed the case. For example, we can make sure that the matching selector is capitalized or not. A good use for that could be selecting elements with the href attribute as long as they do not contain uppercase letters:

/* Case sensitive */
a[href*='css-tricks' s] {}

The s in there tells CSS that we only want to select a link with an href attribute that does not contain uppercase letters.

<!-- 👎 No match -->
<a href="https://CSS-Tricks.com">...</a>

<!-- 👍 Match! -->
<a href="https://css-tricks.com">...</a>

If case sensitivity isn’t a big deal, we can tell CSS that as well:

/* Case insensitive */
a[href*='css-tricks' i] {}

Now, either one of the link examples will match regardless of there being upper- or lowercase letters in the href attribute.

<!-- 👍 I match! -->
<a href="https://CSS-Tricks.com">...</a>

<!-- 👍 I match too! -->
<a href="https://css-tricks.com">...</a>

There are many, many different types of HTML attributes. Be sure to check out our Data Attributes guide for a complete rundown of not only [data-attribute] but how they relate to other attributes and how to style them with CSS.

Universal selector

CSS-Tricks has a special relationship with the Universal Selector — it’s our logo!

That’s right, the asterisk symbol (*) is a selector all unto itself whose purpose is to select all the things. Quite literally, we can select everything on a page — every single element — with that one little asterisk. Note I said every single element, so this won’t pick up things like IDs, classes, or even pseudo-elements. It’s the element selector for selecting all elements.

/* Select ALL THE THINGS! 💥 */
* {
  /* Styles */
}

Or, we can use it with another selector type to select everything inside a specific element.

/* Select everything in an <article> */
article * {
  /* Styles */
}

That is a handy way to select everything in an <article>, even in the future if you decide to add other elements inside that element to the HTML. The times you’ll see the Universal Selector used most is to set border-sizing on all elements across the board, including all elements and pseudo-elements.

*,
*::before,
*::after {
  box-sizing: border-box;
}

There’s a good reason this snippet of CSS winds up in so many stylesheets, which you can read all about in the following articles.

Sometimes the Universal Selector is implied. For example, when using a pseudo selector at the start of a new selector. These are selecting exactly the same:

*:has(article) { }
:has(article)  { }

Pseudo-selectors

Pseudo-selectors are for selecting pseudo-elements, just as element selectors are for selecting elements. And a pseudo-element is just like an element, but it doesn’t actually show up in the HTML. If pseudo-elements are new to you, we have a quick explainer you can reference.

Every element has a ::before and ::after pseudo-element attached to it even though we can’t see it in the HTML.

<div class="container">
  <!-- ::before psuedo-element here -->
  <div>Item</div>
  <div>Item</div>
  <div>Item</div>
  <!-- ::after psuedo-element here -->
</div>

These are super handy because they’re additional ways we can hook into an element an apply additional styles without adding more markup to the HTML. Keep things as clean as possible, right?!

We know that ::before and ::after are pseudo-elements because they are preceded by a pair of colons (::). That’s how we select them, too!

.container::before {
  /* Styles */
}

The ::before and ::after pseudo-elements can also be written with a single colon — i.e., :before and :after — but it’s still more common to see a double colon because it helps distinguish pseudo-elements from pseudo-classes.

But there’s a catch when using pseudo-selectors: they require the content property. That’s because pseudos aren’t “real” elements but ones that do not exist as far as HTML is concerned. That means they need content that can be displayed… even if it’s empty content:

.container::before {
  content: "";
}

Of course, if we were to supply words in the content property, those would be displayed on the page.


Complex selectors

Complex selectors may need a little marketing help because “complex” is an awfully scary term to come across when you’re in the beginning stages of learning this stuff. While selectors can indeed become complex and messy, the general idea is super straightforward: we can combine multiple selectors in the same ruleset.

Let’s look at three different routes we have for writing these “not-so-complex” complex selectors.

Listing selectors

First off, it’s possible to combine selectors so that they share the same set of styles. All we do is separate each selector with a comma.

.selector-1,
.selector-2,
.selector-3 {
  /* We share these styles! 🤗 */
}

You’ll see this often when styling headings — which tend to share the same general styling except, perhaps, for font-size.

h1,
h2,
h3,
h4,
h5,
h6 {
  color: hsl(25 80% 15%);
  font-family: "Poppins", system-ui;
}

Adding a line break between selectors can make things more legible. You can probably imagine how complex and messy this might get. Here’s one, for example:

section h1, section h2, section h3, section h4, section h5, section h6, 
article h1, article h2, article h3, article h4, article h5, article h6, 
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, 
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
  color: #BADA55;
}

Ummmm, okay. No one wants this in their stylesheet. It’s tough to tell what exactly is being selected, right?

The good news is that we have modern ways of combining these selectors more efficiently, such as the :is() pseudo selector. In this example, notice that we’re technically selecting all of the same elements. If we were to take out the four section, article, aside, and nav element selectors and left the descendants in place, we’d have this:

h1, h2, h3, h4, h5, h6, 
h1, h2, h3, h4, h5, h6,
h1, h2, h3, h4, h5, h6, 
h1, h2, h3, h4, h5, h6, {
  color: #BADA55;
}

The only difference is which element those headings are scoped to. This is where :is() comes in handy because we can match those four elements like this:

:is(section, article, aside, nav) {
  color: #BADA55;
}

That will apply color to the elements themselves, but what we want is to apply it to the headings. Instead of listing those out for each heading, we can reach for :is() again to select them in one fell swoop:

/* Matches any of the following headings scoped to any of the following elements.  */
:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
  color: #BADA55;
}

While we’re talking about :is() it’s worth noting that we have the :where() pseudo selector as well and that it does the exact same thing as :is(). The difference? The specificity of :is() will equal the specificity of the most specific element in the list. Meanwhile, :where() maintains zero specificity. So, if you want a complex selector like this that’s easier to override, go with :where() instead.

Nesting selectors

That last example showing how :is() can be used to write more efficient complex selectors is good, but we can do even better now that CSS nesting is a widely supported feature.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
120117No12017.2

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712717.2

CSS nesting allows us to better see the relationship between selectors. You know how we can clearly see the relationship between elements in HTML when we indent descendant elements?

<!-- Parent -->
<article>
  <!-- Child -->
  <img src="" alt="...">
  <!-- Child -->
  <div class="article-content">
    <!-- Grandchild -->
    <h2>Title</h2>
    <!-- Grandchild -->
    <p>Article content.</p>
  </div>
</article>

CSS nesting is a similar way that we can format CSS rulesets. We start with a parent ruleset and then embed descendant rulesets inside. So, if we were to select the <h2> element in that last HTML example, we might write a descendant selector like this:

article h2 { /* Styles */ }

With nesting:

article  {
  /* Article styles */

  h2 { /* Heading 2 styles */ }
}

You probably noticed that we can technically go one level deeper since the heading is contained in another .article-content element:

article  {
  /* Article styles */

  .article-content {
    /* Container styles */

    h2 { /* Heading 2 styles */ }
  }
}

So, all said and done, selecting the heading with nesting is the equivalent of writing a descendant selector in a flat structure:

article .article-content h2 { /* Heading 2 styles */ }

You might be wondering how the heck it’s possible to write a chained selector in a nesting format. I mean, we could easily nest a chained selector inside another selector:

article  {
  /* Article styles */

  h2.article-content {
    /* Heading 2 styles */
  }
}

But it’s not like we can re-declare the article element selector as a nested selector:

article  {
  /* Article styles */

  /* Nope! 👎 */
  article.article-element {
    /* Container styles */  

    /* Nope! 👎 */
    h2.article-content {
      /* Heading 2 styles */
    }
  }
}

Even if we could do that, it sort of defeats the purpose of a neatly organized nest that shows the relationships between selectors. Instead, we can use the ampersand (&) symbol to represent the selector that we’re nesting into. We call this the nesting selector.

article  {

  &.article-content {
    /* Equates to: article.article-content */
  }
}

Compounding selectors

We’ve talked quite a bit about the Cascade and how it determines which styles to apply to matching selectors using a specificity score. We saw earlier how an element selector is less specific than a class selector, which is less specific than an ID selector, and so on.

article { /* Specificity: 0, 0, 1 */ }
.featured { /* Specificity: 0, 1, 0 */ }
#featured { /* Specificity: 1, 0, 0 */ }

Well, we can increase specificity by chaining — or “compounding” — selectors together. This way, we give our selector a higher priority when it comes to evaluating two or more matching styles. Again, overriding ID selectors is incredibly difficult so we’ll work with the element and class selectors to illustrate chained selectors.

We can chain our article element selector with our .featured class selector to generate a higher specificity score.

article { /* Specificity: 0, 0, 1 */ }
.featured { /* Specificity: 0, 1, 0 */ }

articie.featured { /* Specificity: 0, 1, 1 */ }

This new compound selector is more specific (and powerful!) than the other two individual selectors. Notice in the following demo how the compound selector comes before the two individual selectors in the CSS yet still beats them when the Cascade evaluates their specificity scores.

Interestingly, we can use “fake” classes in chained selectors as a strategy for managing specificity. Take this real-life example:

.wp-block-theme-button .button:not(.specificity):not(.extra-specificity) { }

Whoa, right? There’s a lot going on there. But the idea is this: the .specificity and .extra-specificity class selectors are only there to bump up the specificity of the .wp-block-theme .button descendant selector. Let’s compare the specificity score with and without those artificial classes (that are :not() included in the match).

.wp-block-theme-button .button {
  /* Specificity: 0, 2, 0 */
}

.wp-block-theme-button .button:not(.specificity) {
  /* Specificity: 0, 3, 0 */
}

.wp-block-theme-button  .button:not(.specificity):not(.extra-specificity {
  /* Specificity: 0, 4, 0 */
}

Interesting! I’m not sure if I would use this in my own CSS but it is a less heavy-handed approach than resorting to the !important keyword, which is just as tough to override as an ID selector.


Combinators

If selectors are “what” we select in CSS, then you might think of CSS combinators as “how” we select them. they’re used to write selectors that combine other selectors in order to target elements. Inception!

The name “combinator” is excellent because it accurately conveys the many different ways we’re able to combine selectors. Why would we need to combine selectors? As we discussed earlier with Chained Selectors, there are two common situations where we’d want to do that:

  • When we want to increase the specificity of what is selected.
  • When we want to select an element based on a condition.

Let’s go over the many types of combinators that are available in CSS to account for those two situations in addition to chained selectors.

Descendant combinator

We call it a “descendant” combinator because we use it to select elements inside other elements, sorta like this:

/* Selects all elements in .parent with .child class */
.parent .child {}

…which would select all of the elements with the .child class in the following HTML example:

<div class="parent">
  <div class="child"></div>
  <div class="child"></div>

  <div class="friend"></div>

  <div class="child"></div>
  <div class="child"></div>
</div>

See that element with the .friend classname? That’s the only element inside of the .parent element that is not selected with the .parent .child {} descendant combinator since it does not match .child even though it is also a descendant of the .parent element.

Child combinator

A child combinator is really just an offshoot of the descendant combinator, only it is more specific than the descendant combinator because it only selects direct children of an element, rather than any descendant.

Let’s revise the last HTML example we looked at by introducing a descendant element that goes deeper into the family tree, like a .grandchild:

<div class="parent">
  <div class="child"></div>
  <div class="child">
    <div class="grandchild"></div>
  </div>
  <div class="child"></div>
  <div class="child"></div>
</div>

So, what we have is a .parent to four .child elements, one of which contains a .grandchild element inside of it.

Maybe we want to select the .child element without inadvertently selecting the second .child element’s .grandchild. That’s what a child combinator can do. All of the following child combinators would accomplish the same thing:

/* Select only the "direct" children of .parent */
.parent > .child {}
.parent > div {}
.parent > * {}

See how we’re combining different selector types to make a selection? We’re combinating, dangit! We’re just doing it in slightly different ways based on the type of child selector we’re combining.

/* Select only the "direct" children of .parent */
.parent > #child { /* direct child with #child ID */
.parent > .child { /* direct child with .child class */ }
.parent > div { /* direct child div elements */ }
.parent > * { /* all direct child elements */ }

It’s pretty darn neat that we not only have a way to select only the direct children of an element, but be more or less specific about it based on the type of selector. For example, the ID selector is more specific than the class selector, which is more specific than the element selector, and so on.

General sibling combinator

If two elements share the same parent element, that makes them siblings like brother and sister. We saw an example of this in passing when discussing the descendant combinator. Let’s revise the class names from that example to make the sibling relationship a little clearer:

<div class="parent">
  <div class="brother"></div>
  <div class="sister"></div>
</div>

This is how we can select the .sister element as long as it is preceded by a sibling with class .brother.

/* Select .sister only if follows .brother */
.brother ~ .sister { }

The Tilda symbol (~) is what tells us this is a sibling combinator.

It doesn’t matter if a .sister comes immediately after a .brother or not — as long as a .sister comes after a brother and they share the same parent element, it will be selected. Let’s see a more complicated HTML example:

<main class="parent">
  
  <!-- .sister immediately after .brother -->
  <div class="brother"></div>
  <div class="sister"></div>

  <!-- .sister immediately after .brother -->
  <div class="brother"></div>
  <div class="sister"></div>
  <!-- .sister immediately after .sister -->
  <div class="sister"></div>

  <!-- .cousin immediately after .brother -->
  <div class="brother"></div>
  <div class="cousin">
    <!-- .sister contained in a .cousin -->
    <div class="sister"></div>
  </div>
</main>

The sibling combinator we wrote only selects the first three .sister elements because they are the only ones that come after a .brother element and share the same parent — even in the case of the third .sister which comes after another sister! The fourth .sister is contained inside of a .cousin, which prevents it from matching the selector.

Let’s see this in context. So, we can select all of the elements with an element selector since each element in the HTML is a div:

From there, we can select just the brothers with a class selector to give them a different background color:

We can also use a class selector to set a different background color on all of the elements with a .sister class:

And, finally, we can use a general sibling combinator to select only sisters that are directly after a brother.

Did you notice how the last .sister element’s background color remained green while the others became purple? That’s because it’s the only .sister in the bunch that does not share the same .parent as a .brother element.

Adjacent combinator

Believe it or not, we can get even more specific about what elements we select with an adjacent combinator. The general sibling selector we just looked at will select all of the .sister elements on the page as long as it shares the same parent as .brother and comes after the .brother.

What makes an adjacent combinator different is that it selects any element immediately following another. Remember how the last .sister didn’t match because it is contained in a different parent element (i.e., .cousin)? Well, we can indeed select it by itself using an adjacent combinator:

/* Select .sister only if directly follows .brother */
.brother + .sister { }

Notice what happens when we add that to our last example:

The first two .sister elements changed color! That’s because they are the only sisters that come immediately after a .brother. The third .sister comes immediately after another .sister and the fourth one is contained in a .cousin which prevents both of them from matching the selection.


Learn more about CSS selectors


References

The vast majority of what you’re reading here is information pulled from articles we’ve published on CSS-Tricks and those are linked up throughout the guide. In addition to those articles, the following resources were super helpful for putting this guide together.


CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-selectors/feed/ 2 378745
“If” CSS Gets Inline Conditionals https://css-tricks.com/if-css-gets-inline-conditionals/ https://css-tricks.com/if-css-gets-inline-conditionals/#comments Tue, 09 Jul 2024 15:18:11 +0000 https://css-tricks.com/?p=379002 A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that …


“If” CSS Gets Inline Conditionals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that caught my attention:

Lea is the one who opened the GitHub issue leading to the discussion and in a stroke of coincidence — or serendipity, perhaps — the resolution came in on her birthday. That had to be quite a whirlwind of a day! What did you get for your birthday? “Oh, you know, just an accepted proposal to the CSS spec.” Wild, just wild.

The accepted proposal is a green light for the CSSWG to work on the idea with the intent of circulating a draft specification for further input and considerations en route to, hopefully, become a recommended CSS feature. So, it’s gonna be a hot minute before any of this is baked, that is, if it gets fully baked.

But the idea of applying styles based on a conditional requirement is super exciting and worth an early look at the idea. I scribbled some notes about it on my blog the same day Lea posted to X and thought I’d distill those here for posterity while rounding up more details that have come up since then.

This isn’t a new idea

Many proposals are born from previously rejected proposals and if() is no different. And, indeed, we have gained several CSS features in recent days that allow for conditional styling — :has() and Container Style Queries being two of the more obvious examples. Lea even cites a 2018 ticket that looks and reads a lot like the accepted proposal.

The difference?

Style queries had already shipped, and we could simply reference the same syntax for conditions (plus media() and supports() from Tab’s @when proposal) whereas in the 2018 proposal how conditions would work was largely undefined.

Lea Verou, “Inline conditionals in CSS?”

I like how Lea points out that CSS goes on to describe how CSS has always been a conditional language:

Folks… CSS had conditionals from the very beginning. Every selector is essentially a conditional!

Lea Verou, “Inline conditionals in CSS?”

True! The Cascade is the vehicle for evaluating selectors and matching them to HTML elements on a page. What if() brings to the table is a way to write inline conditions with selectors.

Syntax

It boils down to this:

<if()> = if( <container-query>, [<declaration-value>]{1, 2} )

…where:

  • Values can be nested to produce multiple branches.
  • If a third argument is not provided, it becomes equivalent to an empty token stream.

All of this is conceptual at the moment and nothing is set in stone. We’re likely to see things change as the CSSWG works on the feature. But as it currently stands, the idea seems to revolve around specifying a condition, and setting one of two declared styles — one as the “default” style, and one as the “updated” style when a match occurs.

.element {
  background-color:
    /* If the style declares the following custom property: */
    if(style(--variant: success),
      var(--color-green-50), /* Matched condition */
      var(--color-blue-50);  /* Default style */
    );
}

In this case, we’re looking for a style() condition where a CSS variable called --variant is declared and is set to a value of success, and:

  • …if --variant is set to success, we set the value of success to --color-green-50 which is a variable mapped to some greenish color value.
  • …if --variant is not set to success, we set the value of the success to --color-blue-50 which is a variable mapped to some bluish color value.

The default style would be optional, so I think it can be omitted in some cases for slightly better legibility:

.element {
  background-color:
    /* If the style declares the following custom property: */
    if(style(--variant: success),
      var(--color-green-50) /* Matched condition */
    );
}

The syntax definition up top mentions that we could support a third argument in addition to the matched condition and default style that allows us to nest conditions within conditions:

background-color: if(
  style(--variant: success), var(--color-success-60), 
    if(style(--variant: warning), var(--color-warning-60), 
      if(style(--variant: danger), var(--color-danger-60), 
        if(style(--variant: primary), var(--color-primary)
      )
    ),
  )
);

Oomph, looks like some wild inception is happening in there! Lea goes on to suggest a syntax that would result in a much flatter structure:

<if()> = if( 
  [ <container-query>, [<declaration-value>]{2}  ]#{0, },
  <container-query>, [<declaration-value>]{1, 2} 
)

In other words, nested conditions are much more flat as they can be declared outside of the initial condition. Same concept as before, but a different syntax:

background-color: if(
  style(--variant: success), var(--color-success-60), 
  style(--variant: warning), var(--color-warning-60),
  style(--variant: danger), var(--color-danger-60), 
  style(--variant: primary), var(--color-primary)
);

So, rather than one if() statement inside another if() statement, we can lump all of the possible matching conditions into a single statement.

We’re attempting to match an if() condition by querying an element’s styles. There is no corresponding size() function for querying dimensions — container queries implicitly assume size:

.element {
  background: var(--color-primary);

  /* Condition */
  @container parent (width >= 60ch) {
    /* Applied styles */
    background: var(--color-success-60);
  }
}

And container queries become style queries when we call the style() function instead:

.element {
  background: orangered;

  /* Condition */
  @container parent style(--variant: success) {
    /* Applied styles */
    background: dodgerblue;
  }
}

Style queries make a lot more sense to me when they’re viewed in the context of if(). Without if(), it’s easy to question the general usefulness of style queries. But in this light, it’s clear that style queries are part of a much bigger picture that goes beyond container queries alone.

There’s still plenty of things to suss out with the if() syntax. For example, Tab Atkins describes a possible scenario that could lead to confusion between what is the matched condition and default style parameters. So, who knows how this all shakes out in the end!

Conditions supporting other conditions

As we’ve already noted, if() is far from the only type of conditional check already provided in CSS. What would it look like to write an inline conditional statement that checks for other conditions, such as @supports and @media?

In code:

background-color: if(
  supports( /* etc. */ ),
  @media( /* etc. */ )
);

The challenge would be container supporting size queries. As mentioned earlier, there is no explicit size() function; instead it’s more like an anonymous function.

@andruud has a succinctly describes the challenge in the GitHub discussion:

I don’t see why we couldn’t do supports() and media(), but size queries would cause cycles with layout that are hard/impossible to even detect. (That’s why we needed the restrictions we currently have for size CQs in the first place.

“Can’t we already do this with [X] approach?”

When we were looking at the syntax earlier, you may have noticed that if() is just as much about custom properties as it is about conditionals. Several workarounds have emerged over the years to mimic what we’d gain if() we could set a custom property value conditionally, including:

  • Using custom properties as a Boolean to apply styles or not depending on whether it is equal to 0 or 1. (Ana has a wonderful article on this.)
  • Using a placeholder custom property with an empty value that’s set when another custom property is set, i.e. “the custom property toggle trick” as Chris describes it.
  • Container Style Queries! The problem (besides lack of implementation) is that containers only apply styles to their descendants, i.e., they cannot apply styles to themselves when they meet a certain condition, only its contents.

Lea gets deep into this in a separate post titled “Inline conditional statements in CSS, now?” that includes a table that outlines and compares approaches, which I’ll simply paste below. The explanations are full of complex CSS nerdery but are extremely helpful for understanding the need for if() and how it compares to the clever “hacks” we’ve used for years.

MethodInput valuesOutput valuesProsCons
Binary Linear InterpolationNumbersQuantitativeCan be used as part of a valueLimited output range
Togglesvar(--alias) (actual values are too weird to expose raw)AnyCan be used in part of a valueWeird values that need to be aliased
Paused animationsNumbersAnyNormal, decoupled declarationsTakes over animation property

Cascade weirdness
Type GrindingKeywordsAny value supported by the syntax descriptorHigh flexibility for exposed APIGood encapsulationMust insert CSS into light DOM

Tedious code (though can be automated with build tools)

No Firefox support (though that’s changing)
Variable animation nameKeywordsAnyNormal, decoupled declarationsImpractical outside of Shadow DOM due to name clashes

Takes over animation property

Cascade weirdness

Happy birthday, Lea!

Belated by two weeks, but thanks for sharing the spoils of your big day with us! 🎂

References

To Shared LinkPermalink on CSS-Tricks


“If” CSS Gets Inline Conditionals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/if-css-gets-inline-conditionals/feed/ 9 379002
Case-Sensitive Selectors https://css-tricks.com/snippets/css/case-sensitive-selectors/ Wed, 22 May 2024 18:24:49 +0000 https://css-tricks.com/?page_id=378046 /* Case sensitive */ a[href*='css-tricks' s] {} /* Case insensitive */ a[href*='css-tricks' i] {}

Adding an s makes the selector case-sensitive and i makes it case-insensitive.

Source


Case-Sensitive Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
/* Case sensitive */ a[href*='css-tricks' s] {} /* Case insensitive */ a[href*='css-tricks' i] {}

Adding an s makes the selector case-sensitive and i makes it case-insensitive.


Case-Sensitive Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
378046
@supports selector() https://css-tricks.com/supports-selector/ https://css-tricks.com/supports-selector/#comments Tue, 19 Oct 2021 21:12:18 +0000 https://css-tricks.com/?p=354115 I didn’t realize the support for @supports determining selector support was so good! I usually think of @supports as a way to test for property: value pair support. But with the selector() function, we can test for selector support …


@supports selector() originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I didn’t realize the support for @supports determining selector support was so good! I usually think of @supports as a way to test for property: value pair support. But with the selector() function, we can test for selector support as well. It looks like this:

@supports selector(:nth-child(1 of .foo)) {

}

You just drop the selector right between the parens and that’s what it tests for.

That selector above is a pretty good test, actually. It’s a “selector list argument” that works for the :nth-child ‘n’ friends selectors. As I write, it’s only supported in Safari.

So let’s say your ideal situation is that the browser supports this selector. Here’s an example. You know that with <ol> and <ul> the only valid child element is <li>. But also say this list needs separators, so you (and I’m not saying this is a great idea) did this kind of thing:

<ul>
  <li class="list-item">List item</li>
  <li class="list-item">List item</li>
  <li class="separator"></li>
  /* ... */
</ul>

Then you also want to zebra-stripe the list. And, if you want zebra striping, you need to select every other .list-item, ignoring the .separator. So…

li:nth-child(odd of .list-item) {
  background: lightgoldenrodyellow;
}

But only Safari supports that… so you can do:

@supports selector(:nth-child(1 of .foo)) {
  li:nth-child(odd of .list-item) {
    background: lightgoldenrodyellow;
  }
}

If you didn’t care what the fallback was, you wouldn’t even have to bother with the @supports at all. But say you do care about the fallback. Perhaps in the supported situation, the zebra striping does the heavy lifting of the UX you are shooting for, so all you need for the seperator is a bit of space. But for non-supporting browsers, you’ll need something beefier because you don’t have the zebra striping.

So now you can style both situations:

@supports selector(:nth-child(1 of .foo)) {
  li {
    padding: 0.25em;
  }
  li:nth-child(odd of .list-item) {
    background: lightgoldenrodyellow;
  }
  li.separator {
    list-style: none;
    margin: 0.25em 0;
  }
}
@supports not selector(:nth-child(1 of .foo)) {
  li.separator {
    height: 1px;
    list-style: none;
    border-top: 1px dashed purple;
    margin: 0.25em 0;
  }
}

If we get the @when syntax, then we can write it a little cleaner:

/* Maybe? */
@when supports(selector(:nth-child(1 of .foo))) {

} @else {

}

Anyway. The end result is…

Supported
Not Supported

There is a JavaScript API for testing support as well. I wasn’t sure if this would actually work, but it appears to! This fails in Chrome and passes in Safari as I write:

CSS.supports("selector(:nth-child(1 of .foo))")

While I was putting this together, I was thinking… hmmmmmmm — what CSS selectors are out there that have weird cross-browser support? It’s really not that many. And even of those that do have weird cross-browser support, thinking of the number of use-cases where you care to actually wrap it in an @supports (rather than just let it fail) is fairly few.

The ::marker pseudo-element would have been a great one, but it’s pretty well supported now. I was thinking the case-insensitive attribute selector, like [href$="pdf" i], would have been a good one, but nope, also well supported. Same deal with the comma-separated :not(a, .b, [c]). Maybe something like :fullscreen / :-webkit-full-screen would be interesting and useful because it’s uniquely not supported in iOS Safari?


@supports selector() originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/supports-selector/feed/ 2 354115
You want enabling CSS selectors, not disabling ones https://css-tricks.com/you-want-enabling-css-selectors-not-disabling-ones/ https://css-tricks.com/you-want-enabling-css-selectors-not-disabling-ones/#comments Tue, 31 Aug 2021 20:17:25 +0000 https://css-tricks.com/?p=350788 I think this is good advice from Silvestar Bistrović:

An enabling selector is what I call a selector that does a job without disabling the particular rule.

The classic example is applying margin to everything, only to have to remove …


You want enabling CSS selectors, not disabling ones originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I think this is good advice from Silvestar Bistrović:

An enabling selector is what I call a selector that does a job without disabling the particular rule.

The classic example is applying margin to everything, only to have to remove it from the final element because it adds space in a place you don’t want.

.card {
  margin-bottom: 1rem;
}

/* Wait but not on the last one!! */
.parent-of-cards :last-child {
  margin-bottom: 0;
}

You might also do…

/* "Disabling" rule */
.card:last-child {
  margin-bottom: 0;
}

But that’s maybe not as contextual as selecting from the parent.

Another variation is:

.card:not(:last-child) {
  margin-bottom: 1rem;
}

That’s what Silvestar refers to as “enabling” because you’re only ever applying this rule — not applying it and then removing it with another selector later. I agree that’s harder to understand and error-prone.

Yet another example is a scoped version of Lobotomized Owls:

/* Only space them out if they stack */
.card + .card {
  margin-top: 1rem;
}

I think gap is where this is all headed in the long term. Put the onus on the parent, not the child, and keep it an enabling selector:

.parent-of-cards {
  display: grid;
  gap: 1rem;
}

To Shared LinkPermalink on CSS-Tricks


You want enabling CSS selectors, not disabling ones originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/you-want-enabling-css-selectors-not-disabling-ones/feed/ 4 350788
CSS Vocabulary https://css-tricks.com/css-vocabulary/ Mon, 27 Jul 2020 21:47:42 +0000 https://css-tricks.com/?p=317807 This is a neat interactive page by Ville V. Vanninen to reference the names of things in the CSS syntax. I feel like the easy ones to remember are “selector,” “property,” and “value,” but even as a person who writes …


CSS Vocabulary originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is a neat interactive page by Ville V. Vanninen to reference the names of things in the CSS syntax. I feel like the easy ones to remember are “selector,” “property,” and “value,” but even as a person who writes about CSS a lot, I forget some of the others. Like the property and value together (with the colon) is called a declaration. And all the declarations together, including the curly brackets (but not the selector)? That’s a declaration block, which is slightly more specific than a block, because a block might be inside an at-rule and thus contain other complete rule-sets.

To Shared LinkPermalink on CSS-Tricks


CSS Vocabulary originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
317807
A Use Case for a Parent Selector https://css-tricks.com/a-use-case-for-a-parent-selector/ https://css-tricks.com/a-use-case-for-a-parent-selector/#comments Tue, 31 Dec 2019 17:06:54 +0000 https://css-tricks.com/?p=300633 Having a “parent selector” in CSS is mentioned regularly as something CSS could really use. I feel like I’ve had that thought plenty of times myself, but then when I ask my brain for a use case, I find it …


A Use Case for a Parent Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Having a “parent selector” in CSS is mentioned regularly as something CSS could really use. I feel like I’ve had that thought plenty of times myself, but then when I ask my brain for a use case, I find it hard to think of one. Well, I just had one so I thought I’d document it here.

A classic parent/child:

<div class="parent">
  <div class="child"></div>
</div>

Say it makes a lot of sense for this parent to have hidden overflow and also for the child to use absolute positioning.

.parent {
   overflow: hidden;
   position: relative;
}

.child {
   position: absolute; 
}

Now let’s say there’s one special circumstance where the child needs to be positioned outside the parent and still be visible. Hidden overflow is still a good default for the vast majority of situations, so it’s best to leave that rule in place, but in this very specific situation, we need to override that overflow.

.special-child {
   position: absolute; 
   bottom: -20px; /* needs to be slightly outside parent */
}

/* Not real, but just to make a point */
.special-child:parent(.parent) {
   overflow: visible;
}

That selector above is fake but it’s saying, “Select the parent of .special-child,” which would allow that override as needed. Maybe it’s like this:

.parent < .special-child {

}

…which is selecting the element on the left rather than the right. Who knows? Probably both of those are problematic somehow and the final syntax would be something else. Or maybe we’ll never get it. I have no idea. Just documenting a real use case I had.

You might be thinking, “Why not just use another special class on the parent?” I would have, but the parent was being injected by a third-party library through an API that did not offer to add a class of my choosing on it. Ultimately, I did have to add the class to the parent by writing some custom JavaScript that queried the DOM to find the .special-child, find the parent, then add the class there.

Do y’all have some other use-cases for a parent selector?


Here’s one from Uzair Hayat:

My project has an <input> which is wrapped in a<div>. The <div> has a few design elements which need to take effect once the <input> is in :focus. I could have used ::before and ::after pseudo-elements, but inputs do not support those as they are replaced elements. Right now, I juse JavaScript to detect if the input is in focus and apply a class to the parent div. I wish I could do…

input:focus:parent(div):after {
    display: block;
    /* display design changes when focused */
}

A Use Case for a Parent Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-use-case-for-a-parent-selector/feed/ 19 300633
Could Grouping HTML Classes Make Them More Readable? https://css-tricks.com/could-grouping-html-classes-make-them-more-readable/ https://css-tricks.com/could-grouping-html-classes-make-them-more-readable/#comments Mon, 22 Apr 2019 19:45:46 +0000 http://css-tricks.com/?p=286565 You can have multiple classes on an HTML element:

<div class="module p-2"></div>

Nothing incorrect or invalid there at all. It has two classes. In CSS, both of these will apply:

.module { }
.p-2 { }
const div = document.querySelector("div");


Could Grouping HTML Classes Make Them More Readable? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
You can have multiple classes on an HTML element:

<div class="module p-2"></div>

Nothing incorrect or invalid there at all. It has two classes. In CSS, both of these will apply:

.module { }
.p-2 { }
const div = document.querySelector("div");
console.log(div.classList.contains("module")); // true
console.log(div.classList.contains("p-3"));    // false

But what about grouping them? All we have here is a space-separated string. Maybe that’s fine. But maybe we can make things more clear!

Years ago, Harry Roberts talked about grouping them. He wrapped groups of classes in square brackets:

<div class="[ foo  foo--bar ]  [ baz  baz--foo ]">

The example class names above are totally abstract just to demonstrate the grouping. Imagine they are like primary names and variations as one group, then utility classes as another group:

<header class="[ site-header site-header-large ]  [ mb-10 p-15 ]">

Those square brackets? Meaningless. Those are there to visually represent the groups to us developers. Technically, they are also classes, so if some sadist wrote .[ {}, it would do stuff in your CSS. But that’s so unlikely that, hopefully, the clarity from the groups outweighs it and is more helpful.

That example above groups the primary name and a variation in one group and some example utility classes in another group.

I’m not necessarily recommending that approach. They are simply groups of classes that you might have.

Here’s the same style of grouping, with different groups:

<button class="[ link-button ] [ font-base text-xs color-primary ] [ js-trigger ]" type="button" hidden>

That example has a single primary name, utility classes with different naming styles, and a third group for JavaScript specific selectors.

Harry wound up shunning this approach a few years ago, saying that the look of it was just too weird for the variety of people and teams he worked with. It caused enough confusion that the benefits of grouped classes weren’t worth it. He suggested line breaks instead:

<div class="media  media--large
            testimonial  testimonial--main"> 

That seems similarly clear to me. The line breaks in HTML are totally fine. Plus, the browser will have no trouble with that and JSX is generally written with lots of line breaks in HTML anyway because of how much extra stuff is plopped onto elements in there, like event handlers and props.

Perhaps we combine the ideas of line breaks as separators and identified groups… with emojis!

See the Pen
Grouping Classes
by Chris Coyier (@chriscoyier)
on CodePen.

Weird, but fun. Emojis are totally valid there. Like the square brackets, they could also do things if someone wrote a class name for them, but that’s generally unlikely and something for a team to talk about.

Another thing I’ve seen used is data-* attributes for groups instead of classes, like…

<div 
  class="primary-name"
  data-js="js-hook-1 js-hook-2"
  data-utilities="padding-large"
>

You can still select and style based on attributes in both CSS and JavaScript, so it’s functional, though slightly less convenient because of the awkward selectors like [data-js="js-hook-1"] and lack of convenient APIs like classList.

How about you? Do you have any other clever ideas for class name groups?


Could Grouping HTML Classes Make Them More Readable? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/could-grouping-html-classes-make-them-more-readable/feed/ 16 286565
CSS Selectors are Conditional Statements https://css-tricks.com/css-selectors-are-conditional-statements/ Thu, 06 Dec 2018 15:22:51 +0000 http://css-tricks.com/?p=279857 .foo { }

Programmatically, is:

if (element has a class name of "foo") {

}

Descendent selectors are && logic and commas are ||. It just gets more complicated from there, with things like combinators and pseudo selectors. Just


CSS Selectors are Conditional Statements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
.foo { }

Programmatically, is:

if (element has a class name of "foo") {

}

Descendent selectors are && logic and commas are ||. It just gets more complicated from there, with things like combinators and pseudo selectors. Just look at all the ways styles can cascade.

Jeremy Keith:

If you find you can’t select something in the CSS, that’s a sign that you probably need to add another class name to the HTML. The complexity is confined to the markup in order to keep the CSS more straightforward, modular, and maintainable.

To Shared LinkPermalink on CSS-Tricks


CSS Selectors are Conditional Statements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
279857
One Invalid Pseudo Selector Equals an Entire Ignored Selector https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/ https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/#comments Fri, 05 Oct 2018 13:45:01 +0000 http://css-tricks.com/?p=276762 Perhaps you know this one: if any part of a selector is invalid, it invalidates the whole selector. For example:

div, span::butt {
  background: red;
}

Even though div is a perfectly valid selector, span:butt is not, thus the entire …


One Invalid Pseudo Selector Equals an Entire Ignored Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Perhaps you know this one: if any part of a selector is invalid, it invalidates the whole selector. For example:

div, span::butt {
  background: red;
}

Even though div is a perfectly valid selector, span:butt is not, thus the entire selector is invalidated — neither divs nor span::butt elements on the page will have a red background.

Normally that’s not a terribly huge problem. It may even be even useful, depending on the situation. But there are plenty of situations where it has kind of been a pain in the, uh, :butt.

Here’s a classic:

::selection {
  background: lightblue;
}

For a long time, Firefox didn’t understand that selector, and required a vendor prefix (::-moz-selection) to get the same effect. (This is no longer the case in Firefox 62+, but you take the point.)

In other words, this wasn’t possible:

/* would break for everyone */
::selection, ::-moz-selection {
  background: lightblue;
}

That would break for browsers that understood ::selection and break for Firefox that only understood ::-moz-selection. It made it ripe territory for a preprocessor @mixin, that’s for sure.

Here’s another zinger.

/* For navigation with submenus */
ul.submenu {
  display: none;
}

ul.menu li:hover ul.submenu,
ul.menu li:focus ul.submenu,
ul.menu li:focus-within ul.submenu {
  display: block;
}

/* Oh no! We've broken all menu functionality in IE 11, 
   because it doesn't know what `:focus-within` is so it
   throws out the entire selector */

This behavior is annoying enough that browsers have apparently fixed it going forward. In a conversation with Estelle Weyl, I learned that this is being changed. She wrote in the MDN docs:

Generally, if there is an invalid pseudo-element or pseudo-class within in a chain or group of selectors, the whole selector list is invalid. If a pseudo-element (but not pseudo-class) has a -webkit- prefix, As of Firefox 63, Blink, Webkit and Gecko browsers assume it is valid, not invalidating the selector list.

This isn’t for any selector; it’s specifically for pseudo-elements. That is, double colons (::).

Here’s a test:

I’d call that a positive change.


One Invalid Pseudo Selector Equals an Entire Ignored Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/feed/ 5 276762
Selectors That Depend on Layout https://css-tricks.com/selectors-that-depend-on-layout/ Tue, 02 Oct 2018 22:44:38 +0000 http://css-tricks.com/?p=277031 “Why the heck don’t we have ::first-column?”

I heard someone ask that the other day and it’s a valid question. I’d even take that question further by asking about ::nth-column() or whatever else relates to CSS columns. We …


Selectors That Depend on Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
“Why the heck don’t we have ::first-column?”

I heard someone ask that the other day and it’s a valid question. I’d even take that question further by asking about ::nth-column() or whatever else relates to CSS columns. We have stuff like ::first-letter and ::first-line. Why not others?

There are many notable things missing from the “nth” crowd. Seven years ago, I wrote “A Call for ::nth-everything” and it included clear use cases like, perhaps, selecting the first two lines of a paragraph.

I don’t know all the technical details of it all, but I know there are some fairly decent reasons why we don’t have all of these in CSS. Part of it is the difficulty of getting it specced (e.g. words and characters get tricky across written languages) and part of it is the difficulty of implementing them. What I just found out is that there is a FAQ document that explains!

So, why don’t we have ::first-column? Because it’s a “selector that depends on layout”:

This falls into a class of problems that unlikely to be solvable in CSS: selectors in general, and pseudo classes in particular, cannot depend on layout, because otherwise they could be used to modify layout in a way that made them no longer match, which would modify the layout back to where it was, so they match again, and we get stuck in an infinite loop of contradictions.

For a simple example:

:stuck { position: static; }

Now what?

Some of the changes web developers might want to apply with a :stuck pseudo class may be safe and not trigger such loops, but selectors are a generic mechanism, and would enable this kind of contradictions.

So even though many of the problem people are trying to address using such pseudo classes are legitimate, selectors are unlikely to be the answer.

What we’ve got are infinite loops that are basically the problem (but read the FAQ — it goes into great detail about the nuance of it). In a related way, the same reason we don’t have element queries in CSS.

It’s a little tricky to think about because even stuff like ::first-line are riddled with paradoxes. Say you use it to increase the font-size. That means fewer characters fit on the line, so the characters that originally matched are now pushed down and don’t match anymore. Seems a bit loopy, but that’s been sorted out. Plus, classics like :hover potentially causing jitter. The document talks about these things in clear terms. It’s not all cut and dry!

The whole FAQ is a fascinating read and covers much more than this situation.

To Shared LinkPermalink on CSS-Tricks


Selectors That Depend on Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
277031
“Stop Using CSS Selectors for Non-CSS” https://css-tricks.com/stop-using-css-selectors-non-css/ https://css-tricks.com/stop-using-css-selectors-non-css/#comments Tue, 16 Jan 2018 22:14:30 +0000 http://css-tricks.com/?p=265461 I saw Nicole Dominguez tweet this the other day:

I wasn’t at this conference, so I have very little context. Normally, I’d consider it …


“Stop Using CSS Selectors for Non-CSS” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I saw Nicole Dominguez tweet this the other day:

I wasn’t at this conference, so I have very little context. Normally, I’d consider it a sin to weigh in on a subject brought up by looking at two out-of-context slides, but I’m only weighing in out of interest and to continue the conversation.

The idea seems to be that if you need to select an element in the DOM with JavaScript, don’t use the same selector as you would in CSS.

So if you have…

<article class="article">
</article>

…and you need to apply an event listener to that article for some reason, then don’t use…

$(".article")

(or querySelector or whatever, I assume.)

Instead, apply an attribute intended just for the JavaScript to target, like…

<article class="article" data-hoverable>
</article>

…and target that like…

$("[data-hoverable]")

The idea is that you can separate jobs. The class has the job of styling, and the data attribute has the job of JavaScripting. Both can change without affecting each other.

Seems reasonable to me.

Also seems like there is plenty to talk about here. Performance, I suppose, but that’s probably the least-interesting thing since selectors are generally pretty damn fast these days. We could continue the conversation by talking about:

  • What naming convention?
  • Should you be naming events?
  • What if it needs to be selected for different reasons multiple times?
  • Can you or should you use IDs?
  • Is it worth avoiding DOM selection at all if you can?
  • What other nuances are part of this discussion?

I saw Michael Scharnagl had some thoughts on his own usage of ID’s, classes, and data-attributes that could help frame things a bit.


“Stop Using CSS Selectors for Non-CSS” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/stop-using-css-selectors-non-css/feed/ 40 265461
Use a Sass Variable for a Selector https://css-tricks.com/snippets/sass/use-sass-variable-selector/ https://css-tricks.com/snippets/sass/use-sass-variable-selector/#comments Mon, 02 Jan 2017 20:51:51 +0000 http://css-tricks.com/?page_id=249595 Say you need to use a certain selector in multiple places in your code. It’s not tremendously common, to be sure, but stuff happens. Repeated code is typically an opportunity for abstraction. Abstracting values in Sass is easy, but selectors …


Use a Sass Variable for a Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Say you need to use a certain selector in multiple places in your code. It’s not tremendously common, to be sure, but stuff happens. Repeated code is typically an opportunity for abstraction. Abstracting values in Sass is easy, but selectors is slightly trickier.

One way to do it is to create your selector as a variable. Here’s an example that is a comma separated list of selectors:

$selectors: "
  .module,
  body.alternate .module
";

Then to use that, you have to interpolate the variable, like this:

#{$selectors} {
  background: red;
}

That works with nesting too:

.nested {
  #{$selectors} {
    background: red;
  }
}

Prefixing

A variable can also be only a part of a selector, like a prefix.

$prefix: css-tricks-;

.#{$prefix}button {
  padding: 0.5rem;
}

You could use nesting to do prefixing as well:

.#{$prefix} {
  &module {
    padding: 1rem;
  } 
  &header {
    font-size: 110%;
  }
  &footer {
    font-size: 90%;
  }
}

Selectors in a Map

Perhaps your abstraction lends itself to a key/value pair situation. That’s a map in Sass.

$selectorMap: (
  selectorsFoo: ".module",
  selectorsBar: ".moodule-2"
);

You can use them individually like:

#{map-get($selectorMap, selectorsFoo)} {
  padding: 1rem;
}

Or loop through them:

@each $selectorName, $actualSelector in $selectorMap {
  #{$actualSelector} {
    padding: 1rem;
  }
}

Examples

See the Pen Sass Variables for Selectors by Chris Coyier (@chriscoyier) on CodePen.


Use a Sass Variable for a Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/snippets/sass/use-sass-variable-selector/feed/ 8 249595
inStyle (Modifying the Current Selector `&` in Sass) https://css-tricks.com/instyle-current-selector-sass/ https://css-tricks.com/instyle-current-selector-sass/#comments Fri, 27 May 2016 13:06:17 +0000 http://css-tricks.com/?p=242174 The following is a guest post by Filip Naumovic from Salsita Software. Filip has built a Sass tool to help with an issue I know I’ve experienced many times. You’re happily nesting in Sass. You’re maybe a level or


inStyle (Modifying the Current Selector `&` in Sass) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The following is a guest post by Filip Naumovic from Salsita Software. Filip has built a Sass tool to help with an issue I know I’ve experienced many times. You’re happily nesting in Sass. You’re maybe a level or two deep, and you need to style a variation based on some parent selector. You need to either break out of the nesting and start a new nesting context, or go nuclear with @at-root. I’ll let Filip tell the story of his new tool that changes that.

Sass has helped a lot of us tremendously with cleaning up our CSS code bases. When it arrived on the scene, some extremely useful patterns emerged and got quickly adopted.

For example, the usage of &amp; and nesting allowed for the cleaning up of otherwise rather gross code blocks.

This:

.my-app { display: block; }
.my-app .widget { border-radius: 5px; }
.my-app .widget.blue { color: blue; }
.isIE6 .my-app .widget { background-image: url('fake-borders.png'); }
@media (max-width: 768px) { .my-app .widget { float: left; } }

Turned into this:

.my-app {
  display: block;
  .widget {
    border-radius: 5px;
    &amp;.blue {
      color: blue;
    }
    .isIE6 &amp; {
      background-image: url("fake-borders.png");
    }
    @media (max-width: 768px) {
      float: left;
    }
  }
}

What a positive change for our sanity!

All style variations of the .widget element are clearly nested below itself, using indentation as both a visual cue for relevancy and a query generator.

The current selector (&amp;), in this case, provides a shortcut for the most common patterns. Styling the variation of an element that is invoked either by a property of the element itself or a prepending parent state.

Nested media queries are a younger addition, but they hint that evolution towards indented syntax for styles comes almost naturally. It’s easy to read and navigate, because it somehow mirrors the familiar DOM structure and keeps all styles for an element in one place, while still producing our precious, yet sometimes complicated, selectors.

Today, nesting and current selector features are present in Less, Sass, and Stylus. With a bit of wine, one could almost call it a standard.

A classic case of “You can’t do that.”

Using the above code block as an example, let’s add styles for .my-app.expanded .widget.

Despite our mighty tools, we quickly find ourselves with limited choices:

Option 1

Using the modern @at-root directive (or / in Stylus), we leave the current scope entirely and repeat the full root query to keep the relevant new styles nested below .widget, because the current selector can’t help us express this relationship.

.my-app {
  display: block;
  .widget {
    border-radius: 5px;
    &amp;.blue {
      color: blue;
    }
    .isIE6 &amp; {
      background-image: url("fake-borders.png");
    }
    // repeating the root selector here
    @at-root .my-app.expanded .widget {
      color: red'
    }
    @media (max-width: 768px) {
      float: left;
    }
  }
}

This creates harder to read code with a lot of duplicity, especially when real world usage extends way over our small example piece. But, it keeps our glorious nesting paradigm intact.

Option 2

We create a new code block below .my-app and use it to change all child elements relevant to the .expanded state. This means that our .widget is now styled in different places, and this separation grows for every added state in each element in the nest.

.my-app {
  display: block;
  .widget {
    border-radius: 5px;
    &amp;.blue {
      color: blue;
    }
    .isIE6 &amp; {
      background-image: url("fake-borders.png");
    }
    @media (max-width: 768px) {
      float: left;
    }
  }
  &amp;.expanded .widget
     color: red;
  }
}

While it is in direct violation of our “nesting all relevant styles” dream, it’s the imperfection we learned to live with. Many of you would probably even defend this pattern, because it has been the way things are done for quite a while.

However, for the sake of choice, wouldn’t it be great to have an Option 3? One that would allow us to express the simple change in .my-app.expanded that influences our .widget without having to escape the context?

This idea has been secretly bothering me for quite a while, if only out of some form of OCD about my own stylesheets. I’ve made it my sidequest to try and find this missing tool in the style shed.

Finding Option 3

While digging around the topic, I’ve found spider webs, eternal discussions, and wildly varying propositions, many of which suggested adding some special syntax to the current selector character &amp;. Doing that would mean months of learning complicated core libraries and fighting the long war, which instantly felt like an unacceptable burden.

Secondly, I think &amp; works well because it’s a clear representation of the whole context, and for that reason it might be problematic adding more features to it. It does one thing and does it well, so creating a good partner to it seemed like a better idea at this time.

For sake of easy integration, I’ve decided to implement the idea on the level of preprocessor language, so you could just @import and use it right away. Preprocessors are powerful frameworks nowadays, so why not?

My first choice was Stylus, because it’s just so awesome. Unfortunately, due to issue 1703, the current selector placeholder cannot be modified inside a mixin function as of right now. Like a good zealot I’ll wait until the end of time for Stylus to fix it, but I had to keep searching for something I could implement now.

You shall not parse the current selector, as I’ve learned, in Less, so that was out.

SassScript on the other hand proved to be a powerhouse. While it is missing many useful abstractions for manipulation with strings and arrays, it is very possible to craft such functions manually. Many of them are already provided by Sass Prince Kitty Giraudel.

After months of controlled string terror…

inStyle for Sass 3.4+ is born!

Cheesy name, I know. But it’s suggestive of the functionality, because you want this thing readable in the actual code. Mixin syntax is already familiar with preprocessor users, so having a suggestive name for describing changes in element parents sounded right to me as an added bumper against unfamiliarity.

Either way, all of it has to stay readable while handling complex cases, otherwise it loses purpose in favor of @at-root selector approaches or just nesting the code elsewhere. I decided to go with two basic mechanisms that I believe address even the most despicable needs, while keeping a logically simple parsing algorithm:

Use 1) Modification

Additions to a compound element present in the current selector proved to handle ~80% of real world code, just like our first example tries to achieve.

.my-app {
  display: block;
  .widget {
    border-radius: 5px;
    &amp;.blue {
      color: blue;
    }
    .isIE6 &amp; {
      background-image: url("fake-borders.png");
    }
    @include in(".my-app.expanded") {
      color: red; // .my-app.expanded .widget { };
    }
    @media (max-width: 768px) {
      float: left;
    }
  }
}

Try to read that like this:

styling .widget in the .my-app.expanded state.

The function searches the nest bottom to top for the first occurrence of .my-app element (skipping current element) and appends the class .expanded to it, returning a new selector.

What about longer queries and combo modifications?

table {
  table-layout: fixed;
  thead {
    font-weight: normal;
    tr {
      height: 30px;
      td {
        background-color: #fafafa;
        &amp;.user {
          font-weight: bold'
        }
        @include in('table.one tr.two:hover') {
          background-image: url(rainbow.png) // table.one thead tr.two:hover td { };
        }
      }
    }
  }
}

The tr parent is found and modified with .two:hover. Going upwards, table is also found and modified with .one, other elements are skipped.

Irrelevant multi-selectors are removed from the new selector:

ul, ol {
  list-style: none;
  li {
    display: inline-block;
    a {
      text-decoration: underline;
      @include in("ol.links") {
        color: orange; // ol.links li a { };
      }
    }
  }
}

Impossible cases and invalid CSS queries produce a blocking Sass error on compilation:

table {
  table-layout: fixed;
  td {
    height: 30px;
    @include in("table^s&amp;()#") {
      what: the; // ERROR, invalid CSS
    }
    @include in ("tr.green:hover") {
      border-color: green; // ERROR, no tr or tr.green to modify in &amp;
    }
  }
}

While crash testing this in production (hah!), I found another very practical need that I couldn’t satisfy with modifications of the parent tree only. In fact, it solves the example above, because you have to be able to do just that with tr.green:hover. You just have to be able to say where.

Use 2) Insertion

Let’s assume the following:

table {
  table-layout: fixed;
  thead {
    font-weight: normal;
  }
  tr {
    height: 30px;
  }
  td {
    background-color: #fafafa;
  }
}

Where would you ideally nest a table thead tr selector? By the dogma, you seemingly have to add it as follows:

table {
  table-layout: fixed;
  thead {
    font-weight: normal;
    tr {
      height: 50px;
    }
  tr {
    height: 30px;
  }
  td {
    background-color: #fafafa;
  }
}

However, the styled element in question is tr and you already have that as a generic style, so theoretically, nesting it below itself as a variant might be closer to how you think about the relationship, filling the gaps that current selector &amp; cannot describe.

In this case, it means there has to be a simple way to insert some selector at a certain position above the current element while also allowing combinations with compound modifications. I couldn’t imagine this without adding a special character, so I went with the visually suggestive ^ caret.

table {
  table-layout: fixed;
  thead {
    font-weight: normal;
  }
  tr {
    height: 30px;
    @include in("^thead") {
      height: 50px; // table thead tr { };
    }
  }
  td {
    background-color: #fafafa;
    @include in("table.blue-skin ^tbody") {
      background-color: blue; // table.blue-skin tbody td { };
    }
  }
}

In this case, the caret is inserting thead one level above current or last modified element. More carets mean higher jumps in the current selector:

main {
  display: flex;
  &gt; div {
    flex-grow: 1;
    span {
      display: inline-block;
      &amp;.highlight {
        outline-style: dashed;
        @include in("^^.section.active") {
          outline-style: solid; // main .section.active &gt; div span.highlight { };
        }
        @include in("^^^.section") {
          some: thing; // ERROR, inserting too high, it would equal to ".section &amp;"
        }
      }
    }
  }
}

Note: &amp;.highlight is the same element as span, so the insertion treats it as one step in the nest

I think inStyle shines in the simplest cases, which are also by far the most common. But things can get more complex if needed.

Use 3) Advanced combinations

The matching algorithm allows you to go even wilder with inserting in or modifying more compounds at once:

.my-app {
  display: flex;
  section {
    flex: 1;    
    header {
      width: 100%;
      @include in("^^.wrapper ^.dialog)") {
        height: 50px; // .my-app .wrapper section .dialog header { };
      }
      @include in("^.overlay ^.sidebar") {
        position: fixed; // .my-app section .overlay .sidebar header { };
      }
      @include in("^.modifier section.next ^.parent") {
        opacity: 0; // .my-app .modifier section.next .parent header { };
      }
    }
  }
}
  1. .dialog is inserted one level above header and .wrapper is inserted two levels.
  2. .sidebar is inserted above header and .overlay directly above it.
  3. Pushes .parent above header, modifies section with .next and then pushes .modifier above it.

This reminds me, perhaps you have some feedback! I’ve been thinking about enabling some simpler syntax when you want to insert more compound elements directly after each other as in the second case, perhaps something like @include in("^(.overlay .sidebar)") or improve the parser and enable more natural @include in("^.overlay .sidebar"). Let me know your opinion!

After using it for a while, I’ve found that most of my inconvenient code patterns are solved rather easily by just changing one element here and there or pushing a new selector at a certain position and keep things in place. Still, I need to be honest, it is potentially quite invasive to your usual code organization by nature of the idea.

I can see how using inStyle might bring on heated arguments. My colleagues seem to be either open minded or don’t care, which is both great.

If you use it, I would hope that the correct handling would be like with any other tool: when it’s fit for the job. Spamming complex nested mixins will unlikely score high on readability than flat out writing the full query, but on the other hand it can simplify most real world problems while keeping a slim footprint.

In the near future, I’d like to get the Stylus port working and perhaps create an Atom editor plugin to display the resulting query as a hint in the code.

It was fun taking a shot at solving the first-world problems of CSS and it is my hope that you consider the subject at least worthy of a discussion. The project is open source, so feel free to get onboard with either code or feedback!

Love it or hate it, here it is on GitHub, here’s a little microsite and here’s a live debugger for good measure.

See the Pen inStyle Crash Test Dummy by Salsita Software (@salsita) on CodePen.

Thanks for reading!


inStyle (Modifying the Current Selector `&` in Sass) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/instyle-current-selector-sass/feed/ 36 242174
Strategies for Keeping CSS Specificity Low https://css-tricks.com/strategies-keeping-css-specificity-low/ https://css-tricks.com/strategies-keeping-css-specificity-low/#comments Mon, 12 Jan 2015 15:45:20 +0000 http://css-tricks.com/?p=192577 Keeping CSS specificity low across all the selectors in your project is a worthy goal. It’s generally a sign that things are in relative harmony. You aren’t fighting against yourself and you have plenty of room to override styles when …


Strategies for Keeping CSS Specificity Low originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Keeping CSS specificity low across all the selectors in your project is a worthy goal. It’s generally a sign that things are in relative harmony. You aren’t fighting against yourself and you have plenty of room to override styles when you need to. Specificity on selectors tends to creep up over time, and there is a hard ceiling to that. I’m sure we’ve all felt the pain of !important tags and inline styles.

So how do we keep that specificity low over time?

Give yourself the class you need

Perhaps you’re writing a high-specificity selector because you’re overriding an already-existing selector. Maybe the element you’re trying to select is included on multiple pages, so you select it from a higher-up class:

.section-header {
  /* normal styles */
}

body.about-page .section-header {
  /* override with higher specificity */
}

However you feel about this, recognize that that specificity is creeping up here. To prevent this, is there a way you can alter the class name on that element you’re trying to style directly instead? Sometimes creating some server side helper functions or variables for emitting classes can be helpful, to avoid logic in views.

<header class="<%= header_class %>">
  Which could output one class, or both, as desired.
</header>
.section-header {
  /* normal styles */
}

.about-section-header {
  /* override with same specificity */
  /* possibly extend the standard class */
}

Consider the source-order of the stylesheets

Along that same trying-to-override-something vein, perhaps you are applying an additional class name to handle a style override for you.

<header class="section-header section-header-about">
  
</header>

But where you are writing your override styles with .section-header-about are actually getting overridden by the existing class. That can happen because of selector order in the stylesheet. Both selectors have the exact same specificity, so the rules from whichever one is declared last win.

Fixing that means just ensuring that where ever you write your override class comes later. Perhaps organize your stylesheets something like:

@import "third-party-and-libs-and-stuff";

@import "global-stuff";

@import "section-stuff";

@import "specific-things-and-potential-overrides";

Reduce the specificity of the thing you’re overriding

You know what they say about leaving things better than you found them. You should do that.

If there is an ID on an element and that’s what it’s being styled by, can you remove it? Definitely do a project-wide search for #that before you do it. It might be being used as a JS hook (perfectly fine) in which case you should leave it alone and either add a class or use a class already on it for the CSS.

Don’t avoid classes to begin with

I’ve been known to use a selector like:

.module > h2 {

}

That’ll work fine, until they day you want a special style for that h2 element. You might go in your your markup and be like:

<div class="module">
  <h2 class="unique">
    Special Header
  </h2>
</div>

But sadly:

.module > h2 {
  /* normal styles */
}
.unique {
  /* I'm going to lose this specificity battle */
}
.module .unique {
  /* This will work, but specificity creep! */
}

The specificity creep is happening because the original selector is biting us. That’s why almost all CSS methologies recommend flat structures in the vein of:

<div class="module">
  <h2 class="module-header">
  </h2>
  <div class="module-content">
  </div>
</div>

It can feel like more tedious work up-front because you might not need those classes right away. But by not avoiding that work (don’t be lazy!), the hooks you’ll have later can save your grief.

Use the cascade

As a project ages, it becomes more and more dangerous to alter selectors with low specificity, because they potentially can affect more things and have unintended consequences.

#im #just .gonna[do] .this .to .be #safe {
  /* cries (ಥ_ʖಥ) */
}

But affecting more things is the power of CSS. Having a solid base you’re building from hopefully means less overrides are ever necessary. The strategies for this can be things like…

Use a pattern library and/or style guide and/or atomic design

A pattern library (something like this) can mean you have what you are looking for when you need it. Need some tabs? Here you go, this is the established way for doing that. And it’s likely built in such a way the specificity is already light, so overriding won’t be overly difficult.

Atomic design (book) can guide how your site (or the pattern library) is built, so even if you don’t have a full pattern for what you need, you have the building blocks below it.

A style guide might save you, because it might enforce specific rules about specificity, in an attempt to save you from yourself.

Consider opt-in typography

At the same time you’re trying to use the cascade and have smart defaults for lots of elements, you might want to scope some of those smart defaults sometimes. For instance, a list element is often used for navigation and within content. The styles for them will be drastically different. So why not start with a clean slate and apply text styling only when needed.

/* reset styles globally */
ul, ol, li {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* scope text styles to text content */
.text-content {
  h1, h2, h3 {
  }
  p {
  }
  ul {
  }
  ol {
  }
  /* etc */
}

That does increase the specificity of those text selectors, but it means that rules specifically for text aren’t affecting more than they need to be and there is less need for overrides.

Outside of your control issues

Perhaps some third party code expects or injects certain HTML onto your page. You have to work with that. You can still try and use as low specificity selectors as you can. You can leave comments in the code indicating why the selectors are like this. You can use low specificity selectors, but use !important overrides. You can ask the people responsible for the non-ideal code if they can fix it.

Only up the specificity lightly, and note it

If you need to get into a specificity fight, rather than reaching for a sledgehammer like an ID or !important, trying something lighter.

A tag-qualifier is the minimum possible specificity upgrade.

ul.override {
  /* I win */  
}
.normal {
}

But limiting a selector to a specific tag is a weird limiting factor. Might be smart to just add an additional class instead. If another name doesn’t make sense, you can even use the same class if needed.

.nav.override {
}
.override.override {
}
.nav {
}

Just because nesting is nice, it doesn’t mean specificity has to go up

Nesting in preprocessors is sometimes discouraged because it makes it so easy to write overly selectors. But nesting is sometimes just visually nice in the stylesheet, because it groups things together making it easier to read and digest.

Bohdan Kokotko reminded me recently the ampersand in Sass can be used to essentially do namespacing rather than compound selectors.

.what {
    color: red;
   &-so {
      color: green;
      &-ever {
        color: blue;
      }
   }
}
.what {
  color: red;
}
.what-so {
  color: green;
}
.what-so-ever {
  color: blue;
}

The single-class wrapper

A rather forceful way to handle an override is use an existing wrapping element over the area you need to apply style overrides to, or add a new one.

<div class="override">

   ... existing stuff ...

</div>

You now have the ability to override any existing style in there by only adding a single class to the selector. It’s specificity creep, but an attempt at keeping it low.

.override .existing {
}

Only once

If you’re ever overriding an override, that’s a good place to stop and re-consider.

Can you give yourself just a single class that you can use instead? A single override can actually be efficient, like a variation on a pattern instead of creating a new pattern. An override of an override is a recipe for confusion and further fighting.

Just do better next time

If you’ve been bitten by specificity problems, even if fixing them is impractical right now, at least you know now that it’s problematic and can approach things differently next time.


Strategies for Keeping CSS Specificity Low originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/strategies-keeping-css-specificity-low/feed/ 40 192577