Skip to content

Fix: Mantine Not Working — Styles Not Loading, Theme Not Applying, or Components Broken After Upgrade

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix Mantine UI issues — MantineProvider setup, PostCSS configuration, theme customization, dark mode, form validation with useForm, and Next.js App Router integration.

The Problem

Mantine components render but have no styles:

import { Button } from '@mantine/core';

function App() {
  return <Button>Click me</Button>;
  // Renders an unstyled HTML button
}

Or the theme colors don’t match what you configured:

<MantineProvider theme={{ primaryColor: 'violet' }}>
  <Button>Should be violet</Button>
</MantineProvider>
// Button is still blue

Or after upgrading to Mantine v7, everything breaks:

Module not found: Can't resolve '@mantine/core/styles.css'

Why This Happens

Mantine v7 made significant changes to how styles are loaded. Unlike v6 which used CSS-in-JS (emotion), v7 uses native CSS with PostCSS:

  • CSS must be explicitly imported@mantine/core/styles.css must be imported in your app’s entry point. Without it, components render without any styles. This is the biggest change from v6.
  • MantineProvider is required — the theme context provides colors, fonts, spacing, and other tokens to all components. Without it, components use raw defaults.
  • PostCSS with postcss-preset-mantine is required — Mantine v7 uses CSS modules and needs a specific PostCSS configuration for features like light-dark() function and responsive styles.
  • v6 to v7 is a breaking change — emotion-based styles, createStyles, sx prop, and styles prop format all changed. The migration is not backward-compatible.

The v6 → v7 migration is the source of the majority of Mantine production incidents. Teams upgrade Mantine in a feature branch, run the dev server, see that components mostly render correctly, ship to production, and discover that server-rendered pages flash unstyled content for several hundred milliseconds before the CSS arrives. This FOUC (Flash of Unstyled Content) is the textbook symptom of CSS being loaded after the React tree hydrates, and it’s particularly bad on slow connections where users see the unstyled DOM for one to three seconds.

The other class of failure is hydration mismatch caused by ColorSchemeScript. In dark mode setups, the script reads localStorage or the system preference and applies a class to <html> before React mounts. If you put the script in <body> instead of <head>, or if you forget suppressHydrationWarning on <html>, the server-rendered HTML has one color scheme and the client-rendered tree has another. React throws a hydration warning in dev and silently re-renders the whole tree in production, doubling time-to-interactive.

From a production incident lens, the worst Mantine failures are visual rather than functional. The app works, buttons click, forms submit — but the brand looks broken, the layout shifts, and the dark mode flickers. These bugs rarely page on-call but they erode user trust and conversion. Wire up visual regression testing (Percy, Chromatic, or Playwright screenshots) to catch FOUC and theme drift before they ship. Treat the Mantine upgrade path as a multi-PR migration: lock the major version, ship CSS imports in one PR, migrate createStyles in the next, switch to MantineProvider last.

Fix 1: Setup Mantine v7

npm install @mantine/core @mantine/hooks
npm install -D postcss postcss-preset-mantine postcss-simple-vars
// postcss.config.mjs — required for Mantine v7
export default {
  plugins: {
    'postcss-preset-mantine': {},
    'postcss-simple-vars': {
      variables: {
        'mantine-breakpoint-xs': '36em',
        'mantine-breakpoint-sm': '48em',
        'mantine-breakpoint-md': '62em',
        'mantine-breakpoint-lg': '75em',
        'mantine-breakpoint-xl': '88em',
      },
    },
  },
};
// app/layout.tsx — Next.js App Router
import '@mantine/core/styles.css';
// Optional: additional module styles
// import '@mantine/dates/styles.css';
// import '@mantine/notifications/styles.css';
// import '@mantine/code-highlight/styles.css';
// import '@mantine/carousel/styles.css';

import { MantineProvider, ColorSchemeScript, createTheme } from '@mantine/core';

