Overriding :root CSS variables from inner scopes [duplicate] - css

This question already has answers here:
CSS scoped custom property ignored when used to calculate variable in outer scope
(2 answers)
Closed 3 years ago.
In our design system at Stack Overflow, we use Less to compile CSS color values.
We have global Less variables like #orange-500 that are frequently modified for hover states, building border styling, background colors, etc.
In Less, this is written as darken(#orange-500, 5%). I'm trying to achieve something similar using native CSS variables. Switching to CSS variables will allow us to ship features that rely on theming (Stack Exchange Network, Dark Mode, etc.) much faster, with way fewer lines of CSS, while enabling swapping variables on media query (high contrast, dark mode, etc).
This example of overriding our color’s lightness value in hsl works when the variables are scoped to a CSS class:
.card {
--orange: hsl(255, 72%, var(--lightness, 68%));
background: var(--orange);
}
.card:hover {
--lightness: 45%;
}
<div class="card">
Hello world
</div>
However, we need to specify our color variables globally in a single, swappable place to support global theming, but this doesn't work as expected:
:root {
--orange: hsl(255, 72%, var(--lightness, 68%));
}
.card {
background: var(--orange);
}
.card:hover {
--lightness: 45%;
}
<div class="card">
Hello world
</div>
I've tried switching from :root to html or body without any luck. Any workarounds to this?

This is a scoping issue. The way you're doing it, you're inheriting --orange from the :root, and --orange in the :root has a lightness of 68%.
In order to change it, you'll want to re-scope the --orange variable to an element that will look up the new --lightness value. There's a few ways to pull this off:
Option 1: duplicate the --orange variable on the element:
:root {
--lightness: 68%;
--orange: hsl(255, 72%, var(--lightness));
}
.card {
background: var(--orange);
--orange: hsl(255, 72%, var(--lightness));
}
.card:hover {
--lightness: 45%;
}
<div class="card">
Hello world
</div>
Obviously this kinda stinks, because you're going to have to duplicate that --orange variable.
Option 2:
You could abstract the other parameters of --orange so that it's not as duplicative. I'd be a fan of this approach despite the fact that it's more text:
:root {
--lightness: 68%;
--orangeHue: 255;
--orangeSat: 72%;
--orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
.card {
background: var(--orange);
--orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
.card:hover {
--lightness: 45%;
}
<div class="card">
Hello world
</div>
What you could also do is scope this specifically to a .darkMode class that might be applied to the HTML element or the body. This could also make sense because it's clear what the intent is from the code:
Option 3
:root {
--lightness: 68%;
--orangeHue: 255;
--orangeSat: 72%;
--orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
.card {
background: var(--orange);
}
.card:hover {
--lightness: 45%;
}
.darkMode .card {
--orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
<div class="darkMode">
<div class="card">
Hello world
</div>
</div>
Regardless of how you go, the issue is that the --orange variable is inheriting from its original scope where --lightness is set. Think of it as "inheriting a computed value".
In order to get --orange to get the new lightness, you need a new --orange somewhere.
Option 4
I'm not sure what your theme pattern is, but I can explain how I created a dark mode on my own blog . If you look at the CSS What you'll see is that I've created two complete themes that follow the same naming convention:
--themeLightTextColor: rgb(55, 55, 55);
--themeLightBGColor: rgb(255, 255, 255);
--themeLightAccentColor: rgb(248, 248, 248);
--themeLightTrimColor: rgb(238, 238, 238);
--themeDarkTextColor: rgb(220, 220, 220);
--themeDarkBGColor: rgb(23, 23, 23);
--themeDarkAccentColor: rgb(55, 55, 55);
--themeDarkTrimColor: rgb(40, 40, 40);
What I then do is create a third set of variables whose job it is to be the "active" managers:
--themeActiveLinkColor: var(--linkColor);
--themeActiveLinkColorHover: var(--linkColorHover);
--themeActiveTextColor: var(--themeLightTextColor);
--themeActiveEditorialTextColor: var(--themeLightPltNLow);
--themeActiveBGColor: var(--themeLightBGColor);
--themeActiveAccentColor: var(--themeLightAccentColor);
--themeActiveTrimColor: var(--themeLightTrimColor);
Then, I scope the active theme settings under a single class:
.theme--dark {
--themeActiveTextColor: var(--themeDarkTextColor);
--themeActiveEditorialTextColor: var(--themeDarkPltNLow);
--themeActiveBGColor: var(--themeDarkBGColor);
--themeActiveAccentColor: var(--themeDarkAccentColor);
--themeActiveTrimColor: var(--themeDarkTrimColor);
}
It seems like maybe your intent is to not have to explicitly declare a theme, but rather tweak some "root variables" to adjust it. But I would suggest that maybe you have a pattern in place where a single class can change an active theme. The advantage to this pattern is that you would be able to also adjust any "root variables" on the class name.

I would be interested to learn if there is anything more ideal than this solution but as a possible workaround, you can break up your CSS variables a bit further and build the values inside the element style definitions like so:
:root {
--orangeColor: 37,72%;
--redColor: 1,72%;
--blueColor: 215,72%;
--greenColor: 126,72%;
--LumDefault: 68%;
--LumDark: 45%;
--LumLight: 80%;
}
.card {
background: hsl(var(--orangeColor), var(--LumDefault));
}
.card:hover {
background: hsl(var(--orangeColor), var(--LumDark));
}
.card:active {
background: hsl(var(--redColor), var(--LumDark));
color: hsl(var(--greenColor), var(--LumLight));
}
<div class="card">
Hello world
</div>
I do realize that this does not override as you wanted to accomplish but from the business case you stated, it will give you a way to manage elements at a global level...just a bit more work in defining your CSS on the front end.

The simple solution is to place the CSS variables into a separate CSS file and then swap it out as needed. For example, a media query to support dark mode could do the swap or you could use JavaScript, a pre-baked theme, etc.
What's nice about this is swapping the CSS file with your variable definitions changes the CSS rendering in real-time.
Assume you're using media queries for light/dark mode. If the browser understands and requests "dark mode" then only the first file is loaded. However, if the browser doesn't understand those media queries your "default" is the light.css since both CSS files are loaded but subsequent rules override previous rules.
<link rel="stylesheet" href="/dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/light.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)">
<!-- The main stylesheet -->
<link rel="stylesheet" href="/style.css">
Inside those stylesheets, you want to use the :root pseudo-class as it's basically the same as HTML, but has higher specificity in most browsers.
light.css
:root {
--text-color: #333;
--background-color: #fff;
}
dark.css
:root {
--text-color: #dadada;
--background-color: #333;
}
Also, note that simplifying the variables and building the full rule inside the element is a good idea as Travis mentions in that answer.
style.css (main styling document)
body {
color: var(--text-color);
background-color: var(--background-color);
}
As a side note, you may want to read up on the CSS color-scheme property to get a bit better support with native browser elements.

Related

How do I create a CSS mixin using the new `part` syntax?

I want to apply a number of CSS rules to different selectors, without creating additional selectors. In SCSS, this would be typically done with a mixin, eg:
#mixin gradient-text {
color: transparent;
background-clip: text;
-webkit-background-clip: text;
background-image: linear-gradient(
350deg,
var(--dark-blue),
var(--teal),
var(--bright-green)
);
}
Reading around the internet, there's lots of references to making mixins with the CSS apply syntax, but
https://caniuse.com/sr_css-apply mentions:
#apply was briefly supported behind a flag in Chromium but has since been abandoned in favor of the ::part() selector.
Reading about CSS part though it seems like it's not possible to use CSS part without modifying my HTML and using web components, which have their own issues.
Is it possible to do a mixin in CSS, without modifying my HTML or JS, using part?
According to the MDN article you linked to, ::part can only match elements within a shadow tree. Additionally, the spec for the ::part states
The ::part() pseudo-element only matches anything when the originating element is a shadow host.
Thus, if you wanted to leverage this pseudo-element for CSS mixins, you'd be better working with (developing) a native web component library. You may be able to use the corresponding part HTML attribute outside of the Shadow DOM to implement CSS mixins depending on your requirements.
When in doubt the best thing is to experiment. Here is an example of using ::part() and part (HTML attr) inside and outside of a shadow DOM. Best to test browser support on part as it is a relatively new technology. Moreover, seems there is still ongoing questions about how multiple ident's should be supported, if at all.
customElements.define('custom-thing', class CustomThing extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'closed'})
root.append(document.getElementById('custom').content.cloneNode(true))
}
})
[part~="a"] {
color: red;
}
[part~="b"] {
padding: 20px;
background: gray;
}
p::part(a) {
color: blue !important;
}
custom-thing::part(a) {
color: green;
}
custom-thing::part(a)::after {
content: 'A';
}
custom-thing::part(b) {
color: orange;
}
custom-thing::part(a b) {
/* does multiple ident values work? */
color: blue;
}
<p part="a b">part</p>
<template id="custom">
<style>
p[part="a"] {
color: aqua;
}
</style>
<p part="a">part a</p>
<p part="b">part b</p>
<p part="a b">part a b</p>
</template>
<custom-thing></custom-thing>

