Using vanilla-extract with SvelteKit: Styles with TypeScript
Tags: svelte, webdev, typescript, css
Published: 2021-10-27 10:12:12

---
title: "Using vanilla-extract with SvelteKit: Styles with TypeScript"
published: "true"
description: "Using vanilla-extract with SvelteKit: how you can make zero-runtime stylesheets with the new TypeScript friendly preprocessor in Svelte."
tags: "svelte, webdev, typescript, css"
canonical_url: "https://rodneylab.com/using-vanilla-extract-sveltekit/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlgttgsls6md0l44rewa.png"
---

## 🧑🏽‍🍳 What is vanilla-extract?
	
In this post we look at how to get started using vanilla-extract with SvelteKit.  Before we get going though, we should spend a moment looking at what vanilla-extract is and why you might want to use in a project or at least try it out.

vanilla-extract is a modern <a aria-label="Read more on C S S on M D N" href="https://developer.mozilla.org/en-US/docs/Web/CSS">Cascading Style Sheets (CSS)</a> preprocessor, which generates plain CSS stylesheets.  It is TypeScript friendly and leans on TypeScript to add some extra useful features.  You can create your style in TypeScript files which sit alongside your Svelte code and import the styles, adding them as classes to your components.  The way you use them is similar to CSS modules usage pattern.

We'll get a proper introduction shortly, but here's a sneak peak as a teaser.  Here we define some style in our `index.css.ts` file:

```typescript
export const heading = style({
	fontSize: [fontSize5],
});
```

then consume it it a Svelte file:

```html
<script lang="ts">
  import { heading } from './index.css';
<script>
 
<h1 class={heading}>Example Heading</h1>
```

