I'm making a site using Svelte and SvelteKit, and I'd like to use a predefined array of colors for styling in a normalized way-- if I change one of the values later, everywhere that styles using that value will be updated later.
// palette.js
export const grays = [
'hsl(240 3.7% 10.6%)',
'hsl(240 13% 18%)',
...
];
// routes/+page.svelte
<script>
import { grays } from '$lib/palette.js';
</script>
<div style="background-color: {grays[0]};">
<i>stuff</i>
</div>
My problem is I can't figure out which is the best way to apply one of my colors to <body>
.
Setting CSS values in a <style>
tag like this:
<style>
:global(body) {
background-color: {grays[0]};
}
</style>
is not valid syntax for Svelte (and using :global
rules generally seems to be unidiomatic).
My current options (that I can think of) seem to be:
document.body.style.backgroundColor
in a <script>
-- might cause flickering?<body>
at all, style a big <div>
with a custom CSS property that covers everything (as shown in the Svlte tutorial)-- probably my best option but seems a bit ugly / smelly / boilerplatey to do all the var(...)
and style="--customvar={grays[0]};
.What should I do? Am I overthinking this?
You can set CSS variables on the <html>
element/:root
and they will automatically be inherited to the rest of the application. A good place is the style of the root layout (src/routes/+layout.svelte
) or a stylesheet imported from said layout.
<!-- +layout.svelte --->
<slot />
<style>
:global(html) {
--gray-1: ...;
--gray-2: ...;
--gray-3: ...;
/* ... */
background-color: var(--gray-1);
}
</style>
<!-- +layout.svelte --->
<script>
import './site.css';
</script>
<slot />
/* site.css */
html {
--gray-1: ...;
--gray-2: ...;
--gray-3: ...;
/* ... */
background-color: var(--gray-1);
}
If for some reason the source of truth has to be some piece of JS, the most reliable way is probably to inject the values on build. I would still work with CSS variables, though.
You could e.g. generate the required CSS from the palette file via a custom Vite plugin.
/** @import { PluginOption } from 'vite' */
/**
* A plugin that allows importing a generated CSS file from `@palette`.
* @param {Record<string, string[]>} definitions
* Palette prefixes and the values for each palette.
* @returns {PluginOption} The resulting plugin object.
*/
export function palettes(definitions) {
const prefixedId = '/@palette/variables.css';
return {
name: 'palette-injector',
resolveId(source) {
if (source == '@palette')
return prefixedId;
},
load(id) {
if (id != prefixedId)
return null;
const css = Object.entries(definitions)
.flatMap(([prefix, values]) =>
values.map((value, i) => `--${prefix}-${i + 1}: ${value};`)
)
.join('\n');
return {
code: `:root { ${css} }`,
map: null,
};
},
}
}
// vite.config.js
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { palettes } from './palettes-plugin';
import { grays } from './src/lib/palette';
export default defineConfig((cfg) => ({
// ...
plugins: [
palettes({
gray: grays,
}),
sveltekit(),
],
}));
<!-- +layout.svelte --->
<script>
import '@palette';
</script>
<slot />
<style>
:global(html) {
background-color: var(--gray-1);
}
</style>