# Mantine UI

> Expert guidance for building React applications with Mantine UI. Use when working with Mantine components, styling, forms, layouts, or theming.

- Skill: `deepnoodle-ai/mantine-ui` (Agent Skill)
- Install (CLI): `npx skillmds@latest add deepnoodle-ai/mantine-ui`
- Raw SKILL.md: https://api.skillmd.com/api/skills/deepnoodle-ai/mantine-ui/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: deepnoodle-ai (https://skillmd.com/u/deepnoodle-ai)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/deepnoodle-ai/mantine-ui

---


# Mantine UI

Expert guidance for building React applications with Mantine UI - a modern React component library with 100+ customizable components.

## Installation & Setup

### Install Packages

```bash
npm install @mantine/core @mantine/hooks
# Optional packages
npm install @mantine/form @mantine/dates @mantine/charts
npm install @mantine/notifications @mantine/modals @mantine/dropzone
```

### Basic Setup

Wrap your app with `MantineProvider`:

```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}>
      {/* Your app here */}
    </MantineProvider>
  );
}
```

### Theme Customization

```tsx
const theme = createTheme({
  primaryColor: 'cyan',
  fontFamily: 'Inter, system-ui, sans-serif',
  defaultRadius: 'md',

  components: {
    Button: Button.extend({
      defaultProps: {
        variant: 'filled',
      },
    }),
  },
});
```

### Color Scheme (Dark Mode)

```tsx
<MantineProvider
  defaultColorScheme="auto" // 'light' | 'dark' | 'auto'
  theme={theme}
>
  {/* app */}
</MantineProvider>
```

## Core Components

### Button

```tsx
import { Button } from '@mantine/core';
import { IconPhoto, IconDownload, IconTrash } from '@tabler/icons-react';

// Basic variants
<Button variant="filled">Primary Action</Button>
<Button variant="light">Secondary Action</Button>
<Button variant="outline">Tertiary Action</Button>
<Button variant="subtle">Subtle Action</Button>
<Button variant="default">Default</Button>
<Button variant="gradient" gradient={{ from: 'blue', to: 'cyan', deg: 90 }}>
  Gradient
</Button>

// Colors
<Button color="blue">Blue</Button>
<Button color="red">Red</Button>
<Button color="green">Green</Button>
<Button color="gray">Gray</Button>

// With icons
<Button leftSection={<IconPhoto size={14} />}>Upload Photo</Button>
<Button rightSection={<IconDownload size={14} />}>Download</Button>

// Sizes
<Button size="xs">Extra Small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium (default)</Button>
<Button size="lg">Large</Button>
<Button size="xl">Extra Large</Button>
<Button size="compact-sm">Compact Small</Button>

// States
<Button loading>Processing...</Button>
<Button disabled>Disabled</Button>
<Button fullWidth>Full Width Button</Button>

// As different elements
<Button component="a" href="https://mantine.dev">External Link</Button>
<Button component={Link} to="/dashboard">React Router Link</Button>

// Button groups
<Button.Group>
  <Button variant="default">Left</Button>
  <Button variant="default">Center</Button>
  <Button variant="default">Right</Button>
</Button.Group>

<Button.Group orientation="vertical">
  <Button>Top</Button>
  <Button>Middle</Button>
  <Button>Bottom</Button>
</Button.Group>

// Common patterns
<Group justify="space-between">
  <Button variant="subtle" color="gray">Cancel</Button>
  <Button>Save Changes</Button>
</Group>

<Group justify="flex-end" mt="md">
  <Button variant="outline">Back</Button>
  <Button>Continue</Button>
</Group>

// Icon-only button
<Button variant="light" p={8}>
  <IconTrash size={16} />
</Button>
```

**Props:** variant, size, color, radius, fullWidth, disabled, loading, leftSection, rightSection, component, justify

### Input Components

