CSS Variables - Swapping values? - css

I have a very simple problem with CSS variables. I would like to swap two CSS variables, basically the CSS equivalent of [a, b] = [b, a] in ES6. Here's a simple example:
<p>White background</p>
<button>Black background</button>
<div>
<p>Black background</p>
<button>White background</button>
</div>
:root {
--primary-color: #fff;
--secondary-color: #000;
}
body {
background-color: var(--primary-color);
}
button {
background-color: var(--secondary-color);
}
div {
/* i'd like to do the following: */
--primary-color: var(--secondary-color);
--secondary-color: var(--primary-color);
/* so here, `--primary-color` would be `--secondary-color` from `:root`
* and any children have these colors swapped as well
*/
background-color: var(--primary-color);
}
However, this fails because CSS var()s are live bindings. Am I missing something here? Or is this the way the spec currently works?

You are creating a cyclic dependence because you are defining each property using the other one and this won't work. Instead you may try something like this by introducing more variables:
:root {
--p:#fff;
--s:#000;
--primary-color: var(--p);
--secondary-color: var(--s);
}
body {
background-color: var(--primary-color);
}
button {
background-color: var(--secondary-color);
}
div {
/* i'd like to do the following: */
--primary-color: var(--s);
--secondary-color: var(--p);
background-color: var(--primary-color);
}
<p>White background</p>
<button>Black background</button>
<div>
<p>Black background</p>
<button>White background</button>
</div>

Related

CSS attribute from body element used on other classes?

I am trying to create a basic CSS template for a project. It needs to support both a light and dark mode.
In the html, the body tag has data-layout-color attribute. I have some toggles that allow switching between light and dark, and it is updating this attribute. In my CSS sheet, I use the attribute selector for background color, and it works! Now I need to be able to set other elements color based on the light/dark mode, but that's not working as the individual element doesn't have the attribute. I don't want to add data-layout-color to everything, and then have to update it all with my js. Any suggestions?
HTML:
<body ng-controller="myApp" data-layout-color="dark" data-layout="topnav">
<button type="button" class="btn btn-primary">PRESS ME!</button>
</body>
CSS:
body[data-layout-color="dark"]{
background-color: var(--my-body-dark-bg);
}
body[data-layout-color="light"]{
background-color: var(--my-body-light-bg);
}
.btn-primary[data-layout-color="light" {
color: var(--my-white-light);
background-color: var(--my-primary-light);
border-color: var(--my-primary-light);
}
.btn-primary[data-layout-color="dark" {
color: var(--my-white-dark);
background-color: var(--my-primary-dark);
border-color: var(--my-primary-dark);
}
You could write your selectors such that the attribute selector remains on body:
/* primary button under a "light" layout parent */
[data-layout-color="light"] .btn-primary {
color: var(--my-white-light);
background-color: var(--my-primary-light);
border-color: var(--my-primary-light);
}
But I think a better idea would be to change the custom property values so you don't need the theme-specific selectors on child elements in the first place:
[data-layout-color="dark"] {
--button-color-bg: white;
--button-color-fg: black;
}
[data-layout-color="light"] {
--button-color-bg: black;
--button-color-fg: white;
}
.btn-primary {
background-color: var(--button-color-bg);
color: var(--button-color-fg);
display: inline-block;
padding: 0.25em 1em;
border: 1px solid grey;
margin: 0.5em;
}
<div data-layout-color="dark">
<div class="btn-primary">Dark Body</div>
</div>
<div data-layout-color="light">
<div class="btn-primary">Light Body</div>
</div>
With plain css you can write it like this
body[data-layout-color="dark"]{
background-color: var(--my-body-dark-bg);
}
body[data-layout-color="dark"] .btn-primary{
color: var(--my-white-dark);
background-color: var(--my-primary-dark);
border-color: var(--my-primary-dark);
}
body[data-layout-color="dark"] .btn-primary a{
text-decoration: underline overline #FF3028;
}
I suggest you use scss though. It will make your life easier. If you'r using visualstudio code just download Live sass complier and click watch sass in the bottom right corner.
Using scss you would write it like this:
body[data-layout-color="dark"]{
background-color: var(--my-body-dark-bg);
.btn-primary{
color: var(--my-white-dark);
background-color: var(--my-primary-dark);
border-color: var(--my-primary-dark);
a{
text-decoration: underline overline #FF3028;
}
}
.btn-secondary{
color: var(--my-white-dark-secondary);
background-color: var(--my-primary-dark-secondary);
border-color: var(--my-primary-dark-secondary);
}
p{
color: var(--my-white-dark);
}
}
body[data-layout-color="light"]{
background-color: var(--my-body-light-bg);
/*etc etc*/
}

Can you combine CSS variables to include the property and the value?

While I know you can't write variables like
root: {
--aic: align-items:center;;
}
Is there anyway to get round this, by combining the various parts seperately? The obvious obstical here is the requirement of the colon inside the variable.
i.e.
root: {
--ai: align-items:;
--center: center;
--aic:
var(--ai)
var(--center);
}
.myclass {var(--aic);}
I would suggest you to switch to SCSS and use a #mixin. Read more about it here.
Here's a live demo.
HTML:
<div id="test">TEST</div>
SCSS:
:root {
--text_color: red;
--background_color: gold;
}
#mixin my_mixin {
color: var(--text_color);
background-color: var(--background_color);
}
#test {
#include my_mixin;
}
Based on my comment on your question, you can use classes to achieve something similar. But you can't use custom properties as CSS properties, only values -- it's the same as saying for example margin: margin: var(--customMargin);;
/* Layout unrelated to answer */
div { border: 1px solid black; color: white }
.varText { background-color: red }
.varPad { background-color: blue }
.varText.varPad { background-color: green }
/* Answer */
:root { --size: 1rem }
.varText { font-size: var(--size) }
.varPad { padding: var(--size) }
<div class="varText">
Size Text only to root variable
</div>
<div class="varText" style="--size: 2rem">
Size Text only to inline variable
</div>
<div class="varPad">
Size Padding only to root variable
</div>
<div class="varPad" style="--size: 2rem">
Size Padding only to inline variable
</div>
<div class="varText varPad">
Size Text and Padding to root variable
</div>
<div class="varText varPad" style="--size: 2rem">
Size Text and Padding to inline variable
</div>

