StyleX integration

StyleX is a styling library that compiles styles defined in JavaScript/TypeScript into atomic CSS at build time. It can be used to style Mantine components as an alternative to CSS Modules if you prefer to write your styles in TypeScript.

StyleX vs CSS Modules

Common features of StyleX and CSS Modules:

  • Styles are generated at build time – no runtime and performance overhead
  • Class names are scoped to the component that uses them

Differences between StyleX and CSS Modules:

  • StyleX styles are type-safe and are defined next to the component that uses them
  • StyleX generates atomic class names with increased specificity – StyleX styles always override Mantine styles, regardless of the order in which styles are loaded
  • With StyleX you do not have access to postcss-preset-mantine features like the hover and smaller-than mixins, so not all demos from the Mantine documentation can be copied as is
  • StyleX requires a compiler plugin for your bundler. This page covers Vite setup, see StyleX documentation for other bundlers and frameworks

You can use both StyleX and CSS Modules in the same project.

Installation

Set up Mantine in your application by following the Vite guide, then install StyleX and its bundler plugin. unplugin and lightningcss are required by @stylexjs/unplugin and must be installed alongside it:

yarn add @stylexjs/stylex
yarn add --dev @stylexjs/unplugin unplugin lightningcss

Add StyleX plugin to vite.config.mjs:

import stylex from '@stylexjs/unplugin';
import react from '@vitejs/plugin-react';
import { Features } from 'lightningcss';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    stylex.vite({
      lightningcssOptions: { exclude: Features.LightDark },
    }),
    react(),
  ],
});

lightningcssOptions: { exclude: Features.LightDark } is required to use light-dark() function in StyleX styles. Without it, colors defined with light-dark() are not applied.

In development, StyleX plugin injects generated styles into index.html automatically. If your framework renders the HTML document itself (for example, TanStack Start), add the stylesheet and the runtime script to the document <head> in development mode:

<link rel="stylesheet" href="/virtual:stylex.css" />
<script type="module" src="/@id/virtual:stylex:runtime"></script>

Vitest

StyleX styles must be compiled in tests – stylex.create throws an error when it is called at runtime. If your Vitest configuration uses vite.config.mjs, StyleX plugin is already applied to test files.

At the time of writing (@stylexjs/unplugin@0.19), StyleX plugin keeps Vitest process running for 10 seconds after all tests are completed. To avoid the delay, remove the development server hook of the plugin when running tests:

import stylex from '@stylexjs/unplugin';
import react from '@vitejs/plugin-react';
import { Features } from 'lightningcss';
import { defineConfig } from 'vite';

const stylexPlugin = stylex.vite({
  lightningcssOptions: { exclude: Features.LightDark },
});

if (process.env.VITEST) {
  delete stylexPlugin.configureServer;
}

export default defineConfig({
  plugins: [stylexPlugin, react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './vitest.setup.mjs',
  },
});

Styling components

stylex.props function returns an object with className and style properties. Spread it on any Mantine component to apply styles to the root element:

import * as stylex from '@stylexjs/stylex';
import { Button } from '@mantine/core';

const styles = stylex.create({
  button: {
    backgroundColor: {
      default: 'var(--mantine-color-pink-filled)',
      ':hover': 'var(--mantine-color-pink-filled-hover)',
    },
  },
});

function Demo() {
  return <Button {...stylex.props(styles.button)}>Button</Button>;
}

Styles API

To apply styles to inner elements, pass class names generated by StyleX to classNames prop:

import * as stylex from '@stylexjs/stylex';
import { TextInput } from '@mantine/core';

const styles = stylex.create({
  label: {
    color: 'var(--mantine-color-teal-filled)',
    fontWeight: 700,
  },

  input: {
    borderColor: 'var(--mantine-color-teal-filled)',
    borderWidth: 2,
  },
});

function Demo() {
  return (
    <TextInput
      label="Label"
      classNames={{
        label: stylex.props(styles.label).className,
        input: stylex.props(styles.input).className,
      }}
    />
  );
}

Do not use attributes prop to apply StyleX styles – Mantine class names override className passed to attributes, so StyleX styles are not applied.

Components CSS variables

Since StyleX styles have higher specificity than Mantine styles, they also override styles that Mantine applies based on component state. For example, if you set backgroundColor of a Button, disabled buttons keep the same background color.

To keep states styles, override component CSS variables instead of CSS properties. All components CSS variables are listed on the Styles API tab of each component documentation page:

import * as stylex from '@stylexjs/stylex';
import { Button } from '@mantine/core';

const styles = stylex.create({
  button: {
    '--button-bg': 'var(--mantine-color-pink-filled)',
    '--button-hover': 'var(--mantine-color-pink-filled-hover)',
    '--button-radius': '0px',
  },
});

function Demo() {
  return (
    <>
      <Button {...stylex.props(styles.button)}>Pink button</Button>
      <Button {...stylex.props(styles.button)} disabled>
        Disabled button
      </Button>
    </>
  );
}

Theme tokens

You should not duplicate Mantine theme tokens in StyleX – all theme properties are already exposed as CSS variables. Reference them in StyleX styles directly, or use stylex.defineVars to create type-safe references to the variables that you use frequently:

// tokens.stylex.ts
import * as stylex from '@stylexjs/stylex';

export const mantine = stylex.defineVars({
  primary: 'var(--mantine-primary-color-filled)',
  body: 'var(--mantine-color-body)',
  text: 'var(--mantine-color-text)',
  radius: 'var(--mantine-radius-md)',
  spacing: 'var(--mantine-spacing-md)',
});
// Demo.tsx
import * as stylex from '@stylexjs/stylex';
import { Card } from '@mantine/core';
import { mantine } from './tokens.stylex';

const styles = stylex.create({
  card: {
    backgroundColor: mantine.primary,
    padding: mantine.spacing,
    borderRadius: mantine.radius,
    color: 'white',
  },
});

function Demo() {
  return <Card {...stylex.props(styles.card)}>Card</Card>;
}

Variables defined with stylex.defineVars must be exported from files with .stylex.ts extension.

Light and dark color schemes

Use light-dark() CSS function to define different values for light and dark color schemes. It requires lightningcssOptions configuration described in the installation section:

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  box: {
    backgroundColor: 'light-dark(var(--mantine-color-yellow-1), var(--mantine-color-yellow-9))',
  },
});

Mantine CSS variables that depend on color scheme (for example, --mantine-color-body, --mantine-color-text or --mantine-primary-color-filled) are updated automatically.

Responsive styles

Use theme breakpoints values in em units in StyleX media queries:

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  text: {
    fontSize: {
      default: 'var(--mantine-font-size-sm)',
      '@media (min-width: 48em)': 'var(--mantine-font-size-lg)',
    },
  },
});

Dynamic styles

StyleX dynamic styles are applied with style property returned by stylex.props. If you also pass style prop to the component, it overrides the one from stylex.props. To use both, pass an array to style prop:

import * as stylex from '@stylexjs/stylex';
import { Button } from '@mantine/core';

const styles = stylex.create({
  button: (width: number) => ({ width }),
});

function Demo() {
  const { className, style } = stylex.props(styles.button(300));

  return (
    <Button className={className} style={[style, { height: 50 }]}>
      Button
    </Button>
  );
}

CSS layers

By default, StyleX does not use CSS layers and can be used with @mantine/core/styles.css. If you enable useCSSLayers option in StyleX plugin, import @mantine/core/styles.layer.css instead. Otherwise, Mantine styles override all StyleX styles. See Mantine styles guide to learn more about CSS layers support.