```tsx
import {
  TextInput,
  Textarea,
  NumberInput,
  PasswordInput,
  Select,
  MultiSelect,
  Checkbox,
  Switch,
  Radio,
} from '@mantine/core';
import { IconMail, IconLock, IconAt, IconSearch } from '@tabler/icons-react';

// TextInput - single line text
<TextInput
  label="Email Address"
  placeholder="your@email.com"
  description="We'll never share your email"
  error="Invalid email format"
  withAsterisk
  leftSection={<IconMail size={16} />}
/>

<TextInput
  label="Username"
  placeholder="@username"
  leftSection={<IconAt size={16} />}
  rightSection={<IconSearch size={16} />}
/>

// Textarea - multi-line text
<Textarea
  label="Bio"
  placeholder="Tell us about yourself"
  description="Brief description for your profile"
  minRows={3}
  maxRows={6}
  autosize
/>

<Textarea
  label="Message"
  placeholder="Your message"
  rows={4}
  maxLength={500}
  withAsterisk
/>

// NumberInput
<NumberInput
  label="Age"
  placeholder="Your age"
  min={0}
  max={120}
  defaultValue={18}
/>

<NumberInput
  label="Price"
  placeholder="0.00"
  prefix="$"
  decimalScale={2}
  fixedDecimalScale
  thousandSeparator=","
/>

// PasswordInput
<PasswordInput
  label="Password"
  placeholder="Enter password"
  description="Must be at least 8 characters"
  withAsterisk
  leftSection={<IconLock size={16} />}
/>

// Select
<Select
  label="Country"
  placeholder="Select country"
  data={['USA', 'Canada', 'Mexico', 'UK']}
  searchable
  clearable
/>

<Select
  label="Role"
  placeholder="Pick one"
  data={[
    { value: 'admin', label: 'Administrator' },
    { value: 'user', label: 'Regular User' },
    { value: 'guest', label: 'Guest' },
  ]}
/>

// MultiSelect
<MultiSelect
  label="Skills"
  placeholder="Select all that apply"
  data={['React', 'TypeScript', 'Node.js', 'Python', 'Go']}
  searchable
  clearable
  maxValues={3}
/>

// Checkbox
<Checkbox label="I agree to terms and conditions" />
<Checkbox
  label="Subscribe to newsletter"
  description="Get weekly updates"
  indeterminate
/>

// Checkbox group
<Checkbox.Group
  label="Select your favorite frameworks"
  description="Choose all that you like"
  withAsterisk
>
  <Group mt="xs">
    <Checkbox value="react" label="React" />
    <Checkbox value="vue" label="Vue" />
    <Checkbox value="angular" label="Angular" />
  </Group>
</Checkbox.Group>

// Switch
<Switch label="Enable notifications" />
<Switch
  label="Auto-save"
  description="Automatically save changes"
  onLabel="ON"
  offLabel="OFF"
/>

// Radio
<Radio.Group
  label="Subscription Plan"
  description="Select your preferred plan"
  withAsterisk
>
  <Group mt="xs">
    <Radio value="free" label="Free" />
    <Radio value="pro" label="Pro - $10/month" />
    <Radio value="enterprise" label="Enterprise" />
  </Group>
</Radio.Group>

// Input variants
<TextInput label="Default" variant="default" placeholder="Default" />
<TextInput label="Filled" variant="filled" placeholder="Filled" />
<TextInput label="Unstyled" variant="unstyled" placeholder="Unstyled" />

// Input states
<TextInput label="Disabled" disabled placeholder="Can't type here" />
<TextInput label="Read only" readOnly value="Read-only value" />
<TextInput label="With error" error="This field is required" />
```

**Common input props:**
- `label` - input label text
- `placeholder` - hint text
- `description` - helper text below input
- `error` - error message (string or boolean)
- `withAsterisk` - show required asterisk
- `disabled` / `readOnly` - input states
- `leftSection` / `rightSection` - icons or controls
- `variant` - default, filled, unstyled
- `size` - xs, sm, md, lg, xl
- `radius` - border radius

**Textarea-specific props:**
- `minRows` / `maxRows` - control height
- `autosize` - auto-adjust height
- `maxLength` - character limit

### Modal, Table, Card, AppShell

```tsx
// Modal
const [opened, { open, close }] = useDisclosure(false);
<Modal opened={opened} onClose={close} title="Title" centered size="lg">
  Content
</Modal>

// Table
const data = { head: ['Name', 'Email'], body: [['John', 'john@email.com']] };
<Table data={data} striped highlightOnHover withTableBorder />

// Card
<Card shadow="sm" padding="lg" withBorder>
  <Card.Section><Image src="img.jpg" height={160} /></Card.Section>
  <Text fw={500}>Title</Text>
  <Button fullWidth mt="md">Action</Button>
</Card>

// AppShell - app layout
const [opened, { toggle }] = useDisclosure();
<AppShell
  header={{ height: 60 }}
  navbar={{ width: 300, breakpoint: 'sm', collapsed: { mobile: !opened } }}
>
  <AppShell.Header><Burger opened={opened} onClick={toggle} /></AppShell.Header>
  <AppShell.Navbar>Nav</AppShell.Navbar>
  <AppShell.Main>Content</AppShell.Main>
</AppShell>
```

## Layout Components