How to combine two SCSS selectors?

I want to give a different color to the element if either of the two conditions exists.
Here is the code:
.btn-link{
color: $color-black;
margin: 0;
&:hover{
color: $color-light-blue;
}
&:not(.collapsed){
color: $color-light-blue;
}
}
Everything is good, but it will be better if you can combine the two selectors
I already tried:
&:hover&:not(.collapsed){
color: $color-light-blue;
}
But only the hover is identified
Same way as you do in CSS:
&:hover, &:not(.collapsed) {
color: $color-light-blue;
}
This sets the color only if the element is in a hover state or doesn't have the class collapsed.
You can put a comma between the two combining selectors, like this: &:hover, &:not(.collapsed) {
Full example:
HTML:
<span class="btn-link">one class</span>
<span class="btn-link collapsed">two class</span>
CSS:
$color-black: black;
$color-light-blue: lightblue;
.btn-link {
color: $color-black;
margin: 0;
&:hover, &:not(.collapsed) {
color: $color-light-blue;
}
}
JSfiddle.
Sorry, no StackSnippet. We still can't handle SCSS here!
You can try this simple change your code
/*SCSS*/
$color-light-blue : red;
.btn-link {
&:hover:not(.collapsed) {
color: $color-light-blue;
}
}
/*compiled CSS*/
.btn-link:hover:not(.collapsed) {
color: red;
}
<span class="btn-link">one class</span>
<span class="btn-link collapsed">two class</span>

Update CSS Module variables from Javascript

I'm using a (now older) version of react-boilerplate which came with CSS Modules. What's nice about them is that you can create variables and import them in other CSS files.
Here's my colors.css file
:root {
/* Status colors */
--error: #842A2B;
--success: #657C59;
--pending: #666;
--warning: #7E6939;
}
When I'm importing that file I just have to use at the top of my .css file:
#import 'components/App/colors.css';
I'm looking to have the option for two themes for my website and I would like to be able to dynamically update those variables with Javascript. What's the best way to do this?
Edit: I was hoping there's a way to update the colors.css file and not have to do conditional imports in all the components that draw from two possible css files... let me know if there's a way to do that and if there is I'll change the accepted answer. Thank you to all who answered!
I would just use the default color vars on the element/body/whatever, then put the alternate theme colors in another class, and toggle the theme class via JS. Here's a demo.
$("button").on("click", function() {
$("body").toggleClass("foo");
});
body {
--red: red;
--blue: blue;
--yellow: yellow;
background: #ccc;
text-align: center;
font-size: 5em;
}
.foo {
--red: #ce1126;
--blue: #68bfe5;
--yellow: #ffd100;
}
.red {
color: var(--red);
}
.blue {
color: var(--blue);
}
.yellow {
color: var(--yellow);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="red">RED</span> <span class="blue">BLUE</span> <span class="yellow">YELLOW</span>
<br>
<button>click me</button>
I would have 2 sheets and conditionally switch between the two:
colours.scss
:root {
/* Status colors */
--error: #842A2B;
--success: #657C59;
--pending: #666;
--warning: #7E6939;
}
otherColours.scss
:root {
/* Status colors */
--error: #FF0000;
--success: #00FF00;
--pending: #6666FF;
--warning: #FF00FF;
}
then in your react code import them and use them as you wish:
import styles from 'colours.scss';
import alternativeStyles from 'otherColours.scss';
...
{this.props.useNormalStyle ? styles.myClass : alternativeStyles.myClass}
Is this what you are looking for?
// get the inputs
const inputs = [].slice.call(document.querySelectorAll('.controls input'));
// listen for changes
inputs.forEach(input => input.addEventListener('change', handleUpdate));
inputs.forEach(input => input.addEventListener('mousemove', handleUpdate));
function handleUpdate(e) {
// append 'px' to the end of spacing and blur variables
const suffix = (this.id === 'base' ? '' : 'px');
document.documentElement.style.setProperty(`--${this.id}`, this.value + suffix);
}
:root {
--base: #ffc600;
--spacing: 10px;
--blur: 10px;
}
body {
text-align: center;
}
img {
padding: var(--spacing);
background: var(--base);
-webkit-filter: blur(var(--blur));
/* 👴 */
filter: blur(var(--blur));
}
.hl {
color: var(--base);
}
/*
misc styles, nothing to do with CSS variables
*/
body {
background: #193549;
color: white;
font-family: 'helvetica neue', sans-serif;
font-weight: 100;
font-size: 50px;
}
.controls {
margin-bottom: 50px;
}
a {
color: var(--base);
text-decoration: none;
}
input {
width:100px;
}
<h2>Update CSS Variables with <span class='hl'>JS</span></h2>
<div class="controls">
<label>Spacing:</label>
<input type="range" id="spacing" min="10" max="200" value="10">
<label>Blur:</label>
<input type="range" id="blur" min="0" max="25" value="10">
<label>Base Color</label>
<input type="color" id="base" value="#ffc600">
</div>
<img src="http://unsplash.it/800/500?image=899">
<p class="love">😘</p>
<p class="love">Chrome 49+, Firefox 31+</p>

How I can change a variable in a div in a CSS, using SASS?

I wonder if there is any way to change the assignment of a variable inside a div.
Viewing the code, I think it is more visible what I want:
## CSS ##
.div-name-1 {
$cor: #f00;
}
.div-name-2 {
$cor: #d1d1d1;
}
.div-name-3 {
$cor: #fff;
}
.whatever{
background: $cor;
}
<div class="div-name-2>
<div class="whatever">
</div>
<div class="div-name-3>
<div class="whatever">
</div>
IIRC, variables in SASS are scoped to the block they appear in. Since .div-name-1 and .whatever are different blocks, they are different scopes and cannot transfer values of variables between them.
You will need to reorganize your SASS code so they are in the same block, or introduce a mixin in order to create the same effect:
.div-name-1 {
$cor: #f00;
.whatever {
background: $cor;
}
}
.div-name-2 {
$cor: #d1d1d1;
.whatever {
background: $cor;
}
}
Alternatively:
#mixin whatever($cor) {
.whatever {
background: $cor;
}
}
.div-name-1 { #include whatever(#f00); }
.div-name-2 { #include whatever(#d1d1d1); }

Resources