CSS variables and custom properties - How do they work?

dynamic-text-colors.css
:root {
--title-color: #555555;
}
.text-title-color {
color: var(--title-color);
}
.bg-blue-100 {
--title-color: #999999;
}
.bg-blue-200 {
--title-color: #888888;
}
.bg-blue-300 {
--title-color: #777777;
}
index.html
<div class="bg-blue-100">
<h1 class="text-title-color">I am colored #999999</h1>
</div>
<div class="bg-blue-200">
<h1 class="text-title-color">I am colored #888888</h1>
</div>
<div class="bg-blue-300">
<h1 class="text-title-color">I am colored #777777</h1>
</div>
Question:
I don't understand the process that allows each h1 to have a different color. In this instance, I don't understand how the value of "text-title-color" can be different based the background color.
"Custom properties are scoped to the element(s) they are declared on, and participate in the cascade: the value of such a custom property is that from the declaration decided by the cascading algorithm." - https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
According to the explanation above of custom props by Mozilla, each variable is scoped to its element its decalred on. As such, would the browser some how convert the code to something like this and if so where is the code below stored?:
.bg-blue-100 .text-title-color{
color: #999999
}
.bg-blue-200 .text-title-color{
color: #888888
}
.bg-blue-300 .text-title-color{
color: #777777
}
The browser doesn't need to do any conversion in the way you describe
The variables are all part of what it understands.
It's not like a preprocessor which has to convert everything to 'real' CSS before anything runs.
When the browser has to render an h1, say, as in your question it will pick up the value for --title-color from whichever style is relevant. Just as it would pick up, say, the color or width if they were set there.
The value of CSS variables is precisely because they can be set at run time, e.g. by Javascript on some user action and the new value will be used 'on the fly'.