```tsx
import { Group, Stack, Flex, Grid, Container, Box, Paper, Space, Divider } from '@mantine/core';

// Group - horizontal layout
<Group justify="center" gap="md">
  <Button>Button 1</Button>
  <Button>Button 2</Button>
  <Button>Button 3</Button>
</Group>

<Group justify="space-between">
  <Text>Left aligned</Text>
  <Button>Right aligned</Button>
</Group>

<Group justify="flex-end" gap="sm">
  <Button variant="outline">Cancel</Button>
  <Button>Save</Button>
</Group>

// Group with equal width children
<Group grow>
  <Button>Equal</Button>
  <Button>Width</Button>
  <Button>Buttons</Button>
</Group>

// Stack - vertical layout
<Stack gap="md">
  <TextInput label="Email" />
  <PasswordInput label="Password" />
  <Button>Submit</Button>
</Stack>

<Stack align="center" gap="xs">
  <Avatar size="xl" />
  <Text fw={500}>John Doe</Text>
  <Text c="dimmed" size="sm">john@example.com</Text>
</Stack>

<Stack align="stretch" gap="md">
  <Paper p="md" withBorder>Item 1</Paper>
  <Paper p="md" withBorder>Item 2</Paper>
</Stack>

// Flex - full flexbox control
<Flex
  direction="row"
  wrap="wrap"
  justify="space-between"
  align="center"
  gap="md"
>
  <Box>Item 1</Box>
  <Box>Item 2</Box>
  <Box>Item 3</Box>
</Flex>

<Flex direction="column" gap="sm">
  <Text>Vertical flex</Text>
  <Text>Item 2</Text>
</Flex>

// Responsive flex direction
<Flex
  direction={{ base: 'column', sm: 'row' }}
  gap={{ base: 'sm', sm: 'lg' }}
  justify={{ sm: 'center' }}
>
  <Button>Responsive</Button>
  <Button>Layout</Button>
</Flex>

// Grid - 12 column system
<Grid>
  <Grid.Col span={12}>Full width</Grid.Col>
  <Grid.Col span={6}>Half</Grid.Col>
  <Grid.Col span={6}>Half</Grid.Col>
  <Grid.Col span={4}>1/3</Grid.Col>
  <Grid.Col span={4}>1/3</Grid.Col>
  <Grid.Col span={4}>1/3</Grid.Col>
</Grid>

// Responsive grid
<Grid>
  <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
    <Paper p="md">Responsive column</Paper>
  </Grid.Col>
  <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
    <Paper p="md">Responsive column</Paper>
  </Grid.Col>
  <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
    <Paper p="md">Responsive column</Paper>
  </Grid.Col>
  <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
    <Paper p="md">Responsive column</Paper>
  </Grid.Col>
</Grid>

// Grid with gaps
<Grid gutter="xl">
  <Grid.Col span={6}><Paper p="md">Large gaps</Paper></Grid.Col>
  <Grid.Col span={6}><Paper p="md">Between columns</Paper></Grid.Col>
</Grid>

// Container - centered content with max-width
<Container size="xs">Extra small (540px)</Container>
<Container size="sm">Small (720px)</Container>
<Container size="md">Medium (960px)</Container>
<Container size="lg">Large (1140px)</Container>
<Container size="xl">Extra large (1320px)</Container>

// Box - universal layout wrapper
<Box p="md" bg="blue" c="white">
  Padded box with background
</Box>

<Box
  style={(theme) => ({
    padding: theme.spacing.md,
    backgroundColor: theme.colors.gray[0],
  })}
>
  Box with theme function
</Box>

// Common layout patterns

// Form layout
<Stack gap="md">
  <TextInput label="Name" />
  <TextInput label="Email" />
  <Textarea label="Message" />
  <Group justify="flex-end">
    <Button variant="outline">Cancel</Button>
    <Button>Submit</Button>
  </Group>
</Stack>

// Card grid layout
<Grid>
  {items.map((item) => (
    <Grid.Col key={item.id} span={{ base: 12, sm: 6, md: 4 }}>
      <Card>
        <Text>{item.title}</Text>
      </Card>
    </Grid.Col>
  ))}
</Grid>

// Header with logo and navigation
<Group justify="space-between" p="md">
  <Group>
    <Image src="logo.png" h={30} />
    <Text fw={700}>Brand</Text>
  </Group>
  <Group gap="xs">
    <Button variant="subtle">Home</Button>
    <Button variant="subtle">About</Button>
    <Button variant="subtle">Contact</Button>
  </Group>
</Group>

// Space and Divider utilities
<Stack>
  <Text>Section 1</Text>
  <Space h="md" />
  <Text>Section 2</Text>
  <Divider my="md" />
  <Text>Section 3</Text>
</Stack>
```

