Skip to content
Back to skills

Mantine

ASecurity

Build React applications with the Mantine UI component library. Use when a user asks to use Mantine components, configure themes, implement forms with validation, add notifications, or build responsive layouts with Mantine hooks.

  • 142 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 29, 2026
developmenttypescriptgoshellbashreactnextjsgit

Works with

  • cursor
  • terminal
  • cli

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill mantine --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mantine?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mantine
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-mantine/badge)](https://www.skillsdirectory.com/skills/terminalskills-mantine)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mantine
description: >-
  Build React applications with the Mantine UI component library. Use when a user asks to use Mantine components, configure themes, implement forms with validation, add notifications, or build responsive layouts with Mantine hooks.
license: Apache-2.0
compatibility: "React 19.2+ (Mantine 9); a bundler with PostCSS such as Vite or Next.js"
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: development
  tags: ["react", "ui-library", "components", "hooks", "typescript"]
  repository: https://github.com/mantinedev/mantine
---
# Mantine — Full-Featured React Component Library

## Overview

Mantine is a React component library with 120+ customizable components and 70+ hooks, plus packages for forms, notifications, dates, charts and rich text editing, all written in TypeScript. Since version 7 it ships plain CSS files instead of CSS-in-JS: styles are imported once and customized through the theme, CSS variables and CSS modules. This skill targets Mantine 9 (released March 2026), which requires React 19.2 or later; Example 2 and the Guidelines cover what changed since 7 and 8.

## Instructions

### Setup

```bash
npm install @mantine/core @mantine/hooks @mantine/form @mantine/notifications
npm install @mantine/dates dayjs          # Date components
npm install @mantine/tiptap @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link  # Rich text editor
npm install --save-dev postcss postcss-preset-mantine postcss-simple-vars
```

The PostCSS preset adds `rem()`, `light-dark()`, `@mixin hover` and the breakpoint variables to your own CSS files. Create `postcss.config.cjs` in the project root:

```js
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",
      },
    },
  },
};
```

### Theme and Provider

```tsx
// src/App.tsx — styles, theme and providers, set up once at the root
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css"; // package styles go after core styles

import { Button, MantineProvider, TextInput, createTheme } from "@mantine/core";
import { Notifications } from "@mantine/notifications";
import { Dashboard } from "./Dashboard";

const theme = createTheme({
  primaryColor: "brand",
  fontFamily: "Inter, system-ui, sans-serif",
  colors: {
    // a custom color needs at least 10 shades, lightest first
    brand: ["#f0f0ff", "#d6d6ff", "#b3b3ff", "#8080ff", "#4d4dff", "#1a1aff", "#0000e6", "#0000b3", "#000080", "#00004d"],
  },
  components: {
    Button: Button.extend({ defaultProps: { variant: "light" } }),
    TextInput: TextInput.extend({ defaultProps: { size: "md" } }),
  },
});

export default function App() {
  return (
    <MantineProvider theme={theme} defaultColorScheme="auto">
      <Notifications position="top-right" />
      <Dashboard />
    </MantineProvider>
  );
}
```

### Components

```tsx
// src/Dashboard.tsx — responsive shell: header, collapsible navbar, stat cards
import { AppShell, Avatar, Badge, Burger, Card, Group, Menu, NavLink, SimpleGrid, Text } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { GearIcon, SignOutIcon } from "@phosphor-icons/react";

const stats = [
  { title: "Revenue", value: "$45,200", change: "+12%" },
  { title: "Users", value: "1,234", change: "+5%" },
  { title: "Orders", value: "892", change: "+18%" },
  { title: "Churn", value: "2.1%", change: "-0.3%" },
];

export function Dashboard() {
  const [opened, { toggle }] = useDisclosure();

  return (
    <AppShell header={{ height: 60 }} navbar={{ width: 260, breakpoint: "sm", collapsed: { mobile: !opened } }} padding="md">
      <AppShell.Header>
        <Group h="100%" px="md" justify="space-between">
          <Group>
            <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" aria-label="Toggle navigation" />
            <Text size="xl" fw={700}>Dashboard</Text>
          </Group>
          <Menu>
            <Menu.Target>
              <Avatar name="Dana Whitfield" color="initials" radius="xl" style={{ cursor: "pointer" }} />
            </Menu.Target>
            <Menu.Dropdown>
              <Menu.Item leftSection={<GearIcon size={14} />}>Settings</Menu.Item>
              <Menu.Divider />
              <Menu.Item color="red" leftSection={<SignOutIcon size={14} />}>Log out</Menu.Item>
            </Menu.Dropdown>
          </Menu>
        </Group>
      </AppShell.Header>

      <AppShell.Navbar p="md">
        <NavLink label="Overview" active />
        <NavLink label="Orders" />
        <NavLink label="Customers" />
      </AppShell.Navbar>

      <AppShell.Main>
        <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
          {stats.map((stat) => (
            <Card key={stat.title} withBorder>
              <Text size="sm" c="dimmed">{stat.title}</Text>
              <Group justify="space-between" mt="xs">
                <Text size="xl" fw={700}>{stat.value}</Text>
                <Badge color={stat.title === "Churn" || stat.change.startsWith("+") ? "teal" : "red"}>{stat.change}</Badge>
              </Group>
            </Card>
          ))}
        </SimpleGrid>
      </AppShell.Main>
    </AppShell>
  );
}
```

### Form Handling

```tsx
// src/SignupForm.tsx — uncontrolled form with validation
import { Button, Checkbox, PasswordInput, Select, Stack, TextInput } from "@mantine/core";
import { hasLength, isEmail, isNotEmpty, useForm } from "@mantine/form";
import { notifications } from "@mantine/notifications";

export function SignupForm() {
  const form = useForm({
    mode: "uncontrolled",
    initialValues: { name: "", email: "", password: "", role: "", terms: false },
    validate: {
      name: hasLength({ min: 2 }, "Name too short"),
      email: isEmail("Invalid email"),
      password: hasLength({ min: 8 }, "At least 8 characters"),
      role: isNotEmpty("Select a role"),
      terms: (value) => (value ? null : "You must accept the terms"),
    },
  });

  const handleSubmit = form.onSubmit((values) => {
    notifications.show({ title: "Account created", message: `Welcome, ${values.name}`, color: "teal" });
  });

  return (
    <form onSubmit={handleSubmit}>
      <Stack gap="sm">
        <TextInput label="Name" key={form.key("name")} {...form.getInputProps("name")} />
        <TextInput label="Email" placeholder="dana@northwind.io" key={form.key("email")} {...form.getInputProps("email")} />
        <PasswordInput label="Password" key={form.key("password")} {...form.getInputProps("password")} />
        <Select label="Role" data={["Developer", "Designer", "Product Manager"]} key={form.key("role")} {...form.getInputProps("role")} />
        <Checkbox label="I accept the terms" key={form.key("terms")} {...form.getInputProps("terms", { type: "checkbox" })} />
        <Button type="submit">Create account</Button>
      </Stack>
    </form>
  );
}
```

### Hooks

```tsx
// src/SearchBox.tsx — a few of the 70+ hooks in @mantine/hooks
import { useRef, useState } from "react";
import { Button, Group, TextInput } from "@mantine/core";
import { getHotkeyHandler, useClipboard, useDebouncedValue, useDidUpdate, useHotkeys, useLocalStorage, useMediaQuery } from "@mantine/hooks";

export function SearchBox({ onSearch }: { onSearch: (query: string) => void }) {
  const input = useRef<HTMLInputElement>(null);
  const [search, setSearch] = useState("");
  const [debounced] = useDebouncedValue(search, 300); // updates 300 ms after typing stops
  const clipboard = useClipboard({ timeout: 2000 }); // clipboard.copied stays true for 2 s
  const isMobile = useMediaQuery("(max-width: 48em)"); // false on the server and until the first effect runs

  // without defaultValue the value is typed `string[] | undefined`
  const [recent, setRecent] = useLocalStorage<string[]>({ key: "recent-searches", defaultValue: [] });

  // document-level shortcut; ignored while focus is in an input, textarea or select
  useHotkeys([["mod+K", () => input.current?.focus()]]);

  useDidUpdate(() => onSearch(debounced), [debounced]); // search as you type; skips the first render

  const submit = () => {
    onSearch(search); // not `debounced`: for 300 ms after the last key it still holds the old text
    setRecent([search, ...recent].slice(0, 5));
  };

  return (
    <Group>
      <TextInput
        ref={input}
        placeholder={recent[0] ?? "Search orders"}
        size={isMobile ? "md" : "sm"}
        value={search}
        onChange={(event) => setSearch(event.currentTarget.value)}
        onKeyDown={getHotkeyHandler([["Enter", submit]])} // shortcuts inside an input
      />
      <Button onClick={() => clipboard.copy(search)}>{clipboard.copied ? "Copied" : "Copy query"}</Button>
    </Group>
  );
}
```

## Examples

### Example 1: Add Mantine and a dark-mode toggle to a Vite app

User: "Add Mantine to my Vite + React + TypeScript app and give me a dark mode switch in the header."

```bash
npm install @mantine/core @mantine/hooks @phosphor-icons/react
npm install --save-dev postcss postcss-preset-mantine postcss-simple-vars
```

Add `postcss.config.cjs` from Setup, import `@mantine/core/styles.css` in the root file, wrap the app in `MantineProvider` with `defaultColorScheme="auto"`, then:

```tsx
// src/ColorSchemeToggle.tsx
import { ActionIcon, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
import { MoonIcon, SunIcon } from "@phosphor-icons/react";

export function ColorSchemeToggle() {
  const { setColorScheme } = useMantineColorScheme();
  // "auto" can mean either; the computed value is always "light" or "dark"
  const computed = useComputedColorScheme("light", { getInitialValueInEffect: true });

  return (
    <ActionIcon variant="default" size="lg" aria-label="Toggle color scheme" onClick={() => setColorScheme(computed === "light" ? "dark" : "light")}>
      {computed === "light" ? <MoonIcon size={18} /> : <SunIcon size={18} />}
    </ActionIcon>
  );
}
```

Result: the app follows the operating system's scheme until the button is clicked; the choice is then kept in `localStorage` and `<html>` carries `data-mantine-color-scheme="dark"` or `"light"`.

### Example 2: Upgrade a Mantine 7 project to 9

User: "We're still on Mantine 7 and React 18. Move us to the current version."

```bash
npm install react@^19.2.0 react-dom@^19.2.0 @types/react@^19 @types/react-dom@^19
npm install @mantine/core@9 @mantine/hooks@9 @mantine/form@9 @mantine/dates@9 @mantine/notifications@9
npx tsc --noEmit   # Vite template (tsconfig with project references): npx tsc -b
```

Update `@tiptap/*` to 3.x and `recharts` to 3.x if `@mantine/tiptap` or `@mantine/charts` are installed. The compiler lists most of what broke, for example:

```
error TS2305: Module '"@mantine/core"' has no exported member 'TypographyStylesProvider'.
error TS2322: Type '{ in: boolean; children: Element; }' is not assignable to type '… CollapseProps …'.
  Property 'in' does not exist on type '… CollapseProps …'.
error TS2322: Type 'Dispatch<SetStateAction<[Date | null, Date | null]>>' is not assignable to type '(value: DatesRangeValue<string>) => void'.
```

Fixes: `TypographyStylesProvider` → `Typography`; `<Collapse in>` → `expanded`; `<Grid gutter>` → `gap`; `<Spoiler initialState>` → `defaultExpanded`; `useFullscreen` → `useFullscreenDocument` or `useFullscreenElement`; `useHeadroom()` returns `{ pinned, scrollProgress }`; `zodResolver(schema)` → `schemaResolver(schema, { sync: true })` from `@mantine/form`; `@mantine/dates` values are strings such as `"2026-10-01"` since 8.0, so `useState<Date | null>` becomes `useState<string | null>`. `<Text color="red">` still compiles but no longer sets the color — search for it and use `c="red"`.

## Guidelines

1. **Use the form library** — `@mantine/form` handles validation, touched state, and nested fields; don't build your own. Prefer `mode: "uncontrolled"` with `key={form.key(...)}` on every input and read values with `form.getValues()`; in this mode `form.values` does not update while typing
2. **Theme over inline styles** — Configure component defaults in the theme with `Component.extend`; avoid prop-based styling on every instance
3. **Hooks for logic** — Mantine's hooks library (`useDisclosure`, `useDebouncedValue`, `useLocalStorage`) reduces boilerplate
4. **AppShell for layouts** — Use `AppShell` for dashboard layouts with navbar, header, aside; `Navbar` and `Header` exist only as `AppShell.Navbar` and `AppShell.Header`, each enabled by its config prop
5. **Notifications system** — Render `<Notifications />` exactly once inside `MantineProvider` and call `notifications.show()` from anywhere; supports queue, auto-close, and custom components
6. **CSS modules, not CSS-in-JS** — Import the `styles.css` of every package you use (`@mantine/core/styles.css` first, then for example `@mantine/dates/styles.css`); missing imports are the usual cause of unstyled date pickers and notifications. `createStyles` and the `sx` prop left `@mantine/core` in 7.0 and live on only in the optional `@mantine/emotion` package
7. **Dark mode built-in** — Use `defaultColorScheme="auto"` for system preference detection. With server rendering add `<ColorSchemeScript />` to `<head>` and spread `mantineHtmlProps` on `<html>` to avoid a flash and a hydration warning
8. **Icons** — Any icon library works. The docs and demos use `@phosphor-icons/react`; `@tabler/icons-react` remains a common choice
9. **Version 9 look changes** — The default radius is now `md` and the `light` variant uses solid colors; set `defaultRadius: "sm"` or pass `cssVariablesResolver={v8CssVariablesResolver}` to keep the 8.x look while migrating
10. **When not to use** — Mantine components are client components: they render on the server but cannot be React server components, and a file that uses hooks or compound components such as `Tabs.Tab` needs `'use client'`. Astro is not supported, and a project that cannot move to React 19.2 has to stay on Mantine 8

Files in this skill

  • SKILL.md7.5 KB
  • _scores.json1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.