I'm using a template from another developer made in SCSS and vue.js to develop online courses. All colors are using the SCSS variable $primary and $secondary. I was thinking of inserting a color-blind option that would cycle through complementary color for the Normal, Deuteranopia and Tritanopia spectrums.
I'm not finish with the design yet, just a prototype, but it'd be a circles in a dropdown that would update the primary color of the entire page on click.
I'm not exactly sure how to write it as i'm not too familiar with vue and scss, but it would change the variable of $primary: " "; by another variable from the choice selected. For exemple:
$(".changePrimaryColor").click(function(){
$primary == $theme-blue;
});
I thought of also incrementing the variable of $primary by 1 until 2 or 3, but not sure.
Any lead would help, thank you!
// Theme colors
$theme-green: #c4d600;
$theme-blue: #1a397c;
$theme-red:#9b1717;
$primary: $theme-green;
$secondary: $theme-red;
$success: #288515;
$danger: #a61616;
$warning: #efa92c;
$theme-colors: (
"primary": $primary,
"secondary": $secondary,
"success": $danger,
"danger": $success,
"warning": $warning,
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.9.2/sass.min.js"></script>
Unfortunately scss can't do this (but it would be awesome). This is because scss compiles to normal css. So your scss variables do not exist anymore when the clients loads the page.
Fortunately you can use css properties / variables instead, which do re-render!
https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
This means that something like changing colours is pretty simply!
const button = document.querySelector('button')
function changeColor() {
document.body.style.setProperty("--bg-color", 'DodgerBlue');
}
button.addEventListener('click', changeColor)
:root {
--bg-color: tomato;
}
body {
background: var(--bg-color);
}
<button>Change color</button>
Related
I am developing a library of react components and asked an important question. How to correctly change styles (css) when changing the application theme.
Appeal to those who already have experience in this and who can advise their approach or solution.
Now I'll tell you how it works for me:
I have my own themeProvider wrapped in a Context with the ability to change the theme.
When the theme changes in the provider, the changeCssVariables method is called.
export const changeCssVariables = (theme) => {
const root = document.querySelector(':root');
// root.style.setProperty('--default-color', 'orange');
const cssVariables = [
'color',
'background',
'color-uibutton',
'background-uibutton',
'box-shadow-uibutton',
'color-disabled-uibutton',
'background-disabled-uibutton',
'background-uiradiobutton',
'color-uiinputext',
'color-notes-uiinputext',
'color-subsection',
'background-subsection',
'box-shadow-subsection',
'alt-subsection',
'on-subsection',
'off-subsection',
];
cssVariables.forEach(element => {
root.style.setProperty(
`--default-${element}`,
`var(--theme-${theme}-${element})`
);
})
}
What happens in general: it has a global CSS with variables default, light and dark themes. When changing the theme, a method is called that changes the default variable to the variable of the selected theme.
Example css:
--default-color: var(--theme-light-color);
/* Themes */
--theme-light-color: #000;
--theme-dark-color: #fff;
I don't like that you have to pull in all the css and change it this way. What is the solution?
Thanks for the help!
Found a solution in using styled-components package
Created my wrapper as ThemeProvider (Context)
I have the following css which is loaded into my project:
// Default theme (light mode)
:root {
/* Typography */
--col-body-text: #0b0c0c;
--col-body-text-light: #505a5f;
}
// Dark mode theme
:root.dark {
/* Typography */
--col-body-text: #c5c5c5;
--col-body-text-light: #f8f8f8;
}
In my actual app this works as expected, however, in storybook, it ignores the dark mode variables.
I have updated my preview.js file to add '.dark' to the `HTML element when dark mode is selected - which works as expected - indeed all of the other dark mode specific code in the components works fine. It's only those variables that are being ignored.
Is there an issue with using :root in storybook that I'm not aware of or something?
if it helps, here is the code that adds the class to the HTML element:
// get an instance to the communication channel for the manager and preview
const channel = addons.getChannel()
// switch body class for story along with interface theme
channel.on('DARK_MODE', isDark => {
if (isDark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
})
If you place such stylesheet in the HTML page (e.g. in the <head>), the :root selector refers to <html> (https://developer.mozilla.org/en-US/docs/Web/CSS/:root). If you want to use the :root selector with a class, you need to set the class on the <html> element (rather than on <body> suggested in another answer), so document.documentElement.classList.add('dark') is correct.
There is a playground which uses the Storybook syntax and features where I created a working example for you: https://webcomponents.dev/edit/p8TI3583HotsFNWjBMd8/src/index.stories.js
Not sure if your Storybook is configured in another manner, maybe your stylesheet is not really added or gets overwritten somewhere later. Please also verify if you use the CSS Custom Properties (aka CSS vars) correctly, I hope the working demo helps there too.
try
CSS -> body.dark {
instead of :root.dark {
and
channel.on('DARK_MODE', isDark => document.body.classList.toggle('dark', isDark))
I have a shadow component in stenciljs like
#Component({
tag: "digital-verification",
styleUrl: "digital-verification.scss",
shadow: true,
})
and I have some css variable like
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
Till now everything is working perfect and no issue.
Now I want to set this variables in the code like
document.querySelector(':root').style.setProperty('--app-primary-color', '#ffffff');
I tried
document.querySelector(':host').style
and
document.querySelector(':root').style
and
document.querySelector(':root').shadowRoot.style
and
document.querySelector(':root').root.style
and
#Element() el;
...
this.el.shadowRoot.querySelector(":host") // this is null
but I get compile error that Element doesn't have style
Wondering how should I see css variables in the code.
update
I did below code, and I can see value is set in console, but application color doesn't change
document.documentElement.style.setProperty("--app-primary-color", "#ff0000");
console.log(document.documentElement.style.getPropertyValue("--app-primary-color"));
In case somebody else has same issue
I did the below steps and issue is resolved
1- I deleted the below section
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
2- defined a variable like below
$primary-color : var(--app-primary-color, #488aff);
3- in code set the css variable like below
document.documentElement.style.setProperty("--app-primary-color", "#ffff00" , "important");
Updated solution based on #Danny '365CSI' Engelman comment
1- You can delete the below section if you want to variable be applied to all children or keep it if you want it to be applied only on this component
:host {
--app-primary-color: #488aff;
--app-border-radius: 10px;
--app-error-color: #EE320C;
}
2- define #Element() el: HTMLElement; in your component
3- in code set the css variable like below
this.el.style.setProperty("--app-primary-color", "#ffff00");
I am searching a way to styling shadow DOM from the outside. For example, I would like to set the color of all text in all 'span.special' elements as RED. Including 'span.special' elements from shadow DOM. How I can do this?
Previously there were ::shadow pseudo-element and /deep/ combinator aka >>> for this purpose. So I could write something like
span.special, *::shadow span.special {
color: red
}
But now ::shadow, /deep/ and >>> are deprecated. So, what do we have as a replacement of them?
I did try many methods, including those described here. Since I'm using an external Web Component lib, I don't have access to modify these components. So, the only solution that worked for me was using JS querySelector, like this:
document.querySelector("the-element.with-shadow-dom")
.shadowRoot.querySelector(".some-selector").setAttribute("style", "color: black");
Not the best solution, not suitable for large stylings, but does work for little enchancements.
#John this was tested with Chrome 83.0.4103.116 (still going to test in Safari) and I did for Ionic (v5) ion-toast component. Here is the (almost) real code I used:
import { toastController } from '#ionic/core';
let toastOpts = {
message: "Some message goes here.",
cssClass: "toast-with-vertical-buttons",
buttons: [
{
text: "Button 1",
side: 'end'
},
{
text: "Button2",
side: 'end'
},
{
icon: "close",
side: "start"
}
]
}
toastController.create(toastOpts).then(async p => {
let toast = await p.present(); // this renders ion-toast component and returns HTMLIonToastElement
toast.shadowRoot.querySelector('div.toast-button-group-end').setAttribute("style", "flex-direction: column");
});
There is still no easy way to pierce through the shadow root, but here are 3 ways you can go about it. Just keep in mind that you will need to make changes inside the web component.
Using variables v1 - You will need to pass the property and consume the variable inside the web component.
Using variables v2 - You will need to consume the variable inside the web component.
Using ::part() - You will need to add a part attribute to the element you want to style in the web component. (Note: this pseudo element is well supported but is still in experimental mode, so make sure you're aware of that before using it in production).
Run code sample below for details.
const elA = document.querySelector('custom-container-a');
const shadowRootA = elA.attachShadow({mode:'open'});
shadowRootA.innerHTML = '<style>:host([border]) {display:block;border: var(--custom-border);}</style>'+
'<p>Shadow content A</p>'
const elB = document.querySelector('custom-container-b');
const shadowRootB = elB.attachShadow({mode:'open'});
shadowRootB.innerHTML = '<style>p {display:block;color: var(--custom-color, blue);}</style>'+
'<p>Shadow content B</p>'
const elC = document.querySelector('custom-container-c');
const shadowRootC = elC.attachShadow({mode:'open'});
shadowRootC.innerHTML = '<p part="paragraph">Shadow content C</p>'
/* Normal way of styling */
p {
color: orange;
}
/* Using variables version 1 */
custom-container-a {
--custom-border: 3px solid gold;
}
/* Using variables version 2 */
custom-container-b {
--custom-color: green;
}
/* Using ::part() */
custom-container-c::part(paragraph) {
color: magenta;
}
<p>Light content</p>
<custom-container-a border></custom-container-a>
<custom-container-b></custom-container-b>
<custom-container-c></custom-container-c>
You could use #import css as explained in this answer to another question on SO.
Include the rule inside the style element in the shadow tree.
<style>
#import url( '/css/external-styles.css' )
</style>
Note that the >>> combinator is still part of the CSS Scoping Module Draft.
Well, #import is not a solution if you are working with library web component that you can't change ...
Finally I found several ways to do it:
1) Cascading. Styles of Shadow DOM's host element affect Shadow DOM elements also. Not an option if you need to style a particular element of the Shadow DOM, not every.
2) Custom properties https://www.polymer-project.org/1.0/docs/devguide/styling
If an author of the web component provided such.
3) In Polymer, the have Custom Mixins also https://www.polymer-project.org/1.0/docs/devguide/styling
4) #import, but only for not-library components
So, there are several possibilities, but all of them are limited. No powerful enough way to outside styling as ::shadow were.
I have been long battling this, and I would like to know if any others have feedback. I am about to make a customized library for building web apps quickly, and I want to make sure I use the right approach. I WANT to use this method:
$.fn.someSlider = function(){
var coreStyle = '.slider ul { white-space: nowrap; } .slider ul li {display: inline-block}', coreStyleTemplate = '<style><\/style>';
}
But I feel like hard coding the base CSS into the widget is always frowned upon - instead I see SO users recommending the use of CSS style rules instead of this option. I really really really want that 'it just works' feel, and having to force my users to use a separate style sheet just to get my plugins working... well is annoying!
Just to clarify: I would like to include all base style rules needed for the widgets proper/base functionality to be included inside the script. The user would easily modify the base look of the widget by writing a style rule in their own style sheet.
Example:
Instead of having to look through all the base styles trying to find the font color like this... .slider {display: inline-block; color: #000; someotherconfusingrule : blahblah; }
The user simply starts a new rule with the classes name/selector being used - and then just write the changes to make to the default script styles
They would just write
.slider {color: #000};
Thanks for the help in advance SO!
Nice question! Although I'm not sure what the preferred solution to this would be, I was thinking of the following approach:
Use a IIFE to define your jQuery plugin and enable you to define some private, global variables and functions.
$.fn.pluginName = (function() {
return function() {
...your regular plugins code...
};
}();
Define your plugins CSS as a list of style rules in your plugins code
var rules = [
'.box {' +
' width: 100px;' +
' background-color: #f99;' +
' margin: 10px;' +
' padding: 10px;' +
' font-family: Helvetica, Arial;' +
' text-align: center;' +
'}'
];
Create a private variable that remembers if your stylesheet has already been added to the document
var styleSheetExists = false;
Create a private function that creates a stylesheet using the style rules above and that adds it as the first <style> element in the <head> allowing the user to override styles in their own CSS. See http://davidwalsh.name/add-rules-stylesheets for a good tutorial on how to do this properly
var createStyleSheet = function() {
var style = document.createElement("style");
style.appendChild(document.createTextNode(""));
$('head').prepend(style);
for (var i = 0; i < rules.length; i++) {
style.sheet.insertRule(rules[i], i);
}
};
The first time your plugin is applied to an element check if the stylesheet has already been created and if not create the stylesheet.
var $elements = $(this);
if (!styleSheetExists) {
createStyleSheet();
styleSheetExists = true;
}
$elements.each(function() {
$(this).addClass('box');
});
return $elements;
See http://codepen.io/ckuijjer/pen/FkgsJ for this example. It creates a jQuery plugin called box which simply adds the class box to an element. The class box has a default pink background color defined in its stylesheet which gets overridden by a user defined blue background color.
But please do make this configurable in your jQuery plugin. You want to enable developers to bundle all their css, including your plugins, to optimize resource delivery to the client. Plus injecting stylesheets might be a small performance hit.
It may seem annoying but separating the model, view, and controller is the correct way. You're using jQuery so why not consider how jQuery would approach the situation: a jQuery UI widget like the Accordion comes with several stylesheets, the most important being the base stylesheet and a separate 'theme' stylesheet that (if done correctly) is nondestructive and can be modified without risking the integrity of the widget. You may also want to consider how your favorite plugins are authored and what makes them appeal to you. It's my personal opinion CSS should never be present in JavaScript files however if you've made up your mind, the solution #ckuijjer provided is sound. Hope this helps!