DEV Community

Cover image for Dynamic CSS in Svelte
Daniel Imfeld
Daniel Imfeld

Posted on • Originally published at imfeld.dev on

Dynamic CSS in Svelte

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>

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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);
}

Enter fullscreen mode Exit fullscreen mode

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();
}

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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.

Latest comments (2)

Collapse
 
csaltos profile image
Carlos Saltos

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

Collapse
 
dimfeld profile image
Daniel Imfeld • Edited

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!