**Component Props:**
- **Group**: justify, gap, grow, wrap, align
- **Stack**: gap, align, justify
- **Flex**: direction, wrap, justify, align, gap
- **Grid**: gutter (spacing between columns)
- **Grid.Col**: span (1-12 or responsive object), offset
- **Container**: size (xs, sm, md, lg, xl, or number)
- **Box**: style props (p, m, bg, c, w, h, etc.)

## Form Handling

```tsx
import { useForm } from '@mantine/form';
import { TextInput, Button, Stack } from '@mantine/core';

const form = useForm({
  initialValues: { email: '', name: '' },
  validate: {
    email: (val) => (/^\S+@\S+$/.test(val) ? null : 'Invalid email'),
    name: (val) => (val.length < 2 ? 'Too short' : null),
  },
});

<form onSubmit={form.onSubmit((values) => console.log(values))}>
  <Stack>
    <TextInput label="Email" {...form.getInputProps('email')} />
    <TextInput label="Name" {...form.getInputProps('name')} />
    <Button type="submit">Submit</Button>
  </Stack>
</form>

// Nested: form.getInputProps('user.firstName')
// Lists: form.insertListItem('items', {...}), form.removeListItem('items', idx)
// Methods: getValues(), validate(), setFieldValue(path, val), reset(), isDirty()
```

## Common Hooks & Responsive Design

```tsx
import { useDisclosure, useToggle, useClipboard, useLocalStorage, useMediaQuery } from '@mantine/hooks';

// Boolean state
const [opened, { toggle, open, close }] = useDisclosure(false);

// Toggle values
const [theme, toggle] = useToggle(['light', 'dark']);

// Clipboard
const clipboard = useClipboard();
clipboard.copy('text'); // clipboard.copied is boolean

// localStorage
const [value, setValue] = useLocalStorage({ key: 'my-key', defaultValue: 'default' });

// Media queries
const isMobile = useMediaQuery('(max-width: 768px)');

// Responsive props (breakpoints: base, xs, sm, md, lg, xl)
<Button size={{ base: 'sm', lg: 'lg' }}>Responsive</Button>
<Box hiddenFrom="sm">Hidden on sm+</Box>
<Box visibleFrom="md">Visible on md+</Box>
```

## Styling

### Style Props (Recommended)

```tsx
import { Box, Text, Paper } from '@mantine/core';

// Spacing
<Box p="md">Padding medium</Box>
<Box px="lg" py="xs">Horizontal lg, vertical xs</Box>
<Box m="xl">Margin extra large</Box>
<Box mt="md" mb="sm">Margin top md, bottom sm</Box>

// Size
<Box w={200} h={100}>Fixed size</Box>
<Box w="100%" h="50vh">Relative size</Box>
<Box maw={500} mah={300}>Max width/height</Box>
<Box miw={200} mih={100}>Min width/height</Box>

// Colors
<Box bg="blue">Blue background</Box>
<Box bg="blue.5">Blue shade 5</Box>
<Box bg="rgba(0, 0, 0, 0.5)">Transparent black</Box>
<Text c="dimmed">Dimmed text color</Text>
<Text c="blue">Blue text</Text>
<Text c="red.7">Red shade 7</Text>

// Typography
<Text size="xs">Extra small</Text>
<Text size="sm">Small</Text>
<Text size="md">Medium</Text>
<Text size="lg">Large</Text>
<Text size="xl">Extra large</Text>
<Text fw={700}>Bold</Text>
<Text fs="italic">Italic</Text>
<Text td="underline">Underlined</Text>
<Text tt="uppercase">Uppercase</Text>

// Borders
<Paper withBorder>With border</Paper>
<Box style={{ borderRadius: 8 }}>Rounded corners</Box>

// Display & Position
<Box display="flex">Display flex</Box>
<Box pos="relative">Relative position</Box>
<Box pos="absolute" top={0} left={0}>Absolute</Box>

// Common patterns
<Box
  p="md"
  bg="gray.0"
  style={{ borderRadius: 8 }}
>
  Card-like box
</Box>

<Paper
  p="xl"
  shadow="sm"
  radius="md"
  withBorder
>
  Styled paper
</Paper>
```

**Common style props:**
- **Spacing**: p, px, py, pt, pr, pb, pl, m, mx, my, mt, mr, mb, ml
- **Size**: w, h, maw, mah, miw, mih
- **Colors**: bg (background), c (color)
- **Text**: size, fw (font-weight), fs (font-style), td (text-decoration), tt (text-transform)

### Inline Styles