const theme = createTheme({
  primaryColor: 'blue',
  fontFamily: 'Inter, sans-serif',
  defaultRadius: 'md',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <ColorSchemeScript defaultColorScheme="auto" />
      </head>
      <body>
        <MantineProvider theme={theme} defaultColorScheme="auto">
          {children}
        </MantineProvider>
      </body>
    </html>
  );
}

Fix 2: Theme Customization

import { createTheme, MantineProvider, virtualColor } from '@mantine/core';

const theme = createTheme({
  // Colors — define custom color scales
  colors: {
    brand: [
      '#f0f4ff', '#dbe4ff', '#bac8ff', '#91a7ff', '#748ffc',
      '#5c7cfa', '#4c6ef5', '#4263eb', '#3b5bdb', '#364fc7',
    ],
    // Virtual color — maps to different scales in light/dark mode
    surface: virtualColor({
      name: 'surface',
      dark: 'dark',
      light: 'gray',
    }),
  },
  primaryColor: 'brand',
  primaryShade: { light: 6, dark: 7 },

  // Typography
  fontFamily: 'Inter, -apple-system, sans-serif',
  fontFamilyMonospace: 'Fira Code, monospace',
  headings: {
    fontFamily: 'Cal Sans, sans-serif',
    fontWeight: '700',
    sizes: {
      h1: { fontSize: '2.5rem', lineHeight: '1.2' },
      h2: { fontSize: '2rem', lineHeight: '1.3' },
      h3: { fontSize: '1.5rem', lineHeight: '1.4' },
    },
  },

  // Spacing and radius
  defaultRadius: 'md',
  spacing: { xs: '0.5rem', sm: '0.75rem', md: '1rem', lg: '1.5rem', xl: '2rem' },

  // Component defaults
  components: {
    Button: {
      defaultProps: {
        size: 'md',
        variant: 'filled',
      },
      styles: {
        root: {
          fontWeight: 600,
        },
      },
    },
    Input: {
      defaultProps: {
        size: 'md',
      },
    },
    Card: {
      defaultProps: {
        shadow: 'sm',
        padding: 'lg',
        withBorder: true,
      },
    },
  },
});

Fix 3: Common Components

import {
  Button, TextInput, Select, Checkbox, Group, Stack, Card, Text,
  Title, Badge, Avatar, Tabs, Accordion, Modal, Drawer,
  AppShell, Burger, NavLink,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

// Layout with AppShell
function DashboardLayout({ children }: { children: React.ReactNode }) {
  const [opened, { toggle }] = useDisclosure();

  return (
    <AppShell
      header={{ height: 60 }}
      navbar={{ width: 250, breakpoint: 'sm', collapsed: { mobile: !opened } }}
      padding="md"
    >
      <AppShell.Header>
        <Group h="100%" px="md">
          <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
          <Title order={3}>My App</Title>
        </Group>
      </AppShell.Header>

      <AppShell.Navbar p="md">
        <NavLink label="Dashboard" href="/dashboard" active />
        <NavLink label="Settings" href="/settings" />
        <NavLink label="Users" href="/users" />
      </AppShell.Navbar>

      <AppShell.Main>{children}</AppShell.Main>
    </AppShell>
  );
}

// Card list
function UserCard({ user }: { user: User }) {
  return (
    <Card>
      <Group>
        <Avatar src={user.avatar} radius="xl" size="lg" />
        <div>
          <Text fw={600}>{user.name}</Text>
          <Text size="sm" c="dimmed">{user.email}</Text>
        </div>
        <Badge ml="auto" color={user.role === 'admin' ? 'red' : 'blue'}>
          {user.role}
        </Badge>
      </Group>
    </Card>
  );
}

// Modal
function ConfirmModal() {
  const [opened, { open, close }] = useDisclosure(false);

  return (
    <>
      <Modal opened={opened} onClose={close} title="Confirm Action" centered>
        <Text>Are you sure you want to proceed?</Text>
        <Group mt="md" justify="flex-end">
          <Button variant="default" onClick={close}>Cancel</Button>
          <Button color="red" onClick={() => { handleDelete(); close(); }}>Delete</Button>
        </Group>
      </Modal>
      <Button color="red" onClick={open}>Delete Item</Button>
    </>
  );
}

Fix 4: Forms with @mantine/form

npm install @mantine/form
import { useForm, zodResolver } from '@mantine/form';
import { TextInput, NumberInput, Select, Checkbox, Button, Stack } from '@mantine/core';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email'),
  age: z.number().min(18, 'Must be 18+').max(150),
  role: z.string().min(1, 'Select a role'),
  terms: z.boolean().refine(v => v, 'Must accept terms'),
});