Appropriate use of BEM in CSS

I'm an old hand with CSS, but have recently decided to take the plunge and begin using BEM. For the most part, I understand the value of using such a flat system, avoiding overly specific selectors etc...
My question is, is the following approach correct. It works, technically, but also seems fragile:
.badge {
/* additional declarations */
background: rgba(0, 0, 0, 0.2);
}
.badge--error {
background: red;
}
.badge--success {
background: green;
}
This works fine, because of the cascading nature of CSS. So the default background is overwritten by the modifier successfully. But if I put the modifier before the initial declaration, the modifier is ignored (because of the equal specificity).
Are there any issues with writing BEM this way? Is it considered bad practice to declare a default value of something like a background within the block, if it's to be overwritten with modifiers? Should the default background in this instance, live in a .badge--default modifier? Or have I written this in a true BEM fashion, and BEM actually relies on CSS' cascading in order to remain flat?
You could make use of CSS variables
.badge {
background: var(--background);
}
.badge--error {
--background: var(--error);
}
.badge--success {
--background: var(--success);
}
:root {
--background: yellow;
--error: red;
--success: green;
}
<div class="badge">
a badge
</div>
<div class="badge badge--success">
a badge success
</div>
<div class="badge badge--error">
a badge error
</div>
<div class="badge" style="--background: purple">
a badge random
</div>
I don't see why a modifier could not modify just a background if it is(n't) set in the initial element.
For BEM I can recommend using a CSS preprocessor like SASS since it make it quite easy to nest elements there is less change of declaring some modifier before the initial declaration. Because of the nesting your CSS becomes much more organised. It is also easier to import different components so each component can live in its own file.
With SASS you can do:
.badge {
&--error {}
&--success {}
}