The CSS is generated at compile time, meaning there is no overhead at runtime (when the user opens up the page).  Having TypeScript support allows you create **styling policies** and ensure they are enforced by your team (or even by you when you return to the code in six months' time).

We will look at an example of theming our site.  In doing so, we will create a **theme contract** (this is quite straightforward) and just means if we decide to add a Christmas theme in a month we can be fairly comfortable it won't break the site as long as we define the colours etc, required by the theme.


## 😕 How is vanilla-extract different to vanilla CSS, Sass, Tailwind etc.?

I first heard about vanilla-extract on the JS Party podcast.  They had a <a aria-label="Open J S Party podcase on Replacing Sass at Shopify" href="https://changelog.com/jsparty/190">show on the process Shopify used for replacing Sass</a>.  I won't tell you what they opted for, in case you want to listen to the show!  Anyway <a aria-label="" href="https://docs.google.com/spreadsheets/d/1rxrRTlbNWiLVu-Q5IK7xh5O1FmWcjyAS2XN7jiPrhYM/edit#gid=0">Shopify generated a grid of the pros and cons for each solution</a>, comparing Sass, CSS, Tailwind and other frameworks.  If you are looking at switching CSS tooling, definitely consider adapting that grid to your own use case to help inform your decision process.  On top there is a <a aria-label="Open Shopify Git Hub discussion on replacing Sass" href="https://github.com/Shopify/foundational-design-system-proto/discussions/44">top notch GitHub discussion on replacing Sass too</a>.  Although titled &ldquo;Replacing Sass&rdquo;, the process was open and keeping Sass was a possible solution.  Hopefully those resources answer any questions you have on the relative merits of the different solutions.

## 🌟 Why use vanilla-extract for styling?

I like vanilla-extract for a few things, probably the most important ones to me are:

- it is modern with wide support &mdash; although we focus on SvelteKit here, <a aria-label="Read about setting up vanilla-extract with different tooling" href="https://github.com/seek-oss/vanilla-extract/#setup">vanilla-extract works with ESBuild, Vite and Snowpack as well as Next.js and other React site generators</a>,
- you create efficient CSS files with zero runtime overhead,
- contracts offer a built-in way of enforcing styles, and preventing issues like colour drift over time.  This is where, for example, you might hardcode a colour in CSS while debugging or checking contrast ratio and forget to swap it out for a variable.


## 🧱 What we're Building

We'll knock up a minimal Single Page App advertising a couple of courses for a fictitious event.  We will:

- see how to configure vanilla-extract for SvelteKit,
- create a couple of themes,
- add a contract for new themes so they need to have all our colour variables defined.


![Using vanilla-extract with SvelteKit: What we're Building: Summer Theme: Screenshot shows a summary of a course with the title and a picture of the speaker.  Above is a button labelled Summer theme.  The sites shows bright colours invoking a summer feel.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34fy072xdwnzeghxaygi.png)

![Using vanilla-extract with SvelteKit: What we're Building: Winter Theme: Screenshot shows a summary of a course with the title and a picture of the speaker.  Above is a button labelled Summer theme.  The sites shows muted colours invoking a winter feel.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebpsrbqkrb5q26wnyt9q.png)

As well as seeing the vanilla-extract features in action we will see how to store the chosen theme in the browser local storage and a little bit of animations in SvelteKit.  If there's anything there you can get excited about then let's get started.

## ⚙️ Using vanilla-extract with SvelteKit: Getting Started

Create a new SvelteKit skeleton TypeScript project:

```shell
pnpm init svelte@next sveltekit-vanilla-extract && cd $_
pnpm install
```
From the options, choose **Skeleton project**, **Use TypeScript:? Yes**, **Add ESLint...? Yes** and **Add Prettier...? Yes**.  Next we install the vanilla-extract plugins and some other packages we will use later, then start up the dev server:

```shell
pnpm install -D @vanilla-extract/css @vanilla-extract/css-utils @vanilla-extract/vite-plugin
pnpm install -D @fontsource/source-sans-pro @fontsource/source-serif-pro sharp vanilla-lazyload vite-imagetools @sveltejs/adapter-static@next
pnpm run dev
```

We will use TypeScript in this tutorial to get the most out of vanilla-extract.  We only use minimal and basic TypeScript so, hopefully you can follow along, even if you are more comfortable with JavaScript.

## 🔨 Using vanilla-extract with SvelteKit: SvelteKit Setup for vanilla-extract

Next we will add vanilla-extract configuration to `svelte.config.js`.  Thanks to Ben Jervis from the vanilla-extract maintainer team for helping to get this config working.  vanilla-extract is not yet (at time of writing) a 100%  ESM compatible package.  Without the `vite/ssr` config (below) you can currently run a site in dev mode though to build, you will need the extra parameters.  Update `svelte.config.js` to get started:

```javascript
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { imagetools } from 'vite-imagetools';
 
/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: preprocess(),
 
  kit: {
    adapter: adapter(),
    // hydrate the <div id="svelte"> element in src/app.html
    target: '#svelte',
    vite: {
      define: {
        'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString()),
      },
      plugins: [vanillaExtractPlugin(), imagetools({ force: true })],
      ssr:
        process.env.NODE_ENV === 'development'
          ? {}
          : {
              noExternal: ['@vanilla-extract/css', '@vanilla-extract/css/fileScope'],
            },
    },
  },
};
 
export default config;
```

In line `20` we are added the `imagetools` plugin as well as `vanillaExtractPlugin()`.  The former is not strictly needed for running vanilla-extract though we will use it on this site to help with processing images.

The extra `vite/ssr` parameters (lines `21`&ndash;`26`) needed for building the site seem to break the dev site so we have a switch to change the config based on whether we have run `pnpm run dev` or `pnpm run build`.  If you are quite new to SvelteKit, I should explain, `pnpm run dev` lets you develop the site, with fast, hot reloading so when you change something it is updated straight away in your browser. Each time you integrate a new feature, it is a good idea to run `pnpm run build` to generate a production-ready site and then `pnpm run preview` to view that production-ready version of your site.  Hopefully that makes the first part of the paragraph clearer!

### Extra setup

For type consistency add the Lazyload type to the browser document (this is the `src/global.t.ds` in your project):

```typescript
/// <reference types="@sveltejs/kit" />
import type { ILazyLoadInstance } from 'vanilla-lazyload';
 
export declare global {
  interface Document {
    lazyloadInstance: ILazyLoadInstance;
  }
}
```

As a last bit of setup, download a couple of images (<a aria-label="Download Blake image" href="https://github.com/rodneylab/sveltekit-vanilla-extract/raw/main/src/lib/assets/blake.jpg">blake.jpg</a> and <a aria-label="Download River image" href="https://github.com/rodneylab/sveltekit-vanilla-extract/raw/main/src/lib/assets/river.jpg">river.jpg</a>) which we will use on the site.  Place these in a new `src/lib/assets` folder.  Finally, create a JSON file containing event details as `src/lib/data` below.  Next, we will add our global vanilla-extract styles and variables.

```json
[
  {
    "speaker": "Blake Shakespeare",
    "title": "Moving your React site to SvelteKit",
    "date": "Friday, 6pm",
    "abstract": "Learn from my experience in transitioning my site from React to SvelteKit: what I got right, what I would do differently going forward.",
    "profileImageBase64": "data:image/jpeg;base64,/9j/2wBDAAwREhMUFBUUFBofGhUaHiIcGhohKC4jJB4hMzg/PTouKC5CRFpMPi5XRTc3VmBRVlpgZmRkQklveXFmeFtlaWf/2wBDAQgMDQwNDw8PDxFgEBIaVmBaYFpgY2NjY2BjYGBiY2NjY2NjY2NjY2NjY2JjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAwQFBv/EABQBAQAAAAAAAAAAAAAAAAAAAAX/2gAMAwEAAhADEAAAACwt2yY//8QAHxABAAEDBAMAAAAAAAAAAAAAAQACAxMREkGBUVJi/9oACAEBAAE/AG1VgA2jUaJ9czBf9ZSGa9096TaeCf/EABkRAAEFAAAAAAAAAAAAAAAAAAEAAgMSIv/aAAgBAgEBPwBsRNtL/8QAGREAAgMBAAAAAAAAAAAAAAAAAhEAARQx/9oACAEDAQE/ANZhbXZ//9k="
  },
  {
    "speaker": "River Costa",
    "title": "Building out a GraphQL API in Svelte using Prisma and Supabase",
    "date": "Saturday, 7pm",
    "abstract": "See just how easy it can be to write an API using a modern framework and tooling.  I take you through step by step from pnpm init to production ready product. The future is SvelteKit!",
    "profileImageBase64": "data:image/jpeg;base64,/9j/2wBDAAwREhMUFBUUFBofGhUaHiIcGhohKC4jJB4hMzg/PTouKC5CRFpMPi5XRTc3VmBRVlpgZmRkQklveXFmeFtlaWf/2wBDAQgMDQwNDw8PDxFgEBIaVmBaYFpgY2NjY2BjYGBiY2NjY2NjY2NjY2NjY2JjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAABgT/xAAVAQEBAAAAAAAAAAAAAAAAAAAFBv/aAAwDAQACEAMQAAAAoWEU8u//AP/EAB4QAAICAgIDAAAAAAAAAAAAAAECAxEABAUQIlHR/9oACAEBAAE/AN2bbSeSQNQUqqIl+S/cD2AaIvNtmHIRi/XX/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAECAxL/2gAIAQIBAT8Ask3k/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQIAMf/aAAgBAwEBPwBVIl4b/9k="
  }
]
```

Learn more about <a aria-label="Open video on importing J S O N in Svelte Kit" href="https://rodneylab.com/sveltekit-json-import/">importing JSON in SvelteKit in the video on SvelteKit JSON data import</a>.

## 💄  Vanilla Extract Styles

Let's create a styles folder and add some vanilla-extract to our project.  Create a new directory at `src/lib.styles`.  In there create `vars.css.ts` and `styles.css.ts` and paste in the following content:

```typescript
import { createVar } from '@vanilla-extract/css';
 
export const desktopBreakpoint = createVar();
 
export const fontFamilyHeading = createVar();
export const fontFamilyBody = createVar();
 
export const fontSizeRoot = createVar();
export const fontSize0 = createVar();
export const fontSize1 = createVar();
export const fontSize2 = createVar();
export const fontSize3 = createVar();
export const fontSize4 = createVar();
export const fontSize5 = createVar();
export const fontSize6 = createVar();
export const fontSize7 = createVar();
 
export const fontWeightBlack = createVar();
export const fontWeightBold = createVar();
 
export const lineHeightRelaxed = createVar();
 
export const spacingPx = createVar();
export const spacing0 = createVar();
export const spacing1 = createVar();
export const spacing2 = createVar();
export const spacing3 = createVar();
export const spacing4 = createVar();
export const spacing5 = createVar();
export const spacing6 = createVar();
export const spacing8 = createVar();
export const spacing12 = createVar();
```

```typescript
import {
  desktopBreakpoint,
  fontFamilyBody,
  fontFamilyHeading,
  fontSize0,
  fontSize1,
  fontSize2,
  fontSize3,
  fontSize4,
  fontSize5,
  fontSize6,
  fontSize7,
  fontSizeRoot,
  fontWeightBlack,
  fontWeightBold,
  lineHeightRelaxed,
  spacing0,
  spacing1,
  spacing12,
  spacing2,
  spacing3,
  spacing4,
  spacing5,
  spacing6,
  spacing8,
  spacingPx,
} from '$lib/styles/vars.css';
import { globalFontFace, globalStyle } from '@vanilla-extract/css';
 
globalFontFace('HeadingFont', {
  src: 'local("Source Serif Pro")',
});
 
globalStyle('html, body', {
  vars: {
    [desktopBreakpoint]: '48rem',
    [fontFamilyHeading]: 'Source Serif Pro',
    [fontFamilyBody]: 'Source Sans Pro',
 
    [fontSizeRoot]: '16px',
    [fontSize0]: '0.8rem',
    [fontSize1]: '1rem',
    [fontSize2]: '1.25rem',
    [fontSize3]: '1.563rem',
    [fontSize4]: '1.953rem',
    [fontSize5]: '2.441rem',
    [fontSize6]: '3.052rem',
    [fontSize7]: '3.815rem',
 
    [fontWeightBold]: '700',
    [fontWeightBlack]: '900',
 
    [lineHeightRelaxed]: '1.75',
 
    [spacingPx]: '1px',
    [spacing0]: '0',
    [spacing1]: '0.25rem',
    [spacing2]: '0.5rem',
    [spacing3]: '0.75rem',
    [spacing4]: '1rem',
    [spacing5]: '1.25rem',
    [spacing6]: '1.5rem',
    [spacing8]: '2.0rem',
    [spacing12]: '3.0rem',
  },
 
  fontFamily: [fontFamilyBody],
});
 
globalStyle('body', {
  margin: [spacing0],
});
 
globalStyle('h1', {
  fontFamily: [fontFamilyHeading],
});
 
globalStyle('p', {
  fontFamily: [fontFamilyBody],
});
 
globalStyle('button', {
  cursor: 'pointer',
  padding: [spacing2, spacing4],
  fontSize: [fontSize2],
  fontFamily: [fontFamilyHeading],
  fontWeight: [fontWeightBlack],
  lineHeight: [lineHeightRelaxed],
  borderStyle: 'none',
  borderRadius: [spacing2],
  '@media': {
    '(prefers-reduced-motion: no-preference)': {
      transition: ['color', '250ms'],
    },
    '(prefers-reduced-motion: reduce)': {
      transition: ['color', '2000ms'],
    },
  },
});
```

In the first file you see how you can declare variables in vanilla-extract.  Using variables is handy when working with a design system and lets you easily change styles across the project quickly.  Later we will use these variables when styling our Svelte components.  The second file contains our global styles.  In there we define the variables (give them values) and also set some other global CSS like font families, much like you would in CSS or Sass.

### Using vanilla-extract with SvelteKit: Theming

We're only going to scratch the surface on what vanilla-extract can do with theming here.  We will create a theme template which will act as our **theme contract**.  We then use that template to create a summer and winter colour theme.  Create a `src/lib/styles/themes` folder and in there create `theme.css.ts` and paste the content below.  This will be our template.

```typescript
import { createTheme } from '@vanilla-extract/css';
 
export const [theme, themeVars] = createTheme({
  colour: {
    primary: '#f00',
    secondary: '#f00',
    alternative: '#f00',
    light: '#f00',
    dark: '#f00',
  },
});
```

We are only varying colours by theme, but in a fully-fleshed out project, you might add fonts, accessible styling and a whole host of other elements.  We will see in a moment when we create a actual themes, we will get a TypeScript error if we forget to define one of the theme colours.  This can be great when working in teams or on long-term projects making it harder to break the site by adding new themes.

Let's create the summer and winter themes in the same folder:

```typescript
import { createTheme } from '@vanilla-extract/css';
import { themeVars } from './theme.css';
 
export const summerTheme = createTheme(themeVars, {
  colour: {
    primary: '#d11c5b', // rubine red
    secondary: '#fac600', // mikado yellow
    alternative: '#ea4b1d', // flame
    light: '#f6eed5', // eggshell
    dark: '#32373b', // onyx
  },
});
```

```typescript
import { createTheme } from '@vanilla-extract/css';
import { themeVars } from './theme.css';
 
export const winterTheme = createTheme(themeVars, {
  colour: {
    primary: '#f56a79', // ultra red
    secondary: '#1aa6b7', // pacific blue
    alternative: '#ff414d', // red salsa
    light: '#f6eed5', // eggshell
    dark: '#002a32', // gunmetal
  },
});
```

Try commenting out one of lines where you define a colour in the `winterTheme.css.ts` file.  You should get a warning in your code editor that a colour is missing.  This is one of the advantages TypeScript brings to the party.  Next we will create some components and use these themes.

## 🏠 Home Page Styles

With vanilla-extract, we can define styles in a TypeScript file which sits alongside the page or component they will be used in. Then we import them into the Svelte file.  Let's create `src/routes/index.css.ts` which will contain the styles needed for the home page:

```typescript
import { themeVars } from '$lib/styles/themes/theme.css';
import {
  fontFamilyHeading,
  fontSize2,
  fontSize4,
  fontSize5,
  fontWeightBold,
  lineHeightRelaxed,
  spacing0,
  spacing2,
  spacing4,
  spacing6,
} from '$lib/styles/vars.css';
import { style } from '@vanilla-extract/css';
import { calc } from '@vanilla-extract/css-utils';
 
export const main = style({
  background: themeVars.colour.primary,
  color: themeVars.colour.dark,
});
 
export const heading = style({
  fontSize: [fontSize5],
  minHeight: calc(lineHeightRelaxed).multiply(fontSize4).multiply(2).toString(),
  marginBottom: [spacing0],
  textAlign: 'center',
});
 
export const speakerName = style({
  marginTop: [spacing4],
  marginBottom: [spacing6],
  fontSize: [fontSize4],
  fontFamily: [fontFamilyHeading],
  fontWeight: [fontWeightBold],
});
 
export const abstractText = style({
  borderRadius: [spacing2],
  fontSize: [fontSize2],
  lineHeight: [lineHeightRelaxed],
  minHeight: calc(lineHeightRelaxed).multiply(fontSize2).multiply(4).toString(),
  marginTop: [spacing4],
  marginBottom: [spacing6],
  padding: [spacing2, spacing4],
  backgroundColor: themeVars.colour.dark,
  color: themeVars.colour.secondary,
});
 
export const dateText = style({
  fontSize: [fontSize2],
  fontWeight: [fontWeightBold],
  marginBottom: [spacing4],
});
 
export const button = style({
  borderStyle: 'solid',
  fontSize: fontSize4,
  background: themeVars.colour.dark,
  borderColor: themeVars.colour.light,
  color: themeVars.colour.secondary,
  ':focus': {
    color: themeVars.colour.light,
  },
  ':hover': {
    color: themeVars.colour.light,
  },
});
```

We see in line `1` that we import the template theme we just created.  Then throughout the file, we reference this.  In our svelte component, we will add the class for the current theme. vanilla-extract will generate the all the CSS we need for handling themes. 

You will notice we are importing and using variables defined in our `styles` folder. In line `41` you can see an example of using our existing CSS variables to calculate the `minHeight` for a text element.


### Store

We will use the browser local storage to keep track of which theme the user prefers.  Svelte stores will provide an interface between our components and local storage.  Make a store by creating `src/lib/shared/stores/theme.ts` and adding the following content:

```typescript
import { browser } from '$app/env';
import { writable } from 'svelte/store';
 
export const theme = writable<string>(
  browser ? window.localStorage.getItem('theme') || 'summer' : 'summer',
);
 
theme.subscribe((value) => {
  if (browser) {
    window.localStorage.setItem('theme', value);
  }
});
```

In line `5` we set the default value for the store, which is used when the page first loads.  It checks if there is a value already stored in the local storage for theme and defaults to summer. The code in line `10` runs when our components update the store and sets the theme to the value passed in.


## 🧩 Using vanilla-extract with SvelteKit: Components

Before we add the markup for the home page, we will add some components which get used in the home page.  Naturally, these components will also use vanilla-extract so we will continue learning on the way. Create a components folder at `src/lib/components` and add `Card.css.ts` file with the following content:

```typescript
import { themeVars } from '$lib/styles/themes/theme.css';
import { spacing3, spacing4, spacing6, spacing8 } from '$lib/styles/vars.css';
import { style } from '@vanilla-extract/css';
 
export const container = style({
  display: 'flex',
  width: '100%',
});
 
export const content = style({
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  width: '60%',
  borderRadius: [spacing3],
  margin: [spacing6, 'auto'],
  padding: [spacing4, spacing8],
  '@media': {
    '(max-width: 768px )': {
      width: '100%',
    },
  },
  background: themeVars.colour.secondary,
  borderStyle: 'solid',
  borderColor: themeVars.colour.light,
});
```

Now we can create `Card.svelte` which will import these styles:

```typescript
<script lang="ts">
  import { theme } from '$lib/shared/stores/theme';
  import { summerTheme } from '$lib/styles/themes/summerTheme.css';
  import { winterTheme } from '$lib/styles/themes/winterTheme.css';
  import { container, content } from './Card.css';
 
  $: themeIsSummer = $theme === 'summer';
  $: contentStyle = `${content}  ${themeIsSummer ? summerTheme : winterTheme}`;
</script>
 
<section class={container}>
  <div class={contentStyle}>
    <slot />
  </div>
</section>
```

This lets us see a few things. In line `7` we can access the store with `$theme` (we imported the file in line `2`).  All we do here is check what the theme is.  We use the store as a central source of truth, accessible in components. This means we don't have to pass the theme as a prop between components.  We imported styles from the `Card` style file in line `5`.  As an example, in line `11`, you add the `container` class variable to the `section` element.  vanilla-extract does the magic to convert this to a class with a unique name and also generate the CSS.

The last interesting line here is line `8` where we add the right class variable depending on the current theme.  We imported the themes in lines `3` & `4`. 

### Speaker Picture Component

Let's pick up the pace now and paste in a couple of files for pictures we use.  We add the responsive image boiler plate in full here, though for a larger project, it is probably <a aria-label="See how you can use an image component to cut down on responsive image boilerplate code" href="https://rodneylab.com/sveltekit-image-plugin/#component">worth using an image component to help</a>.

```typescript
import { themeVars } from '$lib/styles/themes/theme.css';
import { spacing1 } from '$lib/styles/vars.css';
import { style } from '@vanilla-extract/css';
 
export const image = style({
  borderRadius: '50%',
  borderStyle: 'solid',
  borderWidth: [spacing1],
  borderColor: themeVars.colour.alternative,
});
```

```html
<script lang="ts">
  import { browser } from '$app/env';
  import { image } from '$lib/components/SpeakerPicture.css';
  import { theme } from '$lib/shared/stores/theme';
  import { summerTheme } from '$lib/styles/themes/summerTheme.css';
  import { winterTheme } from '$lib/styles/themes/winterTheme.css';
  import { onMount } from 'svelte';
 
  export let alt: string;
  export let placeholder: string;
  export let src: string;
  export let srcset: string;
  export let srcsetWebp: string;
 
  onMount(() => {
    if (browser) {
      document.lazyloadInstance.update();
    }
  });
 
  $: themeIsSummer = $theme === 'summer';
  $: imageStyle = `${image} ${themeIsSummer ? summerTheme : winterTheme}`;
 
  const sizes = '200px;';
</script>
 
<picture>
  <source
    data-sizes="{sizes},"
    data-srcset={srcsetWebp}
    type="image/webp"
    width="200"
    height="200"
  />
  <source data-sizes="{sizes}," data-srcset={srcset} type="image/jpeg" width="200" height="200" />
  <img
    class={`lazy ${imageStyle}`}
    {alt}
    loading="eager"
    decoding="async"
    width="200"
    height="200"
    data-src={src}
    \src={placeholder}
  />
</picture>
```

If anything here needs more explanation let me know and I can update the post.


## Home Page ❤️ Svelte

We're back home!  Replace the content in `src/routes/index.svelte`:

```typescript
<script lang="ts">
  import { browser } from '$app/env';
  import blakeSrc from '$lib/assets/blake.jpg?w=200';
  import blakeSrcsetWebp from '$lib/assets/blake.jpg?w=400;200&format=webp&srcset';
  import blakeSrcset from '$lib/assets/blake.jpg?w=400;200&srcset';
  import riverSrc from '$lib/assets/river.jpg?w=200';
  import riverSrcsetWebp from '$lib/assets/river.jpg?w=400;200&format=webp&srcset';
  import riverSrcset from '$lib/assets/river.jpg?w=400;200&srcset';
  import Card from '$lib/components/Card.svelte';
  import SpeakerPicture from '$lib/components/SpeakerPicture.svelte';
  import talks from '$lib/data/talks.json';
  import { theme } from '$lib/shared/stores/theme';
  import '$lib/styles/styles.css';
  import { summerTheme } from '$lib/styles/themes/summerTheme.css';
  import { winterTheme } from '$lib/styles/themes/winterTheme.css';
  import '@fontsource/source-sans-pro/400.css';
  import '@fontsource/source-serif-pro/400.css';
  import { sineInOut } from 'svelte/easing';
  import { fade, fly } from 'svelte/transition';
  import { abstractText, button, dateText, heading, main, speakerName } from './index.css';
 
  const sources = [blakeSrc, riverSrc];
  const sourceSets = [blakeSrcset, riverSrcset];
  const sourceSetsWebp = [blakeSrcsetWebp, riverSrcsetWebp];
 
  $: themeIsSummer = $theme === 'summer';
  $: currentTheme = themeIsSummer ? summerTheme : winterTheme;
  $: abstractTextStyle = `${abstractText} ${currentTheme}`;
  $: buttonStyle = `${button} ${currentTheme}`;
  $: mainStyle = `${main} ${themeIsSummer ? summerTheme : winterTheme}`;
  $: currentIndex = 0;
  $: alt = `Picture of ${talks[currentIndex].speaker}`;
  $: placeholder = talks[currentIndex].profileImageBase64;
  $: src = sources[currentIndex];
  $: srcset = sourceSets[currentIndex];
  $: srcsetWebp = sourceSetsWebp[currentIndex];
 
  const transitionInterval = 5000;
 
  function advanceIndex() {
    currentIndex = (currentIndex + 1) % 2;
  }
 
  function startTransitions() {
    setTimeout(transitionSlides, transitionInterval);
  }
 
  function transitionSlides() {
    advanceIndex();
    startTransitions();
  }
 
  startTransitions();
 
  let flyDuration =
    browser && window.matchMedia('(prefers-reduced-motion: no-preference') ? 1000 : 2000;
</script>
 
<svelte:head>
  <title>SvelteKit Vanilla Extract: Course Registration Example</title>
  <html lang="en-GB" />
</svelte:head>
{#key currentIndex}
  <!-- svelte-ignore component-name-lowercase -->
  <main class={mainStyle} in:fly={{ duration: flyDuration, x: -100, easing: sineInOut }} out:fade>
    <Card>
      <h1 class={heading}>{talks[currentIndex].title}</h1>
      <SpeakerPicture {alt} {src} {srcset} {srcsetWebp} {placeholder} />
      <div class={speakerName}>{talks[currentIndex].speaker}</div>
      <div class={abstractTextStyle}>{talks[currentIndex].abstract}</div>
      <div class={dateText}>{talks[currentIndex].date}</div>
      <!-- svelte-ignore component-name-lowercase -->
      <button class={buttonStyle} type="button"> Book now!</button>
    </Card>
  </main>
{/key}
```

In terms of vanilla-extract we're not introducing new concepts here, which we haven't seen in other components.  You might notice we have added some animation.  Let me know if you would like a separate post focussing on animation with SvelteKit.  Svelte does make it easy to add a little polish with not too much effort.  We will just gloss over some details here so this post doesn't get too long.

In line `63` we use the `key` keyword to let SvelteKit know which variable change will trigger the animation.  Here, this is `currentIndex` which changes between zero and one.  The dynamic parts are within the `Card`, but we can set animation parameters in a parent element.  We do that in line `65` on the main element. There are a lot of different easing functions you can use.  Josh Nussbaum has put together a <a aria-label="Open Josh Nussbaum's Svelte transition visualiser" href="https://svelte.dev/repl/d412618c0d7b48229a7a5956b95087a9?version=3.44.0">nice little playground to experiment with</a>.  You can also <a aria-label="Learn about Svelte transitions"  href="https://svelte.dev/tutorial/in-and-out">learn more about Svelte Transitions in the official tutorial</a>.

## 🏁  Layout

The final piece of the puzzle before testing is the layout.  In our layout component, we will add a switch for changing theme and also the related code for updating the store.  First add the styles to a new file: `src/routes/layout.css.ts`.

```typescript
import { themeVars } from '$lib/styles/themes/theme.css';
import { spacing12, spacing2, spacing4, spacing8 } from '$lib/styles/vars.css';
import { style } from '@vanilla-extract/css';
 
export const container = style({
  display: 'flex',
  flexDirection: 'column',
  padding: [spacing12, 'auto'],
  minHeight: '100vh',
  '@media': {
    '(max-width: 768px )': {
      padding: [spacing4],
    },
  },
});
 
export const header = style({
  display: 'flex',
  paddingBottom: [spacing12],
});
 
export const themeButton = style({
  marginLeft: 'auto',
  padding: [spacing2, spacing4],
  borderRadius: [spacing8],
  background: themeVars.colour.dark,
  borderStyle: 'solid',
  borderColor: themeVars.colour.light,
  color: themeVars.colour.secondary,
  ':focus': {
    color: themeVars.colour.light,
  },
  ':hover': {
    color: themeVars.colour.light,
  },
});
 
export const containerMain = style({
  background: themeVars.colour.primary,
});
```

Then let's paste in the Svelte (`src/routes/__layout.svelte`) and take a quick look.

```typescript
<script lang="ts">
  import { browser } from '$app/env';
  import { theme } from '$lib/shared/stores/theme';
  import '$lib/styles/styles.css';
  import { summerTheme } from '$lib/styles/themes/summerTheme.css';
  import { winterTheme } from '$lib/styles/themes/winterTheme.css';
  import lazyload from 'vanilla-lazyload';
  import { container, containerMain, header, themeButton } from './layout.css';
 
  if (browser && !document.lazyloadInstance) {
    document.lazyloadInstance = new lazyload();
  }
 
  $: themeIsSummer = $theme === 'summer';
  $: currentTheme = themeIsSummer ? summerTheme : winterTheme;
  $: buttonText = themeIsSummer ? 'Summer theme' : 'Winter theme';
  $: buttonAriaLabel = themeIsSummer ? 'Switch to Winter theme' : 'Switch to Summer theme';
  $: buttonStyle = `${themeButton} ${currentTheme}`;
</script>
 
<div class={`${container} ${containerMain} ${currentTheme}`}>
  <!-- svelte-ignore component-name-lowercase -->
  <header class={header}>
    <button
      aria-label={buttonAriaLabel}
      class={buttonStyle}
      on:click={() => ($theme === 'summer' ? theme.set('winter') : theme.set('summer'))}
      >{buttonText}</button
    >
  </header>
  <slot />
</div>
```

The most interesting part here is line `27`.  When the user clicks the button, we change the theme in the store by calling `theme.set()`.  This changes the theme for all components.  On top it triggers the code in our store file which sets the theme in local storage.

## 💯  SvelteKit Infinite Scroll: Testing

That was a lot to get through! Thanks for following through to the end.  Go get a drink then and some fresh air so you can fully appreciate testing!

Everything should be working now so you can go to <a aria-label="Open the dev site" href="http://localhost:3000/">localhost:3000</a> to take the site for spin.

Inspect one of the site's elements and see what class you find.  You should see something like this:

```html
<div class="layout__12shizy0 layout__12shizy3 summerTheme__1660gci0">
	...
</div>

vanilla-extract has generated a unique class name, giving us scoped styling without the need for BEM or a similar system.  Next, in your browser developer tools, open up Local Storage.  You should see something like:

```json
{
	theme: "summer"
}
```

Try toggling theme to make sure this changes. Finally run `pnpm run build` then in your code editor find `build/_app/assets/winterTheme.css. ... .css`.  This is an example of the CSS files created by vanilla-extract.

If that's all worked give yourself a star!

## 🙌🏽 Using vanilla-extract with SvelteKit: What we Learned

In this post we learned:
- some vanilla-extract basics,

- configuration for using vanilla-extract with SvelteKit,

- a cheeky peek at how to set up animation in SvelteKit.

I do hope there is at least one thing in this article which you can use in your work or a side project.  There is a lot more you can learn on vanilla-extract.  Continue exploring by heading <a aria-label="Open the official vanilla-extract docs" href="https://vanilla-extract.style/documentation/styling-api/#styling-api">over to the official vanilla-extract documentation</a>.  You can see the full working example at <a aria-label="Open demo site" href="https://sveltekit-vanilla-extract.rodneylab.com/">sveltekit-vanilla-extract.rodneylab.com/</a>.

As always get in touch with feedback if I have missed a trick somewhere!  You can see the <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/sveltekit-vanilla-extract">full code for this using vanilla-extract with SvelteKit tutorial on the Rodney Lab Git Hub repo</a>.

## 🙏🏽 Using vanilla-extract with SvelteKit: Feedback

Have you found the post useful? Do you have your own methods for solving this problem? Let me know your solution. Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rondeylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <an aria-label="Reach out to me on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.