```tsx
// Simple inline styles
<Button style={{ backgroundColor: 'red', color: 'white' }}>
  Custom Button
</Button>

// With theme function
<Box
  style={(theme) => ({
    padding: theme.spacing.md,
    backgroundColor: theme.colors.blue[0],
    borderRadius: theme.radius.md,
    '&:hover': {
      backgroundColor: theme.colors.blue[1],
    },
  })}
>
  Themed box
</Box>

// Responsive styles
<Box
  style={{
    padding: 16,
    '@media (max-width: 768px)': {
      padding: 8,
    },
  }}
>
  Responsive padding
</Box>
```

### Styles API (Component-Specific)

```tsx
// Target specific parts of a component
<Button
  classNames={{
    root: 'my-button-root',
    inner: 'my-button-inner',
    label: 'my-button-label',
  }}
  styles={{
    root: {
      backgroundColor: 'blue',
      border: '2px solid darkblue',
    },
    label: {
      fontSize: 18,
      fontWeight: 700,
    },
  }}
>
  Fully Custom Button
</Button>

<TextInput
  styles={{
    input: {
      backgroundColor: '#f0f0f0',
      border: '1px solid #ccc',
    },
    label: {
      fontSize: 14,
      fontWeight: 600,
    },
  }}
  label="Custom Input"
/>

// With theme function
<Card
  styles={(theme) => ({
    root: {
      backgroundColor: theme.colors.gray[0],
      '&:hover': {
        backgroundColor: theme.colors.gray[1],
      },
    },
  })}
>
  Custom card
</Card>
```

### CSS Modules

```tsx
// Demo.module.css
/*
.button {
  background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 12px 24px;
  border-radius: 8px;
  font-weight: 600;
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
*/

import classes from './Demo.module.css';

<Button className={classes.button}>
  Styled with CSS Module
</Button>

// Combine with classNames API
<Button
  className={classes.button}
  classNames={{
    label: classes.label,
  }}
>
  Combined styling
</Button>
```

### Using Theme Colors

```tsx
import { useMantineTheme } from '@mantine/core';

function Demo() {
  const theme = useMantineTheme();

  return (
    <Box
      style={{
        backgroundColor: theme.colors.blue[6],
        color: theme.white,
        padding: theme.spacing.md,
        borderRadius: theme.radius.md,
      }}
    >
      Using theme values
    </Box>
  );
}

// Color array access
<Box bg="blue.0">Lightest blue</Box>
<Box bg="blue.5">Medium blue</Box>
<Box bg="blue.9">Darkest blue</Box>
```

### Global Styles

```tsx
// In your app root or _app.tsx
import { MantineProvider, createTheme, Global } from '@mantine/core';

const theme = createTheme({
  fontFamily: 'Inter, sans-serif',
  headings: { fontFamily: 'Greycliff CF, sans-serif' },
});

function App() {
  return (
    <MantineProvider theme={theme}>
      <Global
        styles={(theme) => ({
          body: {
            backgroundColor: theme.colors.gray[0],
          },
          'h1, h2, h3': {
            color: theme.colors.dark[8],
          },
        })}
      />
      {/* Your app */}
    </MantineProvider>
  );
}
```

## Additional Components

```tsx
import { notifications } from '@mantine/notifications';
import { Drawer, Menu, Tooltip, Tabs, Badge, Avatar } from '@mantine/core';

// Notifications
notifications.show({ title: 'Success', message: 'Done!', color: 'green' });

// Drawer
<Drawer opened={opened} onClose={close} title="Drawer">Content</Drawer>

// Menu
<Menu>
  <Menu.Target><Button>Menu</Button></Menu.Target>
  <Menu.Dropdown>
    <Menu.Item>Settings</Menu.Item>
    <Menu.Item color="red">Delete</Menu.Item>
  </Menu.Dropdown>
</Menu>

// Tooltip & Badge
<Tooltip label="Info"><Button>Hover</Button></Tooltip>
<Badge>New</Badge>
<Badge color="red">Urgent</Badge>

// Avatar
<Avatar src="avatar.jpg" radius="xl" />
<Avatar color="blue">JD</Avatar>
```

## Best Practices

1. **Always wrap with MantineProvider** at app root
2. **Import CSS** - `import '@mantine/core/styles.css'`
3. **Use theme for consistency** - define colors, fonts, radius in theme
4. **Leverage hooks** - useDisclosure, useForm, etc. for common patterns
5. **Responsive by default** - use responsive props and breakpoints
6. **Accessibility** - always provide labels or aria-labels
7. **Form validation** - use useForm for robust form handling
8. **CSS Modules over inline styles** for better performance
9. **Use Styles API** for component-specific customization
10. **Polymorphic components** - use `component` prop to render as different elements

