23

I enjoy using CSS custom properties, but there's this thing that I often find that I wish I could do.

I wish to be able to apply some styles conditionally based on the value of a CSS custom property. Here's some pseudo-code:

.something {
  border: var(--is-dark-theme) ? 1px solid : none;
}

I understand that custom properties do not work like this. But may be there is some other way that I'm not aware of that could help me achieve a similar result?

Or perhaps there is some spec proposal that would this possible in the future?

1
  • Depends if you want to do styles based on a value comparison or text string, both are possible. Look at this MDN reference on attribute selectors for more info. for value comparison answers, you can use the answer by @oridori for string matching you can use Attribute selectors. Here is an excellent reference for string matching. developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors Commented Oct 2, 2018 at 22:06

7 Answers 7

12

Here is another idea similar to Ori Drori's answer where I rely on the use of an invalid value inside border to remove the border. This can be useful in case you want to use keywords like false/true/yes/no

.something {
  border: var(--is-dark-theme,2px) solid black;
}
<div class="something">Dark theme</div>

<div class="something" style="--is-dark-theme: false">Light theme</div>

Sign up to request clarification or add additional context in comments.

1 Comment

Nice! That's actually closer to what the OP wants, since you can do background: var(--is-dark-theme, red);
9

In cases where you have something like "condition xyz is on or off" (like in the light theme vs dark theme example) you can use the following CSS trick (I know this looks completely weird if you see this trick for the first time, but in contrast to the tricks presented in the other answers, this one will work for all CSS properties):

For each on/off condition define two custom properties, one for the 'on' case, one for the 'off' case. Instead of on use the value initial, instead of off use value (space character).

.light-theme {
  --is-light-theme: initial; 
  --is-dark-theme: ;
}

.dark-theme {
  --is-light-theme: ;
  --is-dark-theme: initial;
}

