Die Oberfläche ist lokalisiert; Skill-Beschreibungen und -Dokumente werden per KI automatisch in Ihre Sprache übersetzt. Code, Befehle, Pfade und Fachbegriffe bleiben im Original, maßgeblich ist das Dokument des Autors.
Skills entdecken
Design und Kreativität · SKILL.md

mantine

mantine

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.

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Skill-Dokumentation

GitHub

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

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:

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

// 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

// 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

// 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

// 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."

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:

// 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."

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
Gespeicherte Skills