function RegistrationForm() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      name: '',
      email: '',
      age: 18,
      role: '',
      terms: false,
    },
    validate: zodResolver(schema),
  });

  function handleSubmit(values: typeof form.values) {
    console.log(values);
  }

  return (
    <form onSubmit={form.onSubmit(handleSubmit)}>
      <Stack>
        <TextInput
          label="Name"
          placeholder="Your name"
          withAsterisk
          key={form.key('name')}
          {...form.getInputProps('name')}
        />

        <TextInput
          label="Email"
          placeholder="[email protected]"
          withAsterisk
          key={form.key('email')}
          {...form.getInputProps('email')}
        />

        <NumberInput
          label="Age"
          withAsterisk
          min={0}
          max={150}
          key={form.key('age')}
          {...form.getInputProps('age')}
        />

        <Select
          label="Role"
          placeholder="Select role"
          withAsterisk
          data={[
            { value: 'developer', label: 'Developer' },
            { value: 'designer', label: 'Designer' },
            { value: 'manager', label: 'Manager' },
          ]}
          key={form.key('role')}
          {...form.getInputProps('role')}
        />

        <Checkbox
          label="I accept the terms and conditions"
          key={form.key('terms')}
          {...form.getInputProps('terms', { type: 'checkbox' })}
        />

        <Button type="submit">Register</Button>
      </Stack>
    </form>
  );
}

Fix 5: Dark Mode

// Toggle color scheme
'use client';

import { useMantineColorScheme, ActionIcon, Group } from '@mantine/core';

function ColorSchemeToggle() {
  const { colorScheme, setColorScheme } = useMantineColorScheme();

  return (
    <Group>
      <ActionIcon
        onClick={() => setColorScheme('light')}
        variant={colorScheme === 'light' ? 'filled' : 'default'}
      >
        ☀️
      </ActionIcon>
      <ActionIcon
        onClick={() => setColorScheme('dark')}
        variant={colorScheme === 'dark' ? 'filled' : 'default'}
      >
        🌙
      </ActionIcon>
      <ActionIcon
        onClick={() => setColorScheme('auto')}
        variant={colorScheme === 'auto' ? 'filled' : 'default'}
      >
        💻
      </ActionIcon>
    </Group>
  );
}

// CSS module with light-dark() — requires postcss-preset-mantine
// styles.module.css
/*
.card {
  background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
  color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
  border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
*/

Production Incident: Theme Breaks After CSS-in-JS Hydration Mismatch, FOUC in Production

A typical production incident: you deploy a Mantine app to Vercel. Lighthouse scores drop. Users on slow connections see a fully unstyled DOM for two seconds, then the styles snap in. The page jumps as buttons resize and modals reposition. Conversion drops 8%.

The root cause is almost always one of three things:

CSS not in critical render path. @mantine/core/styles.css is imported in a Client Component instead of a Server Component or root layout. The browser doesn’t see the CSS link until the JavaScript bundle parses and executes, which is too late. Move the import to app/layout.tsx or app/_app.tsx so it’s in the initial HTML response.