Creating CSS Global Variables : Stylesheet theme management [duplicate]

This question already has answers here:
How can I define colors as variables in CSS?
(19 answers)
Closed 6 years ago.
Is there a way to set global variables in css such as:
#Color1 = #fff;
#Color2 = #b00;
h1 {
color:#Color1;
background:#Color2;
}
Latest Update: 16/01/2020
CSS Custom Properties (Variables) have arrived!
It's 2020 and time to officially roll out this feature in your new applications.
Preprocessor "NOT" required!
There is a lot of repetition in CSS. A single color may be used in several places.
For some CSS declarations, it is possible to declare this higher in the cascade and let CSS inheritance solve this problem naturally.
For non-trivial projects, this is not always possible. By declaring a variable on the :root pseudo-element, a CSS author can halt some instances of repetition by using the variable.
How it works
Set your variable at the top of your stylesheet:
CSS
Create a root class:
:root {
}
Create variables (-- [String] : [value])
:root {
--red: #b00;
--blue: #00b;
--fullwidth: 100%;
}
Set your variables anywhere in your CSS document:
h1 {
color: var(--red);
}
#MyText {
color: var(--blue);
width: var(--fullwidth);
}
BROWSER SUPPORT / COMPATIBILITY
See caniuse.com for current compatability.
Firefox: Version 31+ (Enabled by default)
Supported since 2014 (Leading the way as usual.)
More info from Mozilla
Chrome: Version 49+ (Enabled by default).
Supported since 2016
Safari/IOS Safari: Version 9.1/9.3 (Enabled by default).
Supported since 2016
Opera: Version 39+ (Enabled by default).
Supported since 2016
Android: Version 52+ (Enabled by default).
Supported since 2016
Edge: Version 15+ (Enabled by default).
Supported since 2017
CSS Custom Properties landed in Windows Insider Preview build 14986
IE: When pigs fly.
It's time to finally let this ship sink. No one enjoyed riding her anyway. ☺
W3C SPEC
Full specification for upcoming CSS variables
Read more
TRY IT OUT
A fiddle and snippet are attached below for testing:
(It will only work with supported browsers.)
DEMO FIDDLE
:root {
--red: #b00;
--blue: #4679bd;
--grey: #ddd;
--W200: 200px;
--Lft: left;
}
.Bx1,
.Bx2,
.Bx3,
.Bx4 {
float: var(--Lft);
width: var(--W200);
height: var(--W200);
margin: 10px;
padding: 10px;
border: 1px solid var(--red);
}
.Bx1 {
color: var(--red);
background: var(--grey);
}
.Bx2 {
color: var(--grey);
background: black;
}
.Bx3 {
color: var(--grey);
background: var(--blue);
}
.Bx4 {
color: var(--grey);
background: var(--red);
}
<p>If you see four square boxes then variables are working as expected.</p>
<div class="Bx1">I should be red text on grey background.</div>
<div class="Bx2">I should be grey text on black background.</div>
<div class="Bx3">I should be grey text on blue background.</div>
<div class="Bx4">I should be grey text on red background.</div>
You can't create variables in CSS right now. If you want this sort of functionality you will need to use a CSS preprocessor like SASS or LESS. Here are your styles as they would appear in SASS:
$Color1:#fff;
$Color2:#b00;
$Color3:#050;
h1 {
color:$Color1;
background:$Color2;
}
They also allow you to do other (awesome) things like nesting selectors:
#some-id {
color:red;
&:hover {
cursor:pointer;
}
}
This would compile to:
#some-id { color:red; }
#some-id:hover { cursor:pointer; }
Check out the official SASS tutorial for setup instructions and more on syntax/features. Personally I use a Visual Studio extension called Web Workbench by Mindscape for easy developing, there are a lot of plugins for other IDEs as well.
Update
As of July/August 2014, Firefox has implemented the draft spec for CSS variables, here is the syntax:
:root {
--main-color: #06c;
--accent-color: #006;
}
/* The rest of the CSS file */
#foo h1 {
color: var(--main-color);
}
It's not possible using CSS, but using a CSS preprocessor like less or SASS.
Try SASS http://sass-lang.com/ or LESS http://lesscss.org/
I love SASS and use it for all my projects.
I do it this way:
The html:
<head>
<style type="text/css"> <? require_once('xCss.php'); ?> </style>
</head>
The xCss.php:
<? // place here your vars
$fntBtn = 'bold 14px Arial'
$colBorder = '#556677' ;
$colBG0 = '#dddddd' ;
$colBG1 = '#44dddd' ;
$colBtn = '#aadddd' ;
// here goes your css after the php-close tag:
?>
button { border: solid 1px <?= $colBorder; ?>; border-radius:4px; font: <?= $fntBtn; ?>; background-color:<?= $colBtn; ?>; }
You will either need LESS or SASS for the same..
But here is another alternative which I believe will work out in CSS3..
http://css3.bradshawenterprises.com/blog/css-variables/
Example :
:root {
-webkit-var-beautifulColor: rgba(255,40,100, 0.8);
-moz-var-beautifulColor: rgba(255,40,100, 0.8);
-ms-var-beautifulColor: rgba(255,40,100, 0.8);
-o-var-beautifulColor: rgba(255,40,100, 0.8);
var-beautifulColor: rgba(255,40,100, 0.8);
}
.example1 h1 {
color: -webkit-var(beautifulColor);
color: -moz-var(beautifulColor);
color: -ms-var(beautifulColor);
color: -o-var(beautifulColor);
color: var(beautifulColor);
}

