mantine

mgd34msu's avatarfrom mgd34msu

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.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill enables the rapid development of professional React applications using the Mantine UI framework. It streamlines the frontend engineering process by providing access to over 100 accessible components, 50+ custom hooks, and a robust theming system, ensuring high-quality UI/UX with built-in dark mode and TypeScript support.

Use Cases

  • Rapid Prototyping: Quickly building feature-rich admin dashboards and web interfaces using pre-configured layout components like AppShell and Grid.
  • Accessible UI Development: Creating web applications that meet high accessibility standards (A11Y) and include native dark mode support out of the box.
  • Complex Form Management: Implementing sophisticated, type-safe forms with built-in validation and seamless integration with libraries like Zod.
  • Custom Design Systems: Developing consistent brand identities across web projects using Mantine's flexible theming engine and CSS Variables support.
namemantine
descriptionBuilds 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}
/>
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

  1. Use CSS modules - Most performant styling approach
  2. Leverage hooks - useDisclosure, useDebouncedValue, useForm for common patterns
  3. Use Styles API - Override component parts with classNames prop
  4. Theme tokens - Use CSS variables for consistent theming
  5. Form validation - Use @mantine/form with Zod for type-safe forms

Reference Files