ColorSchemeScript in the wrong place. Putting it in <body> causes a flash of light mode before the script runs and applies dark mode. Put it in <head> and add suppressHydrationWarning to <html>. The script must execute before paint, not after hydration.

PostCSS missing from the build pipeline. A team migrates from Vite to Next.js (or vice versa) and forgets to copy over postcss.config.mjs. Mantine’s light-dark() calls fall through to raw CSS, which the browser ignores. Dev works because Vite has a fallback; production breaks because Next.js applies a stricter PostCSS pipeline.

The diagnostic playbook is straightforward. Open DevTools → Network and reload with cache disabled. Look at the order of CSS requests. If styles.css comes after the main JavaScript bundle, your import is wrong. Look at the rendered <head>ColorSchemeScript should be the first or second <script> tag. Add a visual regression test that fails when the rendered button color differs from the expected color by more than 5%.

The blast radius is brand and conversion, not uptime. But for any product where first impressions matter (marketing sites, signup flows, paid landing pages), a Mantine theme regression is more damaging than a brief outage because users blame your product, not your infrastructure.

Fix 6: Notifications

npm install @mantine/notifications
// app/layout.tsx — add Notifications provider
import '@mantine/notifications/styles.css';
import { Notifications } from '@mantine/notifications';

<MantineProvider>
  <Notifications position="top-right" />
  {children}
</MantineProvider>
// Usage anywhere
import { notifications } from '@mantine/notifications';

function SaveButton() {
  async function handleSave() {
    const id = notifications.show({
      loading: true,
      title: 'Saving...',
      message: 'Please wait',
      autoClose: false,
    });

    try {
      await saveData();
      notifications.update({
        id,
        loading: false,
        title: 'Saved!',
        message: 'Your changes have been saved',
        color: 'green',
        autoClose: 3000,
      });
    } catch {
      notifications.update({
        id,
        loading: false,
        title: 'Error',
        message: 'Failed to save',
        color: 'red',
        autoClose: 5000,
      });
    }
  }

  return <Button onClick={handleSave}>Save</Button>;
}

Still Not Working?

Components have no styles — import @mantine/core/styles.css in your root layout or entry file. This is required in Mantine v7. Each additional package needs its own CSS import (@mantine/dates/styles.css, etc.).

PostCSS errors or light-dark() not working — install postcss-preset-mantine and postcss-simple-vars, then add them to postcss.config.mjs. Without the preset, Mantine’s CSS features like light-dark() don’t compile.

v6 code doesn’t work in v7createStyles, sx prop, and emotion-based styles prop were removed in v7. Replace createStyles with CSS modules, sx with style or className, and update styles to use the new format. See the official v6→v7 migration guide.

ColorSchemeScript causes hydration errors — put <ColorSchemeScript /> in <head>, not <body>. Add suppressHydrationWarning to the <html> tag. The script must run before React hydrates to prevent a flash of wrong color scheme.

FOUC on production but not on local dev — Vite injects CSS via JavaScript during development, but Next.js extracts it to a <link> tag in production. If you import @mantine/core/styles.css inside a Client Component, Next.js may bundle it with the client chunk, which loads after the initial HTML. Move the CSS import to the root layout or to a Server Component that runs before any client code.

Mantine v7 components conflict with Tailwind CSS — Tailwind’s preflight resets headings and buttons to defaults that override Mantine’s styles. Import Mantine’s CSS after Tailwind’s CSS in your root layout, or disable Tailwind’s preflight with corePlugins: { preflight: false }. The order matters because CSS cascade follows import order. See Fix: Tailwind Classes Not Applying for the Tailwind side of this.

Server components throw errors using Mantine hooks — hooks like useMantineColorScheme only work in Client Components. Mark the file with 'use client' at the top. Mantine has both server and client component exports; if you mix them, the build fails with a server/client boundary error.

For related UI component issues, see Fix: shadcn/ui Not Working, Fix: Radix UI Not Working, and Fix: Chakra UI Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles