Combinators for multiple ::slotted elements - css

I am interested if there's a way to achieve something like the following:
::slotted(input[type="checkbox"]:disabled) ~ ::slotted(label) {
cursor: not-allowed;
}
By testing it on some of the examples, it does not work.
Specification does not describe if this should be possible or not. MDN does not cover this case as well.
I do not want to enclose neither input nor label inside the shadow-dom as I do not want to handle and/or duplicate the native behavior of those elements.
P.S. I know that I can do that with javascript (for example, by adding class to slotted label), but I'm looking for a plain css solution.
Full example:
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
::slotted(input:disabled) ~ ::slotted(label) {
color: red;
}
::slotted(input:disabled) + ::slotted(label) {
color: red;
}
</style>
<slot name="inputel"></slot>
<slot name="inputlabel"></slot>`;
}
});
</script>
<my-element>
<input disabled id="input1" type="text" slot="inputel"/>
<label for="input1" slot="inputlabel">label</label>
</my-element>

The full detailed explanation is at: ::slotted CSS selector for nested children in shadowDOM slot
<my-element>
<input disabled id="input1" type="text" slot="inputel"/>
<label for="input1" slot="inputlabel">label</label>
</my-element>
ShadowDOM SLOTs turn off the light
The input and label are in lightDOM,
and remain there invisible when reflected (not moved!) to shadowDOM SLOTs
So you have to style them in lightDOM:
<style>
/* style lightDOM!! */
my-element input:disabled ~ label {
color: red;
}
</style>
<template id="MY-ELEMENT">
<style>
::slotted(input:disabled){
border:1px dashed red;
}
</style>
INPUT <slot name="inputElement"></slot> <slot name="inputLabel"></slot>
</template>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'})
.append(document.getElementById(this.nodeName).content.cloneNode(true));
}
});
</script>
<style>
/* style lightDOM!! */
my-element input:disabled ~ label {
color: red;
font-weight: bold;
}
</style>
<my-element>
<input disabled id="inp1" placeholder="This is disabled" type="text" slot="inputElement"/>
<label for="inp1" slot="inputLabel">input label 1</label>
</my-element>
<br>
<my-element>
<input id="inp2" placeholder="Not disabled" type="text" slot="inputElement"/>
<label for="inp2" slot="inputLabel">input label 2</label>
</my-element>
This use case is a bit contrived, there is nothing gained with my-element, user still has to declare input and label
Maybe create a <input-element type="text" label="A Label"> to create above HTML, you then have the disabled CSS in its shadowDOM (no need for SLOTs)
or (pseudo code)
<template id="INPUT-ELEMENT">
<style>
input:disabled~label{
color:red;
}
</style>
<input id="i"/>
<label for="i"><slot name="label"></slot></label>
</template>
...
connectedCallback(){
let inp=this.shadowRoot.querySelector("input");
inp.type = this.getAttribute("type");
inp.toggleAttribute( "disabled" , this.hasAttribute("disabled"));
}
<input-element type="text" disabled>
<span slot="label"><b>Fancy Label<b></slot>
</input-element>
if you want to give the user more control
::slotted came got into WebComponents V1 spec after its predecessor caused performance issues in V0
Thus it only takes simple selectors as parameter, and can only style first-level elements in the slot
So in above example ::slotted can style the <span slot="label">, but not the <b> inside.
Note: SLOTs are LIVE connections, you can re-apply CSS at any time, and content changes are immediately reflected to shadowDOM:
document.querySelector("input-element > b").innerHTML="Better Label!";
More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs

Related

React : set style of a children based on the prop of a parent

I have below code
<span className="item-toggle" onClick={toggleChecked}>
<Checkbox toggle checked={checked} data-togglecolor={toggleColor}/>
</span>
That renders to
<span class="item-toggle">
<div class="ui checked fitted toggle checkbox" data-togglecolor="#9cd3dd">
<input class="hidden" readonly="" tabindex="0" type="checkbox" value="" checked="">
<label></label>
</div>
</span>
The Checkbox component is part of the Semantic UI React. I would like to use the value of the data-togglecolor to style my input :
.ui.toggle.checkbox input:checked ~ label:before {
background-color: XXX;
}
I could ofcourse hardcode it like below in my CSS
.ui.toggle.checkbox[data-togglecolor="#9cd3dd"] input:checked ~ label:before {
background-color: #9cd3dd;
}
But I was wondering how I could achieve this dynamically.
Thanks !
Do you use styled-components ?
If so, you could override the Checkbox component to pass the background-colour to its child, just like this :
const Checkbox = styled(ImportedCheckbox)`
input {
background-color: ${p => p['data-togglecolor']};
}
`
You can't access props in your CSS files unless you used something like styled-components or something related, what you can do is add a style attribute to your checkbox
<div class="ui checked fitted toggle checkbox" style={{backgroundColor: this.props.yourcolorprophere}}></div>

CSS change label background of another element, when radio button is checked

I want to change the background color of the label class 'label-status', depending on if the radio button is set to agree or disagree.
<div class="testclass">
<label class="label-status">Status</label>
<label class="radio-inline" style="display:inline-block;"><input type="radio" name="status" disabled="">Agree</label>
<label class="radio-inline" style="display:inline-block;"><input type="radio" name="status" checked="" disabled="">Disagree</label>
</div>
Since it's not my website, but some custom user CSS I want to inject to make it more usable, I cannot make any changes to the actual html.
Thanks for your answers in advance.
You can take advantage of the order property (Flexbox or Grid) together with the for attribute to link the label with the related input element:
.testclass {display: flex} /* displays flex-items (children) inline; can also use the "inline-flex" which only takes the content's width */
.label-status {order: -1} /* puts it back to the desired place (above other siblings); the initial value is set to 0 */
.testclass > input:first-of-type:checked ~ .label-status {background: green}
.testclass > input:last-of-type:checked ~ .label-status {background: red}
<div class="testclass">
<input type="radio" name="status" id="agree">
<label for="agree">Agree</label>
<input type="radio" name="status" id="disagree" checked>
<label for="disagree">Disagree</label>
<label class="label-status">Status</label> <!-- needs to be placed below other siblings in order to take advantage of the "~" selector -->
</div>
Then you can use the general sibling combinator ~ to target the .label-status with e.g. :first-of-type & :last-of-type selectors set on the input elements, of course in conjunction with the :checked pseudo-class selector.
You could use JS, like so:
$(document).ready(function() {
$(".label-status").on("click",function() {
if($(this).find('input[type="radio"]').is(':checked')) {
$('.radio-inline').removeClass('sel_bk_color');
$(this).addClass('sel_bk_color');
}
});
});
Here is an example of this kind of thing in action: https://jsfiddle.net/tr9Lyxz3/1/
By default your inputs are disabled. you have to first remove disable property and then add onclick attributes to run a function which will change background color.
document.getElementsByName('status')[0].removeAttribute("disabled")
document.getElementsByName('status')[1].removeAttribute("disabled")
document.getElementsByClassName('radio-inline')[0].setAttribute("onclick","myFunction()")
document.getElementsByClassName('radio-inline')[1].setAttribute("onclick","myFunction()")
function myFunction(){
if (document.getElementsByName('status')[1].checked) {
document.getElementsByClassName("label-status")[0].classList.remove("myclass2")
document.getElementsByClassName("label-status")[0].classList.add("myclass1");
}
else if (document.getElementsByName('status')[0].checked) {
document.getElementsByClassName("label-status")[0].classList.remove("myclass1")
document.getElementsByClassName("label-status")[0].classList.add("myclass2");
}
}
you have to define your css for myclass1 and myclass2 as per your background color requirement.
this is not possible using pure css as you cant read values dynamically using css. so if you can use js then this will solve your problem without editing html.
Some pure CSS solutions seem to miss the you are not allowed to change the HTML.
Are you allowed to use javascript?
Here is some javascript without JQuery:
var updateLabel = function(e) {
var label = document.getElementsByClassName("label-status")[0];
if (e.target !== e.currentTarget && event.target.type === "radio") {
if(e.target.labels[0].innerText === "Agree") {
label.style.background = "green"
} else {
label.style.background = "red";
}
}
e.stopPropagation();
}
var wrapperElement = document.getElementsByClassName("testclass")[0];
wrapperElement.addEventListener("click", updateLabel, false);
https://codepen.io/anon/pen/MXWJXK
You do have to remove the disabled attribute from your radio buttons. With javascript you can use element.removeAttribute("disabled").
https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute
.buttons {
display: flex
align-item: center;
}
.condition {
padding-right: 50px;
}
.status {
order: -1;
padding: 5px;
border: 2px solid #000;
color: #fff;
}
.buttons > input:first-of-type:checked ~ .status {
background: green;
}
.buttons > input:last-of-type:checked ~ .status {
background: red;
}
<div class="buttons">
<input type="radio" name="status" id="agree">
<label for="agree" class="condition">Agree</label>
<input type="radio" name="status" id="disagree" checked>
<label for="disagree" class="condition">Disagree</label>
<label class="status">Status</label>
</div>

CSS Parent Class over other CSS Classes

My Code Looks something like this:
input{
...
}
label{
...
}
<div class="textfield 1">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
<div class="textfield 2">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
<div class="textfield 3">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
now i want to apply the css only on one of the textfields and because the code is way to long to ad a ".textfield1" to every css element i want to ask if i can create a "parent class element" like:
.textfield1{
input{
...
}
label{
...
}
}
.textfield2{
input{
...
}
label{
...
}
}
It's like putting the styled elements in a Folder.
Is there a way to do that?
Thanks a lot in advance!
You can use:
.textfield1 input {
...
}
.textfield1 label {
...
}
Check this link for more CSS selectors combinations: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
As Turnip mentions in comments, you cannot have spaces in classnames. So instead of having class names such as textfield 1, you could have them like textfield1 - or something else. For the time being, I am using textfield1 to demonstrate the solution.
Now, you could use the descendant selector .textfield1 input (notice the space between the class name and tag name) or the child selector .textfield2 > input (notice the arrow > between the class name and tag name) to specify that given CSS rule must apply only to the descendants or children of given class.
input{
border: 2px solid blue;
}
label{
color: blue;
}
.textfield1 input {
border: 2px solid red;
}
.textfield2 > input {
border: 2px solid yellow;
}
<div class="textfield1">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
<div class="textfield2">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
<div class="textfield3">
<input tpye="text" id="fullname">
<label for="fullname">Name</label>
</div>
If you use preprocessor like Sass or Less, you can nasted css like your example. It's not possible in the classical way.
In css:
.textfield2 input{
...
}
.textfield1 input{
...
}
.textfield1 label, //<- if the label style of textfield1 and textefield2 are same
.textfield2 label{
...
}
Be careful, in your HTML you have a space between textfield and the number <div class="textfield 3">. That's mean your div has the CSS class textfield and the CSS class 3.
If you just want one class remove the space and the code above works.
If you keep the space, just modify .textfield2 to .textfield.2 with a dot between textfield and the number (to indicate that the style it's for the class textfield with the class 2)

CSS/SASS previous child selection

I'm trying to make a span background change colors when I focus on an input field. The HTML is as follows:
<div class='parentDiv'>
<span class='spanClass'>Some text</span>
<input class='inputClass' type='text' />
</div>
The closest I could come to something that does this is using the + adjacent sibling selector and doing something like this:
input:focus + span {
background-color: red;
}
But it doesn't quite work because span must come after input. Is there some way for me to make the span background change colors when I focus the input field?
Normally, you would need JS to do that. Here's an example using JS that keeps styling in your CSS:
(function() {
var spanEl = document.querySelector('.parentDiv > .spanClass');
var inputEl = document.querySelector('.parentDiv > .inputClass');
// Add "highlighted" class to "spanClass" element on focus event
inputEl.addEventListener('focus', function() {
spanEl.classList.add('highlighted');
});
// Remove "highlighted" class from "spanClass" element on blur event (un-focus)
inputEl.addEventListener('blur', function() {
spanEl.classList.remove('highlighted');
});
})();
.spanClass.highlighted {
background-color: red;
}
<div class="parentDiv">
<span class="spanClass">Some text</span>
<input class="inputClass" type="text" />
</div>
In your example, though, you could simply float the one element to the left and change the order in the HTML.
.parentDiv { overflow: hidden; }
.spanClass { float: left; }
.inputClass:focus + .spanClass {
background-color: red;
}
<div class="parentDiv">
<input class="inputClass" type="text" />
<span class="spanClass">Some text</span>
</div>
Something to note for the future, though:
The :has() "relational pseudo-class" seems to be in the works for "CSS4". You can also track it here: http://caniuse.com/#feat=css-has
This means that you will (hopefully) be able to do this eventually:
.spanClass:has(+ .inputClass) {
background-color: red;
}

I am trying to target/select elements in a different parent with CSS

Take a look at the code below...
As you can see the 'HAZEL(NUT)' and 'HASSEL(NØD)' has a different parent to the checkbox. Which is why I think the checkbox works for the font-weight part, but doesn't work for selecting #hazelnut or #hasselnod. If anyone could help me with the correct selector for #hazelnut and #hasselnod I would be very grateful.
Hope this is clear, I'm quite a newbie to HTML and CSS, so have trouble explaining what I mean sometimes!
HTML here:
<div class="container" id="lang">
<input type="radio" id="english" name="language" value="english" checked="checked" />
<input type="radio" id="dansk" name="language" value="dansk" />
<ul>
<label for="english"><li id="en">ENGLISH</li></label>
<label for="dansk"><li id="dk">DANSK</li></label>
</ul>
</div>
<div class="container" id="myname">
<h1 id="hazelnut">HAZEL<br>(NUT)</h1>
<h1 id="hasselnod">HASSEL<br>(NØD)</h1>
</div>
CSS here:
#dansk:checked ~ * #dk {
font-weight: 700;
}
#dansk:checked ~ * #en {
cursor: pointer;
}
#dansk:checked * #hazelnut {
display: none;
}
#english:checked ~ * #en {
font-weight: 700;
}
#english:checked ~ * #dk {
cursor: pointer;
}
#english:checked * #hasselnod {
display: none;
}
Many thanks!
In CSS, for the ~ selector to work, the elements must have the same parent. As I see it, I'm afraid you'll have to involve some javascript in here.
What I'd do, is have the radio buttons change a data attribute of #lang, so it would be transparent to the css:
<div id="lang" data-value="en">
and then use the following css rules:
/*when #myname is preceded by #lang with data-value attribute 'en',
select direct child #hasselnod */
#lang[data-value='en'] ~ #myname > #hasselnod {
/* and set its display to none*/
display: none;
}
Now, we'll need the javascript to change the data-value attribute of #lang. Look at the onclick function in the following snippet:
<input type="radio" id="dansk" name="language" value="dansk"
onclick="this.parentNode.setAttribute('data-value', 'da')" />
Check out this fiddle: http://jsfiddle.net/wkL7q/2/
To target elements in a different parent, I think you need to use jQuery:
$("#lang input").change(function () {
var lang = $(this).val();
if(lang == 'dansk') {
$('#hazelnut').hide();
$('#hasselnod').show();
} else {
$('#hazelnut').show();
$('#hasselnod').hide();
}
});
Check out this fiddle: http://jsfiddle.net/X3ZtK/

Resources