Vanilla extract integration

Vanilla extract is a TypeScript CSS preprocessor that generates static CSS files at build time. It is a great alternative to CSS Modules if you prefer to write your styles in TypeScript.

Vanilla extract vs CSS Modules

Vanilla extract and CSS Modules do the same thing, but with different syntax. Common features of Vanilla extract and CSS Modules:

  • Styles are generated at build time – no runtime and performance overhead
  • Classes names are scoped to the styles file

Differences between Vanilla extract and CSS Modules:

  • Vanilla extract styles are type safe
  • You can use any JavaScript/TypeScript code in Vanilla extract styles, including color functions
  • With Vanilla extract you do not have access to postcss-preset-mantine features like light-dark function and hover mixin. Because of this, you will not be able to copy-paste all demos from Mantine documentation and use them with Vanilla extract.
  • Vanilla extract requires additional configuration and setup that may not be available for your build tool/framework. Most popular tools like Next.js and Vite have plugins for Vanilla extract, but if you are using something more niche, you might need to configure it yourself.

Note that you can use both Vanilla extract and CSS Modules in the same project, it will not cause any issues: performance will be the same and the bundle size will not be impacted.

Installation

Follow the installation instructions to install vanilla extract. Then install @mantine/vanilla-extract package, it exports themeToVars function to convert Mantine theme to CSS variables:

yarn add @mantine/vanilla-extract

Templates

You can use one of the following templates to get started or a reference for your own setup. Note that all templates include only minimal setup.

next-vanilla-extract-template

Next.js template with Vanilla extract example

Use template

vite-vanilla-extract-template

Vite template with Vanilla extract example

Use template

Theming

Vanilla extract provides createTheme function which converts given theme object into CSS variables and assigns them to :root or other selector. You should not use Vanilla extract createTheme to generate Mantine theme tokens – all Mantine theme properties are already exposed as CSS variables. Instead, use themeToVars function from @mantine/vanilla-extract package to create an object with CSS variables from Mantine theme:

// theme.ts
import { createTheme } from '@mantine/core';

// Do not forget to pass theme to MantineProvider
export const theme = createTheme({
  fontFamily: 'serif',
  primaryColor: 'cyan',
});
// theme.css.ts
import { theme } from './theme';

// CSS variables object, can be access in *.css.ts files
export const vars = themeToVars(theme);

Styling

Import vars object in *.css.ts files to access Mantine CSS variables:

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme';

export const demo = style({
  fontSize: vars.fontSizes.xl,
  backgroundColor: vars.colors.red[5],
  color: vars.colors.white,
});

rem and em

To convert px to rem or em use rem and em functions from @mantine/core package:

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { rem } from '@mantine/core';

export const demo = style({
  fontSize: rem(16),

  '@media': {
    [`(min-width: ${em(768)})`]: {
      fontSize: rem(18),
    },
  },
});

light and dark selectors

vars object contains lightSelector and darkSelector properties which can be used to apply styles only in light or dark color scheme:

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme';

export const demo = style({
  fontSize: vars.fontSizes.xl,

  selectors: {
    [vars.lightSelector]: {
      backgroundColor: vars.colors.red[5],
      color: vars.colors.white,
    },

    [vars.darkSelector]: {
      backgroundColor: vars.colors.blue[5],
      color: vars.colors.white,
    },
  },
});

Note that usually it is more convenient to use only one of them: apply styles for light color scheme and then override them for dark color scheme with vars.darkSelector (or vice versa):

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme';

export const demo = style({
  fontSize: vars.fontSizes.xl,
  backgroundColor: vars.colors.red[5],
  color: vars.colors.white,

  selectors: {
    [vars.darkSelector]: {
      backgroundColor: vars.colors.blue[5],
      color: vars.colors.white,
    },
  },
});

largerThan and smallerThan

vars object contains largerThan and smallerThan properties which can be used in @media as a shorthand for min-width and max-width:

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme';

export const demo = style({
  fontSize: vars.fontSizes.sm,

  '@media': {
    // equivalent to `(min-width: 640px)` converted to em
    // -> `(min-width: 40em)`
    [vars.largerThan(640)]: {
      fontSize: vars.fontSizes.md,
    },

    // equivalent to `(max-width: 640px)` converted to em
    // -> `(max-width: 40em)`
    [vars.smallerThan(640)]: {
      fontSize: vars.fontSizes.xs,
    },

    // You can reference `theme.breakpoints` values
    [vars.largerThan('sm')]: {
      fontSize: vars.fontSizes.md,
    },
  },
});

rtl selector

Use vars.rtlSelector to apply styles only in rtl direction:

// Demo.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './theme';

export const demo = style({
  paddingRight: vars.spacing.md,

  selectors: {
    [vars.rtlSelector]: {
      paddingLeft: vars.spacing.md,
      paddingRight: 0,
    },
  },
});