What I Like About Writing Styles with Svelte
There’s been a lot of well-deserved hype around Svelte recently, with the project accumulating over 24,000 GitHub stars. Arguably the simplest JavaScript framework out there, Svelte was written by Rich Harris, the developer behind Rollup. There’s a lot to like about Svelte (performance, built-in state management, writing proper markup rather than JSX), but the big draw for me has been its approach to CSS.
Single file components
??
React does not have an opinion about how styles are defined
—React Documentation?
?
????
A UI framework that doesn’t have a built-in way to add styles to your components is unfinished.
—Rich Harris, creator of Svelte
?
In Svelte, you can write CSS in a stylesheet like you normally would on a typical project. You can also use CSS-in-JS solutions, like styled-components and Emotion, if you’d like. It’s become increasingly common to divide code into components, rather than by file type. React, for example, allows for the collocation of a components markup and JavaScript. In Svelte, this is taken one logical step further: the Javascript, markup and styling for a component can all exist together in a single `.svelte`? file. If you’ve ever used single file components in Vue, then Svelte will look familiar.
// button.svelte
<style>
button {
border-radius: 0;
background-color: aqua;
}
</style>
<button>
<slot/>
</button>
Styles are scoped by default
By default, styles defined within a Svelte file are scoped. Like CSS-in-JS libraries or CSS Modules, Svelte generates unique class names when it compiles to make sure the styles for one element never conflict with styles from another.
That means you can use simple element selectors like div
and button
in a Svelte component file without needing to work with class names. If we go back to the button styles in our earlier example, we know that a ruleset for will only be applied to our
component — not to any other HTML button elements within the page. If you were to have multiple buttons within a component and wanted to style them differently, you’d still need classes. Classes will also be scoped by Svelte.
The classes that Svelte generates look like gibberish because they are based on a hash of the component styles (e.g. svelte-433xyz
). This is far easier than a naming convention like BEM. Admittedly though, the experience of looking at styles in DevTools is slightly worse as the class names lack meaning.
It’s not an either/or situation. You can use Svelte’s scoped styling along with a regular stylesheet. I personally write component specific styles within .svelte
files, but make use of utility classes defined in a stylesheet. For global styles to be available across an entire app — CSS custom properties, reusable CSS animations, utility classes, any ‘reset’ styles, or a CSS framework like Bootstrap — I suggest putting them in a stylesheet linked in the head of your HTML document.
It lets us create global styles
As we’ve just seen, you can use a regular stylesheet to define global styles. Should you need to define any global styles from within a Svelte component, you can do that too by using :global
. This is essentially a way to opt out of scoping when and where you need to.
For example, a modal component may want to toggle a class to style the body element:
<style>
:global(.noscroll) {
overflow: hidden;
}
</style>
Unused styles are flagged
Another benefit of Svelte is that it will alert you about any unused styles during compilation. In other words, it searches for places where styles are defined but never used in the markup.
Conditional classes are terse and effortless
If the JavaScript variable name and the class name is the same, the syntax is incredibly terse. In this example, I’m creating modifier props for a full-width button and a ghost button.
<script>
export let big = false;
export let ghost = false;
</script>
<style>
.big {
font-size: 20px;
display: block;
width: 100%;
}
.ghost {
background-color: transparent;
border: solid currentColor 2px;
}
</style>
<button class:big class:ghost>
<slot/>
</button>
A class of ghost
will be applied to the element when a ghost
prop is used, and a class of big
is applied when a big
prop is used.
<script>
import Button from './Button.svelte';
</script>
<Button big ghost>Click Me</Button>
Svelte doesn’t require class names and prop names to be identical.
<script>
export let primary = false;
export let secondary = false;
</script>
<button
class:c-btn--primary={primary}
class:c-btn--secondary={secondary}
class="c-btn">
<slot></slot>
</button>
The above button component will always have a c-btn
class but will include modifier classes only when the relevant prop is passed in, like this:
<Button primary>Click Me</Button>
That will generate this markup:
<button class="c-btn c-btn--primary">Click Me</button>
Any number of arbitrary classes can be passed to a component with a single prop:
<script>
let class_name = '';
export { class_name as class };
</script>
<button class="c-btn {class_name}">
<slot />
</button>
Then, classes can be used much the same way you would with HTML markup:
<Button class="mt40">Click Me</Button>
From BEM to Svelte
Let’s see how much easier Svelte makes writing styles compared to a standard CSS naming convention. Here’s a simple component coded up using BEM.
.c-card {
border-radius: 3px;
border: solid 2px;
}
.c-card__title {
text-transform: uppercase;
}
.c-card__text {
color: gray;
}
.c-card--featured {
border-color: gold;
}
Using BEM, classes get long and ugly. In Svelte, things are a lot simpler.
<style>
div {
border-radius: 3px;
border: solid 2px;
}
h2 {
text-transform: uppercase;
}
p {
color: gray;
}
.featured {
border-color: gold;
}
</style>
<div class:featured>
<h2>{title}</h2>
<p>
<slot />
</p>
</div>
It plays well with preprocessors
CSS preprocessors feels a lot less necessary when working with Svelte, but they can work perfectly alongside one another by making use of a package called Svelte Preprocess. Support is available for Less, Stylus and PostCSS, but here we’ll look at Sass. The first thing we need to do is to install some dependencies:
npm install -D svelte-preprocess node-sass
Then we need to import autoPreprocess in rollup.config.js
at the top of the file.
import autoPreprocess from 'svelte-preprocess';
Next, let’s find the plugins array and add preprocess: autoPreprocess()
to Svelte:
export default {
plugins: [
svelte({
preprocess: autoPreprocess(),
...other stuff
Then all we need to do is specify that we’re using Sass when we’re working in a component file, using type="text/scss"
or lang="scss"
to the style tag.
<style type="text/scss">
$pink: rgb(200, 0, 220);
p {
color: black;
span {
color: $pink;
}
}
</style>
Dynamic values without a runtime
We’ve seen that Svelte comes with most of the benefits of CSS-in-JS out-of-the-box — but without external dependencies! However, there’s one thing that third-party libraries can do that Svelte simply can’t: use JavaScript variables in CSS.
The following code is not valid and will not work:
<script>
export let cols = 4;
</script>
<style>
ul {
display: grid;
width: 100%;
grid-column-gap: 16px;
grid-row-gap: 16px;
grid-template-columns: repeat({cols}, 1fr);
}
</style>
<ul>
<slot />
</ul>
We can, however, achieve similar functionality by using CSS variables.
<script>
export let cols = 4;
</script>
<style>
ul {
display: grid;
width: 100%;
grid-column-gap: 16px;
grid-row-gap: 16px;
grid-template-columns: repeat(var(--columns), 1fr);
}
</style>
<ul style="--columns:{cols}">
<slot />
</ul>
I’ve written CSS in all kinds of different ways over the years: Sass, Shadow DOM, CSS-in-JS, BEM, atomic CSS and PostCSS. Svelte offers the most intuitive, approachable and user-friendly styling API. If you want to read more about this topic then check out the aptly titled The Zen of Just Writing CSS by Rich Harris.
The post What I Like About Writing Styles with Svelte appeared first on CSS-Tricks.