| name | mantine |
| description | Builds React applications with Mantine's 100+ components, hooks, and form library. Use when creating feature-rich UIs with built-in dark mode, accessibility, and TypeScript support. |
Mantine
Full-featured React component library with 100+ customizable components and 50+ hooks.
Quick Start
npm install @mantine/core @mantine/hooks
npm install -D postcss postcss-preset-mantine postcss-simple-vars
// postcss.config.cjs
module.exports = {
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.tsx
import '@mantine/core/styles.css';
import { MantineProvider, createTheme } from '@mantine/core';
const theme = createTheme({
primaryColor: 'blue',
fontFamily: 'Inter, sans-serif',
});
function App() {
return (
<MantineProvider theme={theme}>
<YourApp />
</MantineProvider>
);
}
// Usage
import { Button, TextInput, Group, Stack } from '@mantine/core';
function Demo() {
return (
<Stack>
<TextInput label="Email" placeholder="you@example.com" />
<Group>
<Button>Submit</Button>
<Button variant="outline">Cancel</Button>
</Group>
</Stack>
);
}
Core Components
Button
import { Button, ActionIcon } from '@mantine/core';
import { IconSettings } from '@tabler/icons-react';
// Variants
<Button variant="filled">Filled</Button>
<Button variant="light">Light</Button>
<Button variant="outline">Outline</Button>
<Button variant="subtle">Subtle</Button>
<Button variant="transparent">Transparent</Button>
<Button variant="white">White</Button>
// Sizes
<Button size="xs">Extra Small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="xl">Extra Large</Button>
// Colors
<Button color="blue">Blue</Button>
<Button color="red">Red</Button>
<Button color="green">Green</Button>
// With icons
<Button leftSection={<IconSettings size={16} />}>Settings</Button>
<Button rightSection={<IconArrow size={16} />}>Next</Button>
// Loading
<Button loading>Processing...</Button>
// Icon button
<ActionIcon variant="filled" size="lg">
<IconSettings size={18} />
</ActionIcon>
TextInput
import { TextInput, PasswordInput, Textarea, NumberInput } from '@mantine/core';
<TextInput
label="Email"
description="We'll never share your email"
placeholder="you@example.com"
required
error="Invalid email"
/>
<PasswordInput
label="Password"
placeholder="Enter password"
visible={visible}
onVisibilityChange={toggle}
/>
<Textarea
label="Message"
placeholder="Your message"
autosize
minRows={3}
maxRows={6}
/>
<NumberInput
label="Amount"
placeholder="Enter amount"
min={0}
max={100}
step={5}
prefix="$"
thousandSeparator=","
/>
Select and MultiSelect
import { Select, MultiSelect, Combobox } from '@mantine/core';
<Select
label="Framework"
placeholder="Pick one"
data={['React', 'Vue', 'Angular', 'Svelte']}
searchable
clearable
/>
<Select
label="User"
data={[
{ value: '1', label: 'John Doe' },
{ value: '2', label: 'Jane Smith' },
]}
/>
<MultiSelect
label="Technologies"
placeholder="Select technologies"
data={['TypeScript', 'JavaScript', 'Python', 'Rust']}
searchable
maxSelectedValues={3}
/>
Modal and Drawer
import { Modal, Button } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function ModalDemo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Authentication">
Modal content here...
</Modal>
<Button onClick={open}>Open modal</Button>
</>
);
}
// Drawer
import { Drawer } from '@mantine/core';
<Drawer
opened={opened}
onClose={close}
title="Navigation"
position="left" // left, right, top, bottom
size="md"
>
Drawer content...
</Drawer>
Notifications
npm install @mantine/notifications
import '@mantine/notifications/styles.css';
import { Notifications, notifications } from '@mantine/notifications';
// Add to app root
<MantineProvider>
<Notifications position="top-right" />
<App />
</MantineProvider>
// Show notification
notifications.show({
title: 'Success',
message: 'Your changes have been saved',
color: 'green',
autoClose: 5000,
});
// Update existing notification
const id = notifications.show({
loading: true,
title: 'Loading...',
message: 'Please wait',
autoClose: false,
});
notifications.update({
id,
loading: false,
title: 'Complete',
message: 'Data loaded successfully',
color: 'green',
autoClose: 3000,
});
Tabs
import { Tabs } from '@mantine/core';
<Tabs defaultValue="gallery">
<Tabs.List>
<Tabs.Tab value="gallery">Gallery</Tabs.Tab>
<Tabs.Tab value="messages">Messages</Tabs.Tab>
<Tabs.Tab value="settings">Settings</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="gallery" pt="md">
Gallery content
</Tabs.Panel>
<Tabs.Panel value="messages" pt="md">
Messages content
</Tabs.Panel>
<Tabs.Panel value="settings" pt="md">
Settings content
</Tabs.Panel>
</Tabs>
Card
import { Card, Image, Text, Badge, Button, Group } from '@mantine/core';
<Card shadow="sm" padding="lg" radius="md" withBorder>
<Card.Section>
<Image src="/product.jpg" height={160} alt="Product" />
</Card.Section>
<Group justify="space-between" mt="md" mb="xs">
<Text fw={500}>Product Name</Text>
<Badge color="pink">On Sale</Badge>
</Group>
<Text size="sm" c="dimmed">
Product description goes here.
</Text>
<Button fullWidth mt="md" radius="md">
Add to Cart
</Button>
</Card>
Forms
npm install @mantine/form
import { useForm } from '@mantine/form';
import { TextInput, Button, Stack } from '@mantine/core';
function ContactForm() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
message: '',
},
validate: {
name: (value) => value.length < 2 ? 'Name is too short' : null,
email: (value) => /^\S+@\S+$/.test(value) ? null : 'Invalid email',
message: (value) => value.length < 10 ? 'Message too short' : null,
},
});
return (
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<Stack>
<TextInput
label="Name"
placeholder="Your name"
key={form.key('name')}
{...form.getInputProps('name')}
/>
<TextInput
label="Email"
placeholder="your@email.com"
key={form.key('email')}
{...form.getInputProps('email')}
/>
<Textarea
label="Message"
placeholder="Your message"
key={form.key('message')}
{...form.getInputProps('message')}
/>
<Button type="submit">Submit</Button>
</Stack>
</form>
);
}
Form with Zod Validation
import { useForm, zodResolver } from '@mantine/form';
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 or older'),
});
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: '', email: '', age: 18 },
validate: zodResolver(schema),
});
Hooks
import {
useDisclosure,
useToggle,
useDebouncedValue,
useClipboard,
useLocalStorage,
useMediaQuery,
useClickOutside,
useHotkeys,
useForm,
} from '@mantine/hooks';
// Disclosure (modals, menus)
const [opened, { open, close, toggle }] = useDisclosure(false);
// Toggle between values
const [value, toggle] = useToggle(['light', 'dark']);
// Debounced value
const [search, setSearch] = useState('');
const [debounced] = useDebouncedValue(search, 300);
// Clipboard
const clipboard = useClipboard({ timeout: 2000 });
clipboard.copy('Text to copy');
clipboard.copied // true after copy
// Local storage
const [value, setValue] = useLocalStorage({
key: 'user-preference',
defaultValue: 'dark',
});
// Media query
const isMobile = useMediaQuery('(max-width: 768px)');
// Click outside
const ref = useClickOutside(() => setOpened(false));
// Keyboard shortcuts
useHotkeys([
['mod+K', () => openSearch()],
['mod+S', () => saveDocument()],
['Escape', () => closeModal()],
]);
Theming
Custom Theme
import { createTheme, MantineProvider } from '@mantine/core';
const theme = createTheme({
// Primary color
primaryColor: 'brand',
primaryShade: { light: 6, dark: 8 },
// Custom colors (10 shades each)
colors: {
brand: [
'#e6f2ff', '#b3d9ff', '#80bfff', '#4da6ff', '#1a8cff',
'#0073e6', '#005cb3', '#004480', '#002d4d', '#00161a',
],
},
// Typography
fontFamily: 'Inter, sans-serif',
fontFamilyMonospace: 'Fira Code, monospace',
headings: {
fontFamily: 'Inter, sans-serif',
fontWeight: '600',
sizes: {
h1: { fontSize: '2.5rem', lineHeight: '1.2' },
h2: { fontSize: '2rem', lineHeight: '1.3' },
},
},
// Spacing scale
spacing: {
xs: '0.5rem',
sm: '0.75rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
},
// Border radius
radius: {
xs: '2px',
sm: '4px',
md: '8px',
lg: '16px',
xl: '32px',
},
defaultRadius: 'md',
// Shadows
shadows: {
xs: '0 1px 2px rgba(0, 0, 0, 0.05)',
sm: '0 1px 3px rgba(0, 0, 0, 0.1)',
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px rgba(0, 0, 0, 0.15)',
},
// Focus styles
focusRing: 'auto', // 'auto' | 'always' | 'never'
// Other
cursorType: 'pointer', // 'default' | 'pointer'
autoContrast: true,
});
Dark Mode
import { MantineProvider, useMantineColorScheme, Button } from '@mantine/core';
// Provider setup
<MantineProvider defaultColorScheme="auto">
<App />
</MantineProvider>
// Toggle button
function ColorSchemeToggle() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
return (
<Button onClick={() => toggleColorScheme()}>
{colorScheme === 'dark' ? 'Light' : 'Dark'} mode
</Button>
);
}
Styles API
import { Button, createStyles } from '@mantine/core';
// Using classNames prop
<Button
classNames={{
root: 'my-button-root',
label: 'my-button-label',
}}
>
Styled Button
</Button>
// Using styles prop
<Button
styles={{
root: { backgroundColor: 'red' },
label: { color: 'white' },
}}
>
Inline Styled
</Button>
// CSS Modules (recommended)
// Button.module.css
.root {
background-color: var(--mantine-color-blue-6);
}
.label {
font-weight: 700;
}
// Component
import classes from './Button.module.css';
<Button classNames={classes}>CSS Modules</Button>
CSS Variables
// Access theme tokens via CSS variables
.myComponent {
background-color: var(--mantine-color-blue-6);
padding: var(--mantine-spacing-md);
border-radius: var(--mantine-radius-md);
font-size: var(--mantine-font-size-sm);
/* Dark mode specific */
@mixin dark {
background-color: var(--mantine-color-dark-6);
}
/* Responsive */
@media (max-width: $mantine-breakpoint-sm) {
padding: var(--mantine-spacing-xs);
}
}
Layout
import {
AppShell,
Container,
Grid,
Group,
Stack,
Flex,
SimpleGrid,
Center,
} from '@mantine/core';
// App shell layout
<AppShell
header={{ height: 60 }}
navbar={{ width: 300, breakpoint: 'sm', collapsed: { mobile: !opened } }}
padding="md"
>
<AppShell.Header>Header</AppShell.Header>
<AppShell.Navbar>Navbar</AppShell.Navbar>
<AppShell.Main>Content</AppShell.Main>
</AppShell>
// Container with max width
<Container size="md">Content</Container>
// Grid
<Grid>
<Grid.Col span={6}>Half</Grid.Col>
<Grid.Col span={6}>Half</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 4 }}>Responsive</Grid.Col>
</Grid>
// Simple grid (equal columns)
<SimpleGrid cols={3}>
<div>1</div>
<div>2</div>
<div>3</div>
</SimpleGrid>
// Flex layouts
<Group justify="space-between" align="center">
<Text>Left</Text>
<Text>Right</Text>
</Group>
<Stack gap="md">
<Text>Item 1</Text>
<Text>Item 2</Text>
</Stack>
<Flex gap="md" wrap="wrap" justify="center">
<Box>Item</Box>
<Box>Item</Box>
</Flex>
Best Practices
- Use CSS modules - Most performant styling approach
- Leverage hooks -
useDisclosure,useDebouncedValue,useFormfor common patterns - Use Styles API - Override component parts with
classNamesprop - Theme tokens - Use CSS variables for consistent theming
- Form validation - Use
@mantine/formwith Zod for type-safe forms
Reference Files
- references/components.md - Complete component list
- references/hooks.md - All available hooks