Chaining CSS rules

I have defined some background colors that I'll be using on my site. So I can easily set the background color of different elements like:
.background_highlite{
background-color: rgb(231, 222, 207); /*Cream in my Coffee*/
}
.background_shadow{
background-color: rgb(201, 179, 156); /*Moose Mousse*/
}
Now, if I want all textarea elements on my page to have Moose Mousse color as their background I want to write another CSS rule that references back to .background_shadow, so I only have to change the rgb values in one place.
Something like:
textarea{
height:50px;
background-color: background_highlite /* want to feed forward to keep the rgb in one place */
}
Is this possible with CSS?
People have been frustrated by CSS's simplistic structure, and have created pre-processors to write CSS more conveniently. Look at Less, for example, or CleverCSS.
You can assign all the elements the same class, and then set the background color in the class's CSS:
<textarea class="background_shadow">blah</textarea>
Keep in mind that you can assign a number of classes to any element, so you can use one class just to control the background color, and then use other classes for your other needs:
<textarea class="background_shadow another_class something_else">...</textarea>
Not really. http://dorward.me.uk/www/css/inheritance/ lists your main options.
Sorry, no. CSS does not support variables, or chaining.
however, there is a javascript library that allows that. http://lesscss.org/
The best you can do would be
.hilight textbox {
background: black;
}
textbox {
color: pink;
}
.background_shadow {
background: grey;
}
Or, of course, you could add the .hilite class to your div.
You have two options to work with:
Native CSS, which is possible, but not good to maintain.
Preprocessor, like xCSS, which can create more cleaner code and provide variables.
For simple projects I assume, native CSS will be good. But in more complicated it`s best to use some sort of processors, like pals talked earlier.
In this method you can always use some human readable rule like:
.blabla {min-height: 20px}, which pre-processor by your own logic transform to CSS, that all of our target browsers can understand, like .blabla {min-height: 20px; height: auto !important; height: 20px;} etc.
Also what I realy like in preprocessors is that you can right code, as here:
.specialClass extends .basicClass {} // see more at extends
.selector {
a {
display: block;
}
strong {
color: blue;
}
} // see more at children
or what you needed is vars {
$path = ../img/tmpl1/png;
$color1 = #FF00FF;
$border = border-top: 1px solid $color1;
} // see more at vars

Resources