A common request from Svelte newcomers is how to use template expressions in a <style>
block. This usually comes up in the context of custom themes.
<!-- If Svelte allowed it, you might do something like this -->
<style>
.note {
color: {noteColor || '#000000'};
background-color: {noteBgColor};
}
.note::before {
content: "{notePrefix} || 'A note'";
margin-right: {notePrefixSpacing};
}
</style>
The problem is that Svelte only compiles template syntax in the HTML section of the component. The style
section of a component does not use templates, so this method can not be used to generate styles at runtime.
But there is a very good alternative.
CSS Variables 🔗
CSS has native support for defining values at runtime. Finalized in 2015, CSS variables let you create style rules that depend on values defined up the element tree from where they are applied.
<style>
.note {
color: var(--note-color, var(--note-other-color, tomato));
background-color: var(--note-bg-color, lightgray);
}
</style>
<p class="note">This is important!</p>
CSS variables always start with --
. The var
function gets the value of a variable, and falls back to the second argument (the value after the comma) if the variable is not set. This fallback value can also be a variable reference.
In this example an element with the class note
will use the variables --note-color
and --note-bg-color
if they are set. The color
attribute will fall back to --note-other-color
if --note-color
is not set, and finally fall back to the color tomato
, while the background-color
will use lightgray
if --note-bg-color
is not set.
Of course, variables aren’t very useful if we don’t set them. Variables can be set either as part of other classes or in the style
attributes of elements.
<style>
.note {
color: var(--note-color, tomato);
background-color: var(--note-bg-color, lightgray);
}
.yellow-theme {
--note-color: black;
--note-bg-color: khaki;
}
.purple-note-text {
--note-color: rebeccapurple;
}
</style>
<div style="--note-color:green;--note-bg-color:tomato">
<h1 class="note" style="font-weight:600">Christmas!</h1>
<div class="yellow-theme">
<p class="note">For that yellow notepad look.</p>
<p class="note purple-note-text">Or with purple</p>
</div>
</div>
When Variables Apply 🔗
Although variables apply to the element that sets them and all of its children, any classes or styles that use variables only read those variables at the point where they are set.
Here, the div
element gets the note
class, and the span
sets --note-color:green
. But setting the variable there does not modify how the class applies, and so the text appears as the default color tomato
instead of green
.
<style>
.note { color: var(--note-color, tomato); }
</style>
<div class="note">
<span style="--note-color:green">Not Green!</span>
</div>
The larger example below is similar to the first one, but the note
class is applied only on the topmost element, and so the CSS variables set in the child elements do not take effect. To use the note
class with the new values, it needs to be applied again at or below where the variables are reassigned.
<style>
.note {
color: var(--note-color, tomato);
background-color: var(--note-bg-color, lightgray);
}
.yellow-theme {
--note-color: black;
--note-bg-color: khaki;
}
.purple-note-text {
--note-color: rebeccapurple;
}
</style>
<!-- Although the note class applies to all the child elements, setting
the CSS variables in child elements does not apply unless we reapply the note class again. -->
<div class="note" style="--note-color:green;--note-bg-color:tomato">
<h1 style="font-weight:600">Christmas!</h1>
<div class="yellow-theme">
<p>For that yellow notepad look.</p>
<p class="purple-note-text" style="--note-bg-color:white">Or with purple</p>
</div>
</div>
Setting CSS Variables through Svelte 🔗
As established, we can’t use Svelte to modify the rules inside a <style>
tag, but we can generate element attributes in the template. And so this gives us the ability to set CSS variables using Svelte template syntax, e.g. <span style="--note-color:{noteColor}">
.
This Svelte REPL example sets variables dynamically.
Fallbacks for Internet Explorer 🔗
It’s important to note that CSS variables are not supported in Internet Explorer, so if you need to support it, your stylesheets should provide reasonable fallback defaults.
.classname {
color: red;
color: var(--color, red);
}
With this pair of rules, Internet Explorer will see the first rule and set the color to red. It will then ignore the second color rule since it’s unable to understand the syntax.
Newer browsers will parse both rules, but the second one will take precedence. Note that the first rule is ignored completely, even if there is no value for the --color
variable, so the red
fallback must still be present at the end of the var
clause if you want to provide a default value.
Global CSS Variables 🔗
Sometimes CSS variables need to be globally scoped. The easiest solution is to have a top-level div
in your application that contains all the CSS variables, and use the techniques above to set the variables on that div
.
If for some reason this is not possible, you can set your variables directly on the <html>
element with syntax like this: $: document.documentElement.style.cssText = styles
.
The original example used the
<body>
element. Thanks to Kevv on Twitter for asking about this since it works with the:root
selector as well.
When you have multiple places that need to write CSS variables in this way, they should be written using a global manager instead so that all the variable settings will be combined properly.
A simple manager like this can combine style settings from multiple sources. Here’s a full example: Svelte REPL Document Styles.
const cssVars = new Map();
function refresh() {
let values = [];
for(let [key, value] of cssVars) {
values.push(`--${key}:${value}`);
}
document.documentElement.style.cssText = values.join(';');
}
export function set(name, value) {
cssVars.set(name, value);
refresh();
}
export function del(name) {
cssVars.delete(name);
refresh();
}
The various components in the application can then use this single module to manage the global CSS variables, and so long as the variable names are unique, everything will work.
Multiple Classes 🔗
If the styles required fall into a finite and reasonably small set of values, you don’t even need to use CSS variables. Svelte makes it easy to enable classes on an element based on some expression.
<style>
.green {
color: white;
background-color: green;
}
.red: {
color: black;
background-color: red;
}
</style>
<script>
let isRed = false;
$: className = isRed ? 'red' : 'green';
</script>
<label><input type="checkbox" bind:value={isRed} /> Red?</label>
<!-- Svelte's built-in class: syntax -->
<div class:red={isRed} class:green={!isRed}>Text</div>
<!-- Or using template syntax in the class attribute -->
<div class={className}>Text</div>
Creating Rules from Scratch 🔗
Browsers also support creating rules from scratch with the CSS Object Model (CSSOM), also known as CSS-in-JS. I don’t recommend using this approach with Svelte, since you lose the component class scoping behavior of Svelte and most of the packages that use CSSOM spend a lot of effort recreating behaviors that Svelte provides natively.
Also, most of the CSSOM helper libraries are specifically linked to React or other frameworks, but some, such as Aphrodite, can work independently. So it is an option if you really want to go that way.
Further Reading 🔗
My palette transformer project (source) is a small example of dynamically setting the theme for an entire site using CSS variables.
Once you get comfortable with CSS variables, you may also want to check out other ways to upgrade your CSS knowledge. The CSS Tricks blog is a great place to start.
Top comments (2)
Interesting hack !! ... but what about using an Svelte preprocessor with SCSS !! ... that will have also variables ... and also mixins, imports, extends and other cool stuff and for using with Svelte ... check it out -> github.com/sveltejs/svelte-preprocess
Yeah, I use PostCSS for those purposes along with Tailwind CSS. Definitely makes things more convenient when you can define things in one place and use them throughout the application.
CSS Variables like I described in this article are most useful when you can't predict the values needed at build time. I have a couple examples of this embedded in the page on the version of this post on my actual website or you can also check it out on this Svelte REPL example.
Here's another cool example not by me, where CSS variables are used to selectively change the style on certain elements of a design: lea.verou.me/2020/07/the-cicada-pr...
Thanks for reading!