div {
  font-family: Helvetica, Arial, sans-serif;
  padding: 1rem;
  
  color:
    var(--is-light-theme, black)
    var(--is-dark-theme, white);
  
  background-color:
    var(--is-light-theme, #f4f4f4)
    var(--is-dark-theme, black);

  border:
    var(--is-light-theme, none)
    var(--is-dark-theme, 5px solid #aaa);
}
<div class="light-theme">
  light theme
</div>
<div class="dark-theme">
  dark theme
</div>

1 Comment

This answer should have more upvotes - this is the cleanest way to do it
9

Just FYI: The following is not working today (2023-02-06), but someday in future there might be this very nice CSS feature called container style queries:

/* not working today (2023-02-06), maybe in future */

@container style(--theme: dark) {
  .box {
    border: 1px solid #aaa;
  }
}

2 Comments

This is now supported by browsers that use Blink: developer.mozilla.org/en-US/docs/Web/CSS/…
I am using container style queries in production as a progressive enhancement and added more context and instructions based on my experience in another answer. stackoverflow.com/a/79437732/2758250
7

You can sometimes use calc() to get roughly similar results. In this case if --is-dark-theme is 0 or 1, you can multiply it by the width of border, to show or hide it:

.something {
  border: calc(var(--is-dark-theme) * 1px) solid black;
}
<div class="something" style="--is-dark-theme: 1">Dark theme</div>

<div class="something" style="--is-dark-theme: 0">Light theme</div>

Comments

3

This code enables you to apply a class based on a CSS property of a parent. So by applying a different CSS to a parent element, you can change the class applied to a child element:

.outer:hover {
  container-name: dark;
  container-type: inline-size;
}

.inner{background-color:silver;color:black;}

@container dark (min-width: 0px)  {
  .inner{background-color:#333;color:white}
}
<div class="outer">
  <div class="inner">inner</div>
</div>

Here we have element with the class name .inner, By default it has a silver background and white text color.

Above it we have a parent element with the class name .outer. the .outer class no styles by default, but on hover we set the container-name to dark.

We can the target the .inner element when it's inside an element that has the container-name:dark and use an always true predicate that is (min-width: 0px)

Comments

3

Here's an alternative version of my demo above, using Thorgeir's genius answer (I've added those extra .container divs to make the solution a bit better understandable):

.light-theme {
  --theme: light;
}

.dark-theme {
  --theme: dark;
}

.container {
  container: var(--theme) / inline-size;  
}

.text-box {
  font-family: Helvetica, Arial, sans-serif;
  padding: 1rem;
  color: black;
  background-color: #f4f4f4;
  border: none;
}

@container dark (min-width: 0) {
  .text-box {
    color: white;
    background-color: black;
    border: 5px solid #aaa;
  }
}
  <div class="light-theme">
    <div class="container">
      <div class="text-box">light theme</div>
    </div>
  </div>
  <div class="dark-theme">
    <div class="container">
      <div class="text-box">dark theme</div>
    </div>
  </div>
  

Comments

2

Container Queries

Container queries provide the ability to add conditional styles, including conditionally adding styles based on other computed styles.

Introduction to Container Queries

Containers and container queries are a conceptual extension on what you may be familiar with from using @media queries for responsive styling. The difference is that the conditions come from the closest containment ancestor, instead of viewport size.

General CSS Container Query Syntax:

@container (width > 400px) {
  h2 {
    font-size: 1.5em;
  }
}

You can even name containers to specify which type of ancestor to look for in your queries.

CSS container queries on MDN

@container on MDN

Container Style Queries

To solve the original question, use container style queries, where your conditional styles come from computed style conditions.

Example Solution

.my-container {
  /* This is the ancestor that sets the CSS style containment context. */
  /* - I've provided it an name, `my-container`, that can optionally be used to filter your `@container` query */
  container: my-container;
}

/* This is your condition */
/* - Boolean logic and comparisons of any style, color, and custom property are supported. */
/* - You can even compare variables using `style(--button-color: var(--brand-color))` */
@container style(--is-dark-theme: 1) {
  .my-conditionally-styled-element {
    /* And here's your conditional style. */
    /* Want an else case? Use the CSS cascade! For instance, use the same selector above the conditional style to provide a default. */
    border: 1px solid;
  }
}
<div class="my-container" style="--is-dark-theme: 1">
  <p class="my-conditionally-styled-element">My closest container is in dark theme!</p>
</div>

<div class="my-container" style="--is-dark-theme: 0">
  <p class="my-conditionally-styled-element">My closest container is in light theme!</p>
</div>

Learn more about container style queries on MDN

Important Implementation Considerations

  1. The style query is evaluated on the container, not the selectors inside the query. If you would like to compare two variables and one of them is on the same element as you'd like to apply your styles, consider moving the variable to a wrapper element and making the wrapper a container.
  2. The container must be an ancestor with a containment context. If you don't already have a parent with a CSS container, consider the default value of container-type: normal which enables style query containment without the side effects of container size queries that you may find from popular examples that use a value like inline-size.
  3. To compare colors, you may need a valid @property definition (syntax: "<color>";, inherits, and initial-value) for your variable.
  4. Browser support for container queries is "Baseline 2023 - Newly available". If this is more restrictive than your target browser list, you may need to limit your usage to progressive enhancement or consider another option.

if() Function in CSS

Chromium 137 shipped an if() function in CSS that also can use style queries but works inline. Browser support is even lower and it's still experimental at the time of writing this update, so pay special attention to adding an explicit fallback if you try them out.

The if() statement does not gracefully degrade; an explicit fallback needs to be provided for non-supporting browsers.
- MDN

If you use it exclusively for progressive enhancement or the browser support isn't an issue for your use case, if() has some strengths that may make it a good option:

  • You don't need a CSS container or even apply the variable to a containing ancestor since it's no longer a container query

  • You can write your expressions inline without the syntax bloat from @container

.my-conditionally-styled-element {
  /* Make sure to add a fallback for when `if()` is not supported */
  border: none;
  /* Conditional style */
  border: if(
    style(--is-dark-theme: 1): 1px solid;
    else: none;
  );
}
<p class="my-conditionally-styled-element" style="--is-dark-theme: 1">I am in dark theme!</p>

<p class="my-conditionally-styled-element" style="--is-dark-theme: 0">I am in light theme!</p>

Learn More about CSS if() on MDN

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.