I have components that can be themed:
<div class="ComponentFoo theme-blue">
</div>
Components can be nested into one another.
I want a theme applied to a parent component propagate to all its children.
A naive implementation could look like this:
<div class="ComponentFoo theme-blue">
<div class="ComponentBar">
<div class="ComponentBaz">
</div>
</div>
<div class="ComponentBar">
</div>
<div class="ComponentBar">
</div>
</div>
.theme-blue.ComponentFoo,
.theme-blue .ComponentFoo {
background-color: blue;
}
.theme-blue.ComponentBar,
.theme-blue .ComponentBar {
color: white;
}
This works well for a simple case, but fails miserably when themes are nested:
<div class="ComponentFoo theme-red">
<div class="ComponentBar theme-blue">
<div class="ComponentBaz">
</div>
</div>
</div>
.theme-blue.ComponentBaz,
.theme-blue .ComponentBaz {
border-color: blue,
color: blue,
background-color: white;
}
.theme-red.ComponentBaz,
.theme-red .ComponentBaz {
border-color: red,
color: red,
background-color: white;
}
In this case, I expect ComponentBaz to assume the blue theme because the nearest themed parent is blue. But that's not what happens!
This is because both .theme-blue .ComponentBaz and .theme-red .ComponentBaz selectors match the Baz component. CSS does not care about the depth of nesting.
When both selectors match, it is the order of declarations in CSS code that matters: last one wins. 😫
I can imagine fixing this in the following ways:
Using extremely numerous and verbose selectors exploiting the > parent combinator and something to override CSS specifity, so that .theme-red > * wins over .theme-red > * > *, etc.
I don't like this solution because it would make CSS unreadable.
Use programming/templating to pass a parent's theme into all of its children:
<ComponentFoo #theme="red" as |theme|>
<ComponentBar #theme={{theme}}>
<ComponentBaz #theme="blue" as |theme2|>
<ComponentQuux #theme={{theme2}}/>
</ComponentBaz>
</ComponentBar>
</ComponentFoo>
I don't like this solution because it's also quite verbose and introduces too much coupling.
Simply apply the theme HTML class to each themable component explicitly.
This is what I'm doing, but I don't see it a solution. More like a workaround, a lesser of evils.
What are the more elegant ways of achieving this? I want a pure CSS solution that would let me use an HTML class on a parent, so that it applies styles to children and overrides grand-parents' styles.
Since CSS is very limited, we are using the Sass preprocessor. I wouldn't mind using a solution producing messy CSS if it is abstracted away very elegantly with Sass.
I think you are setting too many rules in your CSS.
Why don't you set the selector just for the themes, and leave inheritance do the job ?
.theme-blue {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red {
border-color: red;
color: red;
background-color: white;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
Alternative solution using CSS constants
.theme-blue {
--border-color: blue;
--color: blue;
--background-color: white;
}
.theme-red {
--border-color: red;
--color: red;
--background-color: white;
}
.ComponentFoo, .ComponentBar, .ComponentBaz {
border-color: var(--border-color);
color: var(--color);
background-color: var(--background-color);
}
.other {
border-color: black;
color: green;
background-color: silver;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
Of course, you can add another class, "themable", and change the selector with .ComponentFoo .ComponentBar .ComponentBaz to ".themable"
How does this work:
In the theme-blue class you define values for CSS constants (some people call it CSS variables). This values are propagated following the cascading rules, so all the children and descendants will have the same values. But, as all the CSS inheritance, if a child redeclares this values, the children of the child will inherit the redeclared values.
All that is needed now is to set the standard CSS properties to this inherited values, that is done using the var() syntax
By going through your question, I think you want to select a child but not the grandchild. For that, in CSS we have > selector.
For example:
.test_class_1 > .test_class_2
Here it will select the child with class test_class_2 but it will not select the grandchild that is inside the test_class_2 div.
According to this, I modified your css a little bit:
.theme-blue>.ComponentBaz,
.theme-blue>.ComponentBaz {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red>.ComponentBaz,
.theme-red>.ComponentBar {
border-color: red;
color: red;
background-color: white;
}
Here I added > which selects div with ComponentBaz as a class but not the div inside this div. I also replaced "," with ";" maybe that was just typo.
Here is the JSfiddle link: https://jsfiddle.net/o20dLy7k/
If you can limit css to theme background, component color and border then you're in luck.
Everything can be simplified to 2 colours - in my snippet the first drawing lblue and black - there are only 3 delicate situations and they all are delicate only because of visibility of the text:
theme light-blue + component light-blue (or black and black)
inherited light-blue theme + component light-blue
theme light-blue + inherited component light-blue
No more worries. Seriously, everything works as a charm and validator is green, has no comments.
The first drawing - 2 colors - a test of possible worries, the second ugly, but informative - a dozen colors in various combinations. I used ::before{ content: attr(class)} for content, so my css looks worse than usual.
I decided, that very, very complicated situations - as in the center of the drawing - did not deserve to have their text colour corrected (special treatment changed the legacy), too much code. If you wish, you can use something different than color and the problem will be gone.
Of course used colours must visible differ each to other.
function switcher(){
var element = document.getElementById("body");
if(element.classList){ element.classList.toggle("shadow");}
else{
var classes = element.className.split(" ");
var i = classes.indexOf("shadow");
if(i >= 0) classes.splice(i, 1);
else classes.push("shadow");
element.className = classes.join(" ");}}
body{ color: #000}
p, span, h1, h2, h3, h4, h5, h6, div::before{ background: #fff}
/* themes */
.shadow .black{ background: #000; color: #fff}
.black{ background: #000;}
.red{ background: #f00;}
.green{ background: #0d0;}
.blue{ background: #00f}
.white{ background: #fff}
.gold{ background: gold}
.purple{ background: purple}
.grey{ background: grey}
.teal{ background: teal}
.lblue{ background: #5ae}
/* components */
.Cblack{ border-color: black; color: black}
.Cred{ border-color: red; color: red}
.Cgreen{ border-color: green; color: green}
.Cblue{ border-color: blue; color: blue}
.Cwhite{ border-color: white; color: white}
.Cgold{ border-color: gold; color: gold}
.Cpurple{ border-color: purple; color: purple}
.Cteal{ border-color: teal; color: teal}
.Clblue{ border-color: #5ae; color: #5ae}
.Cwhite::before, .Cwhite>::before{ background: #aaa}
/* shadow */
.shadow .lblue .Clblue::before, .shadow .red .Cred::before, .shadow .green .Cgreen::before, .shadow .blue .Cblue::before, .shadow .white .Cwithe::before, .shadow .gold .Cgold::before, .shadow .purple .Cpurple::before, .shadow .grey .Cgrey::before, .shadow .teal .Cteal::before, .shadow .black .Cblack::before,
.shadow .Clblue .lblue::before, .shadow .Cred .red::before, .shadow .Cgreen .green::before, .shadow .Cblue .blue::before, .shadow .Cwhite .withe::before, .shadow .Cgold .gold::before, .shadow .Cpurple .purple::before, .shadow .Cgrey .grey::before, .shadow .Cteal .teal::before, .shadow .Cblack .black::before,
.shadow .lblue.Clblue::before, .shadow .red.Cred::before, .shadow .green.Cgreen::before, .shadow .blue.Cblue::before, .shadow .white.Cwhite::before, .shadow .gold.Cgold::before, .shadow .purple.Cpurple::before, .shadow .grey.Cgrey::before, .shadow .teal.Cteal::before, .shadow .black.Cblack::before{
border: 1px dotted black;
text-shadow: -1px 0 1px black, 0 1px 1px black, 1px 0 1px black, 0 -1px 1px black;}
.shadow .black .Cblack::before, .shadow .Cblack .black::before, .shadow .black.Cblack::before{
border: 1px dotted white;
text-shadow: -1px 0 1px white, 0 1px 1px white, 1px 0 1px white, 0 -1px 1px white}
.shadow ::before, .shadow p, .shadow span, .shadow h4{ background: 0}
/* for snippet only */
button{ position: fixed; top: 45vh; right: 0; background: #acf; border: 12px solid white; color: black; padding: 25px; border-radius: 50%; outline: 0}
div{ margin: 10px 8px; padding: 10px 8px; border: 3px solid transparent;}
div::before{ content: attr(class); border-radius: 8px}
p, span, h1, h2, h3, h4, h5, h6, div::before{ padding: 2px 8px;}
<body id="body" class="x">
<div class="lblue Clblue"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cblack">
<div class="black">
<div class="Cblack">
<div class="Clblue">
<div class="lblue">
<div class="Cblack"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Clblue"><h4>h4</h4><p>paragraph</p><span>span</span></div>
<div class="black"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div></div></div></div></div></div>
<div class="red Cgold">
<div class="black">
<div class="black Cblack">
<div class="red Cblack">
<div class="green">
<div class="blue ">
<div class="white ">
</div></div></div>
<div class="gold ">
<div class="purple ">
<div class="grey Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="teal ">
<div class="white Cwhite">
</div></div></div>
<div class="teal Cteal">
<div class="white Cwhite">
<div class="blue">
<div class="black Cteal">
<div class="grey Cred">
</div></div></div>
<div class="Cpurple"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cgold"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
<div class="green">
<div class="white Cgreen">
<div class="red Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cwhite"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
</div></div></div>
</div></div></div>
</div></div></div>
<button onclick="switcher()"><b>switch</b></button></body>
suplement
If you can make sure, that text will always be served inside a tag, forget about shadows
p, span, h1, h2, h3, h4, h5, h6{ background: #fff}
If you can exclude too bright colours - this is it. If not... exceptions...
I added this to the snippet, with exception for .Cwhite::before
You need to be sure of what you mean. If you mean Themes in the traditional sense, these solutions will introduce a hard to find bug!. If more than one rule matches, then all properties not in common between the rules wll apply. For example, imagine a dark theme changing foreground and background and and neon theme changing foreground and color palette. The background (from dark) and the color palette (from neon) would apply regardless of order.
One solution is to regularize your themes such that each themes sets all properties set by any theme.
So I'm working on a website for a radio station and have encountered an issue. The range input we use for volume literally refuses to change its background color and height. I'm using SASS (which makes it a scss file type) for the purposes of this project.
I've done some research and have tried a few things the Internet recommended, but unfortunately, none have worked. I have tried using background, background-color and even color fields to try and fix my issue. I have made sure to overwrite everything that might be stopping me from doing so by setting the -webkit-appearance to none.
This is the content of the slider and its parent elements in my hbs file:
<div class="rootplayer">
<div>
<img class="picture" alt='Artist art' src="{{songart}}" width="170px">
<div class="playerinfo">
<p>On air: {{dj}}</p>
<hr>
<p>Up next: AJS Show</p>
<hr>
<p>Currently playing: {{songTitle}}</p>
<hr>
<p>Current Listeners: {{currentListeners}}</p>
</div>
<audio controls="" id="player">
<source src="http://radio.nowhits.uk:8000/radio.mp3" type="audio/mpeg">
<p>Your browser does not support the audio element.</p>
</audio>
</div>
<div class="playercontrols">
<i class="fas fa-play" onclick="play()" id="play"></i>
<i class="fas fa-pause" onclick="pause()" id="pause"></i>
<input type="range" min="0" max="1" value="0.5" step="0.01" class="slider" id="volume">
<script src="scripts/volume.js"></script>
<script src="scripts/playpause.js"></script>
</div>
</div>
And this is the content of the slider object in my scss file:
.slider {
-webkit-appearance: none;
height: 1px;
width: 65%;
background-color: red;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
cursor: pointer;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 12px;
height: 12px;
background: #000;
border-radius: 50%;
}
.slider::-moz-range-thumb {
width: 12px;
height: 12px;
background: #000;
cursor: pointer;
border-radius: 50%;
}
Obviously, the expected output should be a red background and a height of 1px (should be a pretty thin line), but instead, I get this.
Try to add this :
.slider::-webkit-slider-runnable-track {
background: red;
}
.slider::-moz-range-track {
background: red;
}
.slider::-ms-track {
background: red;
}
Look at this site for more customizations : http://danielstern.ca/range.css
I understand that I can change another element's style when hovering on a different element like this:
.element-one:hover .element-two {
opacity: 0.8;
}
But how can I change the style of all the elements in the page except element-two when I hover on element-one?
You can use .element-one:hover :not(.element-two).
Here is an example:
.element-one:hover :not(.element-two) {
opacity: 0.8;
}
.element-one {
background: black;
margin: 10px;
}
.element-one div {
background: green;
border: 1px solid blue;
margin: 10px;
padding: 10px;
}
<div class="element-one">
<div class="element-two">
element-two
</div>
<div class="element-three">
element-three
</div>
<div class="element-four">
element-four
</div>
</div>
However - note that it will work only for elements inside element-one and not for all the elements in the page.
You can do this with body for example, but the problem there is that .element-two is probably also inside some other element that exists inside body, and in such case the .element-two will get the opacity from it's containing element.
I have problems with the placement of the button and the paragraph/text. They are placed in a staggered way. I want them to be placed on a straight horizontal line. You can see the problem on the image below.
Further more, I want the button to be placed right above the end of the "hr" line, the same way as the paragraph is placed above the left side of the line. Any idea for a solution?
Live Demo
HTML:
<!--Wrapper div-->
<div id="wrapper">
<!--Inbox list and button to add a card-->
<div id="inboxList" class="cellContainer">
<br style="line-height: 23px" />
<p class="paragraph">In-Box</p>
<!--Button to add a Card-->
<div id="btnAddCard" style="float: right;"><span
style="color: #10e20e; font-size:140%" >+</span> Add Card...</div>
<hr class="fancy-line" />
<br />
<!--Card div-->
<div id="userAddedCard"> <br/>
<div>
</div>
</div>
</div>
CSS:
#btnAddCard {
background: #ffffff;
padding: 0.7% 2% 1% 2%; /* Button padding */
font-size: 95%;
font-weight: bold;
display: inline;
text-decoration: none; /* Remove default underline style from hyperlink */
color: #888888;
border: 1px solid #cccccc;
border-radius: 7px; /* Full rounder corners */
font-family: Arial, sans-serif;
cursor: pointer;
vertical-align: bottom;
}
#btnAddCard {
background: rgb(255,255,255); /* Old browsers */
background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,rgba(229,229,229,1) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#e5e5e5',GradientType=0 ); /* IE6-9 */
}
.paragraph {
padding-left:7%;
display:inline;
font-family:cursive;
font:bolder;
font-size:larger;
}
hr.fancy-line {
height: 1.8px;
background-color: blueviolet;
width:85%;
}
Changing p.paragraph to <div.paragraph> and Adding margin-left:7% and removing the font-size of 140% from #btnAddCard seems to have fixed the problem
JSFiddle
Update:
I'd simply put the contents in a div, give it proper margin and highlight it's bottom border instead of the <hr> so that the children will align perfectly, something like this JSFiddle
Side note: please avoid using inline-styles and tags such as <br> <center> etc, it makes maintenance easier Why Use CSS # MDN
In my php page dynamically visualize the thumbnails. To make sure that these are all of the same size I do in this way
<a class="zoom" href="...">
<img src="thumb/default.png" width="130" style="background-image:url(thumb/<?php echo $images_jpg;?>);" class="centered" />
</a>
CSS
img.centered {
background-repeat: no-repeat;
background-position: center center;
}
/* 1 attempt */
a.zoom:hover {
background-image: url(thumb/zoom.jpg);
}
/* 2 attempt */
a.zoom img:hover {
background-image: url(thumb/zoom.jpg);
}
I would like to display a different image on event: hover, but this does not work. How could I do that? thanks
You could always do it like this.
HTML:
<div class="image" style="background-image:url(http://www.randomwebsite.com/images/head.jpg);">
<div class="overlay"></div>
</div>
CSS:
.image {
width: 200px;
height: 200px;
border: 1px solid;
}
.overlay {
width: 100%;
height: 100%;
}
.overlay:hover {
background: url(http://1.bp.blogspot.com/-lJWLTzn8Zgw/T_D4aeKvD9I/AAAAAAAACnM/SnupcVnAsNk/s1600/Random-wallpapers-random-5549791-1280-800.jpg);
}
So here we have the image you are getting via PHP on top as a div. And inside we have the overlay, the image you want when a user is hovering. So we set that to 100% width and height so it takes up all of the parent div and set the hover.
DEMO HERE
In your example the <img> always lays over the <a> background-image.
To avoid that, you could hide the image on hover. But that is kinda ugly ;)
a.zoom:hover {
background-image: url(thumb/zoom.jpg);
}
a.zoom:hover img
{
opacitiy: 0;
}
try this
<img class="centered" src="thumb/default.png"/>
and jquery
$(".centered").attr("src","second.jpg");