Fix: Mantine Not Working — Styles Not Loading, Theme Not Applying, or Components Broken After Upgrade
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 blueOr 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.cssmust be imported in your app’s entry point. Without it, components render without any styles. This is the biggest change from v6. MantineProvideris required — the theme context provides colors, fonts, spacing, and other tokens to all components. Without it, components use raw defaults.- PostCSS with
postcss-preset-mantineis required — Mantine v7 uses CSS modules and needs a specific PostCSS configuration for features likelight-dark()function and responsive styles. - v6 to v7 is a breaking change — emotion-based styles,
createStyles,sxprop, andstylesprop 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/formimport { 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 v7 — createStyles, 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: cmdk Not Working — Command Palette Not Opening, Items Not Filtering, or Keyboard Navigation Broken
How to fix cmdk command palette issues — Dialog setup, custom filtering, groups and separators, keyboard shortcuts, async search, nested pages, and integration with shadcn/ui and Tailwind.
Fix: Conform Not Working — Form Validation Not Triggering, Server Errors Missing, or Zod Schema Rejected
How to fix Conform form validation issues — useForm setup with Zod, server action integration, nested and array fields, file uploads, progressive enhancement, and Remix and Next.js usage.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
Fix: Lingui Not Working — Messages Not Extracted, Translations Missing, or Macro Errors
How to fix Lingui.js i18n issues — setup with React, message extraction, macro compilation, ICU format, lazy loading catalogs, and Next.js integration.