Skip to content

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

FixDevs ·

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.

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));
}
*/

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.

For related UI component issues, see Fix: shadcn/ui Not Working and Fix: Radix 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