Skip to content

Mantine 指南

适用范围:Mantine v8.x → v9.x(2026 年 5 月最新 v9.2.1),React 18 / 19 + TypeScript 5+。

130+ 组件全景

Mantine 8 / 9 的 @mantine/core 提供 130+ 组件 分为 10 大类别。下表是按类别速览,详细 API 见 参考Mantine 官网

Layout(布局)

布局是 Mantine 的强项 —— 10 个组件覆盖从全局壳到内部弹性盒的所有场景。

组件用途关键 props
AppShell全局应用壳(含 header / navbar / aside / footer)header, navbar, aside, footer, padding
Container限宽容器size (xs/sm/md/lg/xl/数字), fluid
Flex弹性盒(CSS Flexbox 直出)direction, gap, wrap, align, justify
Grid12 栅格gutter, columns, grow
Group横向排列(gap + align)gap, justify, wrap, grow
SimpleGrid响应式简单网格cols, spacing, verticalSpacing
Stack纵向排列gap, align, justify
Center居中容器inline
Space间距占位h, w
AspectRatio固定宽高比ratio

AppShell 经典用法(顶部导航 + 侧边栏 + 主内容):

tsx
import { AppShell, Burger } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

export default function Layout({ children }: { children: React.ReactNode }) {
  const [opened, { toggle }] = useDisclosure();

  return (
    <AppShell
      header={{ height: 60 }}
      navbar={{ width: 280, breakpoint: "sm", collapsed: { mobile: !opened } }}
      padding="md"
    >
      <AppShell.Header>
        <Burger opened={opened} onClick={toggle} hiddenFrom="sm" />
        Header 内容
      </AppShell.Header>
      <AppShell.Navbar p="md">侧边栏</AppShell.Navbar>
      <AppShell.Main>{children}</AppShell.Main>
    </AppShell>
  );
}

要点:

  • breakpoint: "sm" - 小屏下导航栏可折叠
  • collapsed: { mobile: !opened } - 移动端默认折叠、按钮触发展开
  • hiddenFrom="sm" - Burger 按钮仅在小屏显示

Inputs(输入)

23+ 个输入组件覆盖所有表单场景。

组件用途
TextInput单行文本输入
Textarea多行文本输入
NumberInput数字输入(支持 step / min / max / prefix / suffix)
PasswordInput密码输入(含可见性切换)
PinInput数字 PIN 验证码输入(6 位等)
FileInput文件选择(按钮形)
JsonInputJSON 编辑器(含校验)
MaskInput输入掩码(电话 / 卡号格式化)
Checkbox单选框
Radio / Radio.Group单选按钮组
Switch开关
Slider / RangeSlider滑块 / 区间滑块
Rating星级评分
SegmentedControl分段控件
ColorPicker / ColorInput颜色选择
HueSlider / AlphaSlider色相 / 透明度滑块
AngleSlider角度滑块
Fieldset字段集(含 legend)
Input底层 Input(用于自定义)
NativeSelect原生 select(不带搜索)

TextInput + useForm 完整示例:

tsx
import { TextInput, NumberInput, Textarea, Button, Stack } from "@mantine/core";
import { useForm } from "@mantine/form";

interface FormValues {
  name: string;
  age: number | "";
  bio: string;
}

export default function ProfileForm() {
  const form = useForm<FormValues>({
    mode: "uncontrolled",
    initialValues: { name: "", age: "", bio: "" },
    validate: {
      name: (value) => (value.length < 2 ? "至少 2 个字符" : null),
      age: (value) => (value === "" || value < 18 ? "必须 ≥ 18 岁" : null),
    },
  });

  return (
    <form onSubmit={form.onSubmit((values) => console.log(values))}>
      <Stack gap="md">
        <TextInput
          label="姓名"
          placeholder="张三"
          required
          {...form.getInputProps("name")}
        />
        <NumberInput
          label="年龄"
          min={0}
          max={120}
          required
          {...form.getInputProps("age")}
        />
        <Textarea
          label="个人简介"
          autosize
          minRows={3}
          maxRows={6}
          {...form.getInputProps("bio")}
        />
        <Button type="submit">提交</Button>
      </Stack>
    </form>
  );
}

Combobox(下拉选择系列)

Combobox 是 Mantine 输入系列里功能最丰富的一支 —— 8 个组件覆盖单选 / 多选 / 自动补全 / 标签 / 树形等所有下拉场景。

组件用途
Select单选下拉(带搜索可选)
MultiSelect多选下拉(带搜索可选)
Autocomplete自动补全输入(不限制只能选 options)
TagsInput标签输入(可创建新标签)
Pill / PillsInput药丸标签 / 药丸输入
TreeSelect树形选择
Combobox底层 Combobox(用于完全自定义)

Select 常见用法:

tsx
import { Select } from "@mantine/core";

<Select
  label="选择国家"
  placeholder="请选择"
  searchable                  // 启用搜索
  clearable                   // 显示清除按钮
  nothingFoundMessage="无匹配"
  data={[
    { value: "cn", label: "中国" },
    { value: "us", label: "美国" },
    { value: "jp", label: "日本" },
  ]}
  onChange={(value) => console.log(value)}
/>;

MultiSelect 多选:

tsx
<MultiSelect
  label="选择标签"
  data={["React", "Vue", "Angular", "Svelte"]}
  searchable
  clearable
  hidePickedOptions          // 已选项从下拉中隐藏
  maxValues={3}              // 最多 3 个
/>

TagsInput 可创建新标签:

tsx
<TagsInput
  label="标签"
  placeholder="按回车添加"
  data={["前端", "后端", "全栈"]}  // 推荐项
  splitChars={[",", " ", "|"]}   // 这些字符也触发添加
  maxTags={5}
/>

Buttons(按钮)

组件用途
Button标准按钮
ActionIcon图标按钮(圆形 / 方形)
CloseButton关闭按钮 (×)
CopyButton复制按钮(含已复制反馈)
FileButton文件选择按钮
UnstyledButton无样式按钮(保留按钮语义)

Button 完整 props 演示:

tsx
<Button
  variant="filled"         // filled / light / outline / subtle / transparent / white / default / gradient
  color="blue"             // 主题色键
  size="md"                // xs / sm / md / lg / xl
  radius="md"
  loading={isSubmitting}   // 加载态(spinner + 禁用)
  loaderProps={{ type: "dots" }}
  leftSection={<IconCheck size={16} />}   // 左侧图标
  rightSection={<IconArrowRight size={16} />}
  fullWidth                // 占满宽度
  disabled
  onClick={handleClick}
>
  提交
</Button>

CopyButton 自带反馈:

tsx
import { Button, CopyButton, Tooltip } from "@mantine/core";

<CopyButton value="要复制的内容" timeout={2000}>
  {({ copied, copy }) => (
    <Tooltip label={copied ? "已复制" : "点击复制"}>
      <Button color={copied ? "teal" : "blue"} onClick={copy}>
        {copied ? "已复制" : "复制"}
      </Button>
    </Tooltip>
  )}
</CopyButton>;
组件用途
Anchor锚点(语义化 a 标签)
Breadcrumbs面包屑
Burger汉堡菜单按钮
NavLink侧边栏导航项(支持嵌套)
Pagination分页
Stepper步骤条
TableOfContents自动生成目录(基于 ScrollSpy)
Tabs选项卡
Tree树视图

NavLink 侧边栏(含嵌套):

tsx
import { NavLink } from "@mantine/core";
import { IconHome, IconUsers, IconSettings } from "@tabler/icons-react";

<>
  <NavLink href="/" label="首页" leftSection={<IconHome size={16} />} />
  <NavLink label="用户管理" leftSection={<IconUsers size={16} />} childrenOffset={28}>
    <NavLink href="/users" label="用户列表" />
    <NavLink href="/roles" label="角色管理" />
  </NavLink>
  <NavLink href="/settings" label="设置" leftSection={<IconSettings size={16} />} />
</>;

Stepper 步骤条:

tsx
import { Stepper, Button, Group } from "@mantine/core";
import { useState } from "react";

const [active, setActive] = useState(0);

<>
  <Stepper active={active} onStepClick={setActive}>
    <Stepper.Step label="第一步" description="填写信息">
      第一步内容
    </Stepper.Step>
    <Stepper.Step label="第二步" description="核对信息">
      第二步内容
    </Stepper.Step>
    <Stepper.Step label="第三步" description="完成">
      已完成
    </Stepper.Step>
    <Stepper.Completed>全部完成</Stepper.Completed>
  </Stepper>

  <Group justify="flex-end" mt="xl">
    <Button variant="default" onClick={() => setActive((s) => Math.max(s - 1, 0))}>
      上一步
    </Button>
    <Button onClick={() => setActive((s) => s + 1)}>下一步</Button>
  </Group>
</>;

Feedback(反馈)

组件用途
Alert警告 / 信息提示框
Loader加载指示器
Notification通知项(搭配 @mantine/notifications
Progress进度条
RingProgress环形进度
SemiCircleProgress半环形进度
Skeleton骨架屏

Alert

tsx
import { Alert } from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";

<Alert
  variant="light"
  color="blue"
  title="提示"
  icon={<IconInfoCircle />}
  withCloseButton
  onClose={() => console.log("关闭")}
>
  请确认你的操作。
</Alert>;

Progress 进度条:

tsx
<Progress value={75} color="blue" size="md" striped animated />

{/* 多色分段 */}
<Progress.Root size="lg">
  <Progress.Section value={40} color="green" />
  <Progress.Section value={30} color="yellow" />
  <Progress.Section value={20} color="red" />
</Progress.Root>

Overlays(覆盖层)

组件用途
Modal模态对话框
Drawer抽屉
Popover弹出框
HoverCard悬停卡片
Menu菜单
Tooltip工具提示
LoadingOverlay加载遮罩
Overlay通用遮罩
Dialog浮动对话框(固定位置)
Affix固定定位元素
FloatingIndicator浮动指示器
FloatingWindow浮动窗口(可拖拽)

Modal 基础用法:

tsx
import { Modal, Button } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

const [opened, { open, close }] = useDisclosure(false);

<>
  <Button onClick={open}>打开 Modal</Button>
  <Modal
    opened={opened}
    onClose={close}
    title="提示"
    centered
    size="md"
    closeOnClickOutside={false}    // 点击遮罩不关闭
    overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
  >
    Modal 内容
  </Modal>
</>;

Menu 菜单:

tsx
import { Menu, Button } from "@mantine/core";
import { IconEdit, IconTrash } from "@tabler/icons-react";

<Menu shadow="md" width={200}>
  <Menu.Target>
    <Button>选项</Button>
  </Menu.Target>
  <Menu.Dropdown>
    <Menu.Label>操作</Menu.Label>
    <Menu.Item leftSection={<IconEdit size={14} />}>编辑</Menu.Item>
    <Menu.Item leftSection={<IconTrash size={14} />} color="red">
      删除
    </Menu.Item>
    <Menu.Divider />
    <Menu.Item disabled>不可用</Menu.Item>
  </Menu.Dropdown>
</Menu>;

Data display(数据展示)

组件用途
Avatar头像
Badge徽章
Card卡片
Accordion手风琴折叠
Image图片
BackgroundImage背景图容器
Indicator指示器(小红点)
Kbd键盘按键样式
ColorSwatch色块
NumberFormatter数字格式化
OverflowList溢出列表
RollingNumber滚动数字
Spoiler展开收起文本
ThemeIcon主题图标容器
Timeline时间线

Card 完整:

tsx
import { Card, Image, Text, Badge, Button, Group } from "@mantine/core";

<Card shadow="sm" padding="lg" radius="md" withBorder>
  <Card.Section>
    <Image src="https://example.com/image.jpg" height={160} alt="封面" />
  </Card.Section>
  <Group justify="space-between" mt="md" mb="xs">
    <Text fw={500}>卡片标题</Text>
    <Badge color="pink">新</Badge>
  </Group>
  <Text size="sm" c="dimmed">
    卡片描述文字
  </Text>
  <Button color="blue" fullWidth mt="md" radius="md">
    立即购买
  </Button>
</Card>;

Accordion

tsx
import { Accordion } from "@mantine/core";

<Accordion defaultValue="item-1" variant="separated">
  <Accordion.Item value="item-1">
    <Accordion.Control>第一项</Accordion.Control>
    <Accordion.Panel>第一项内容</Accordion.Panel>
  </Accordion.Item>
  <Accordion.Item value="item-2">
    <Accordion.Control>第二项</Accordion.Control>
    <Accordion.Panel>第二项内容</Accordion.Panel>
  </Accordion.Item>
</Accordion>;

Typography(排版)

组件用途
Titleh1-h6 标题
Text段落 / 通用文本
Blockquote引用块
Code行内代码
Highlight高亮关键词
List有序 / 无序列表
Mark标记文本
Table表格(基础语义化)
Typography排版容器(应用 prose 样式)
tsx
<Title order={1}>H1 标题</Title>
<Title order={2} c="dimmed">H2 灰色标题</Title>

<Text size="lg" fw={500}>正常文本</Text>
<Text size="sm" c="red.6">红色小字</Text>

<Highlight highlight={["Mantine", "React"]}>
  Mantine 是 React 现代 UI 库
</Highlight>

Miscellaneous(其他)

组件用途
Box通用容器(Style Props 入口)
Collapse折叠
Divider分割线
FocusTrap焦点陷阱
Marquee跑马灯
Paper纸片容器
Portal传送门
ScrollArea自定义滚动条
Transition过渡动画
VisuallyHidden视觉隐藏(a11y 友好)

Style Props 完整 API

Mantine 几乎所有组件都通过 Box 继承 Style Props——这是 Tailwind utility 哲学的 React props 化。

完整 Style Props 列表

tsx
<Box
  // 外边距
  m="md" mt={4} mb="sm" ml="xs" mr={8} mx="lg" my="auto"
  // 内边距
  p="md" pt={4} pb="sm" pl="xs" pr={8} px="lg" py="auto"
  // 尺寸
  w={200} h="100%" miw="200px" maw="600px" mih={100} mah="80vh"
  // 颜色
  c="blue.6"            // text color
  bg="gray.0"           // background
  bd="1px solid blue"   // border
  // 字体
  fz="lg"               // font-size
  fw={700}              // font-weight
  ff="monospace"        // font-family
  lh={1.5}              // line-height
  ta="center"           // text-align
  tt="uppercase"        // text-transform
  td="underline"        // text-decoration
  fs="italic"           // font-style
  lts="0.05em"          // letter-spacing
  // 布局
  display="flex"
  flex={1}
  pos="absolute"
  top={0} left={0} right={0} bottom={0}
>
  内容
</Box>

主题值引用

tsx
{/* 主题 spacing - "xs" / "sm" / "md" / "lg" / "xl" 或数字(px) */}
<Box mt="md" />        {/* = theme.spacing.md */}
<Box mt={16} />        {/* = 16px */}

{/* 主题颜色 - "颜色名.shade" 或纯 CSS 颜色 */}
<Box c="blue.6" />     {/* = theme.colors.blue[6] */}
<Box c="primary.5" />  {/* = theme.colors[theme.primaryColor][5] */}
<Box c="dimmed" />     {/* 特殊值:theme.colors.gray[6] / dark.2 自适应 */}
<Box c="bright" />     {/* 特殊值:自适应高对比文字色 */}
<Box c="#ff0000" />    {/* 直接 CSS 颜色 */}

响应式对象语法

tsx
<Box
  w={{ base: 200, sm: 400, md: 600, lg: 800 }}
  p={{ base: "xs", md: "lg" }}
  c={{ base: "dark", md: "blue.6" }}
>
  小屏 200px、sm 屏 400px ...
</Box>

断点(默认值,可通过 theme.breakpoints 覆盖):

  • xs: 36em / 576px
  • sm: 48em / 768px
  • md: 62em / 992px
  • lg: 75em / 1200px
  • xl: 88em / 1408px

base 是默认值——所有未匹配断点的视口都使用 base。

Style Props 与 className 共存

Style Props 优先级低于 CSS Modules className —— 你可以同时用:

tsx
import classes from "./MyCard.module.css";

<Box className={classes.card} mt="md" p="lg">
  className 设置基础样式、Style Props 覆盖细节
</Box>;

Styles API 三重定制

Mantine 提供三种内部元素样式定制方式,适应不同场景。

方式 1:classNames - 推荐

classNames 接受对象,键是命名插槽(如 root / label / input),值是 CSS Modules 类名:

tsx
// MyButton.module.css
.root { /* 根元素样式 */ }
.label { /* 文本样式 */ }
tsx
import classes from "./MyButton.module.css";

<Button
  classNames={{
    root: classes.root,
    label: classes.label,
  }}
>
  自定义按钮
</Button>;

优势:性能最好(编译时确定)、可命中所有内部 DOM、与 CSS Modules 完美配合。

方式 2:styles - 内联样式

tsx
<Button
  styles={{
    root: {
      backgroundColor: "purple",
      borderColor: "purple",
    },
    label: {
      fontSize: "20px",
      textTransform: "uppercase",
    },
  }}
>
  内联自定义
</Button>

注意:内联样式优先级高于 class —— 同时用 classNames + styles 时 styles 会覆盖 classNames。

方式 3:vars - CSS Variables 覆盖

tsx
<Button
  vars={(theme, props) => ({
    root: {
      "--button-color": theme.colors.violet[6],
      "--button-bg": "transparent",
      "--button-hover": theme.colors.violet[0],
    },
  })}
>
  CSS 变量覆盖
</Button>

优势:精准只改 CSS Variables、不破坏其他样式、性能最好。

Function 形式(响应 props)

classNames / styles 都支持函数形式,接受 theme + 当前组件 props:

tsx
<Button
  classNames={(theme, props) => ({
    root: props.disabled ? classes.disabled : classes.active,
  })}
>
  动态类名
</Button>

Theme 中全局定制

如果同一组件需要全局样式,写到 theme.components

ts
import { createTheme, Button } from "@mantine/core";
import classes from "./global-button.module.css";

export const theme = createTheme({
  components: {
    Button: Button.extend({
      classNames: {
        root: classes.root,
        label: classes.label,
      },
      defaultProps: {
        radius: "md",
        size: "md",
      },
    }),
  },
});

Button.extend() 是 v7+ 引入的辅助函数 —— 比直接写 Button: { classNames: ... } 更类型安全。

createTheme 完整选项

ts
import { createTheme, type MantineColorsTuple } from "@mantine/core";

const brand: MantineColorsTuple = [
  "#e0f7ff", "#b3ecff", "#80dfff", "#4dd2ff", "#26c8ff",
  "#00bfff", "#00a8e8", "#0094cc", "#0080b3", "#006d9e",
];

export const theme = createTheme({
  // ─── 颜色 ────────────────────────────────────
  primaryColor: "brand",        // 默认主色(引用 colors 键)
  primaryShade: 6,              // 默认主色 shade(0-9,可针对 light/dark 分别配置)
  colors: {
    brand,                      // 自定义 brand 色(10 个 shade)
    // 也可以覆盖默认色
    blue: ["#...", /* 10 个 */],
  },
  white: "#ffffff",
  black: "#1A1B1E",

  // ─── 字体 ────────────────────────────────────
  fontFamily: "Inter, sans-serif",
  fontFamilyMonospace: "Fira Code, monospace",
  headings: {
    fontFamily: "Inter, sans-serif",
    fontWeight: "600",
    sizes: {
      h1: { fontSize: "2.5rem", lineHeight: "1.3" },
      h2: { fontSize: "2rem", lineHeight: "1.35" },
      h3: { fontSize: "1.5rem", lineHeight: "1.4" },
      h4: { fontSize: "1.25rem", lineHeight: "1.45" },
      h5: { fontSize: "1.125rem", lineHeight: "1.5" },
      h6: { fontSize: "1rem", lineHeight: "1.55" },
    },
  },
  fontSizes: {
    xs: "0.75rem",
    sm: "0.875rem",
    md: "1rem",
    lg: "1.125rem",
    xl: "1.25rem",
  },
  lineHeights: {
    xs: "1.4",
    sm: "1.45",
    md: "1.55",
    lg: "1.6",
    xl: "1.65",
  },

  // ─── 间距 / 圆角 / 阴影 ──────────────────────
  spacing: {
    xs: "0.625rem",
    sm: "0.75rem",
    md: "1rem",
    lg: "1.25rem",
    xl: "2rem",
  },
  radius: {
    xs: "0.125rem",
    sm: "0.25rem",
    md: "0.5rem",
    lg: "1rem",
    xl: "2rem",
  },
  defaultRadius: "md",
  shadows: {
    xs: "0 1px 3px 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)",
  },

  // ─── 断点 ────────────────────────────────────
  breakpoints: {
    xs: "36em",
    sm: "48em",
    md: "62em",
    lg: "75em",
    xl: "88em",
  },

  // ─── 组件默认 props / 样式 ──────────────────
  components: {
    Button: {
      defaultProps: {
        radius: "md",
        size: "md",
      },
    },
    Card: {
      defaultProps: {
        shadow: "sm",
        radius: "md",
        withBorder: true,
        padding: "lg",
      },
    },
  },

  // ─── 用户自定义字段 ─────────────────────────
  other: {
    customSpacing: "2.5rem",
    brandName: "My Brand",
  },
});

主题嵌套

在子树内覆盖部分主题:

tsx
<MantineProvider theme={theme}>
  <App />
  {/* 子树覆盖 primaryColor */}
  <MantineProvider theme={{ primaryColor: "pink" }}>
    <PinkSection />
  </MantineProvider>
</MantineProvider>

在组件内访问主题

tsx
import { useMantineTheme } from "@mantine/core";

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

  return <div style={{ color: theme.colors.blue[6] }}>主题色</div>;
}

Color Scheme 完整方案

启用方式

tsx
<MantineProvider defaultColorScheme="auto">
  {/* 三种值:light / dark / auto */}
</MantineProvider>

useMantineColorScheme hook

tsx
const {
  colorScheme,        // 当前用户设置 "light" / "dark" / "auto"
  setColorScheme,     // 设置 (cs: 'light' | 'dark' | 'auto') => void
  clearColorScheme,   // 清除持久化 () => void
  toggleColorScheme,  // 切换 light <-> dark () => void
} = useMantineColorScheme();

useComputedColorScheme hook

tsx
// 永远返回 "light" / "dark" 二选一(auto 自动解算)
const computed = useComputedColorScheme("light", { getInitialValueInEffect: true });

关键参数

  • 第一个参数:SSR 初始值(避免 hydration 错位)
  • getInitialValueInEffect: true:在 useEffect 中获取初始值(避免 SSR mismatch)

完整切换按钮

tsx
import { ActionIcon, useMantineColorScheme, useComputedColorScheme } from "@mantine/core";
import { IconSun, IconMoon } from "@tabler/icons-react";

export function ColorSchemeToggle() {
  const { setColorScheme } = useMantineColorScheme();
  const computedColorScheme = useComputedColorScheme("light", {
    getInitialValueInEffect: true,
  });

  const toggle = () => setColorScheme(computedColorScheme === "light" ? "dark" : "light");

  return (
    <ActionIcon onClick={toggle} variant="default" size="lg" aria-label="切换主题">
      {computedColorScheme === "dark" ? <IconSun size={16} /> : <IconMoon size={16} />}
    </ActionIcon>
  );
}

组件级 lightHidden / darkHidden

任意组件可用:

tsx
<Box lightHidden>仅暗色显示</Box>
<Box darkHidden>仅浅色显示</Box>

适合不同主题下显示不同 logo / 图片等场景。

CSS 中适配

CSS Modules 中使用 [data-mantine-color-scheme="dark"] 选择器:

css
.card {
  background: white;
  color: black;
}

[data-mantine-color-scheme="dark"] .card {
  background: var(--mantine-color-dark-7);
  color: white;
}

或者用 light-dark() mixin(postcss-preset-mantine 提供):

css
.card {
  background: light-dark(white, var(--mantine-color-dark-7));
  color: light-dark(black, white);
}

@mantine/form 完整方案

基础用法

tsx
import { useForm } from "@mantine/form";

interface FormValues {
  email: string;
  password: string;
  age: number | "";
  agree: boolean;
}

const form = useForm<FormValues>({
  mode: "uncontrolled",        // "uncontrolled"(推荐)/ "controlled"
  initialValues: {
    email: "",
    password: "",
    age: "",
    agree: false,
  },
  validate: {
    email: (value) => (/^\S+@\S+$/.test(value) ? null : "邮箱格式不正确"),
    password: (value) => (value.length < 6 ? "至少 6 个字符" : null),
    age: (value) =>
      typeof value === "number" && value >= 18 ? null : "必须 ≥ 18 岁",
    agree: (value) => (value ? null : "必须同意条款"),
  },
});

validate 形式

1. 同步函数

ts
validate: {
  email: (value) => (/.../.test(value) ? null : "错误信息"),
}

返回 null / undefined 表示通过,返回字符串表示错误信息。

2. 跨字段引用(接收完整 values)

ts
validate: {
  passwordConfirm: (value, values) =>
    value !== values.password ? "两次密码不一致" : null,
}

3. Zod resolver(推荐生产)

bash
pnpm add zod mantine-form-zod-resolver
ts
import { z } from "zod";
import { zodResolver } from "mantine-form-zod-resolver";

const schema = z.object({
  email: z.string().email("邮箱格式不正确"),
  password: z.string().min(6, "至少 6 个字符"),
});

const form = useForm({
  initialValues: { email: "", password: "" },
  validate: zodResolver(schema),
});

onSubmit

tsx
<form
  onSubmit={form.onSubmit(
    (values) => console.log("提交成功", values),
    (errors) => console.log("校验失败", errors),
  )}
>
  ...
</form>

onSubmit 自动 preventDefault + 触发 validate,校验失败调用第二个参数。

getInputProps - 自动绑定

tsx
<TextInput {...form.getInputProps("email")} />
<Checkbox {...form.getInputProps("agree", { type: "checkbox" })} />

getInputProps 自动展开 value / onChange / error / defaultValue 等。

setValues / setFieldValue

tsx
form.setValues({ email: "new@example.com" });
form.setFieldValue("password", "newPassword");
form.setFieldValue("user.profile.name", "新名字");  // 支持嵌套路径

reset / clearErrors

tsx
form.reset();                  // 重置为 initialValues
form.clearErrors();            // 清空所有错误
form.setFieldError("email", "");

嵌套对象 / 数组

ts
const form = useForm({
  initialValues: {
    user: { name: "", email: "" },
    skills: ["", ""],
  },
});

<TextInput {...form.getInputProps("user.name")} />
<TextInput {...form.getInputProps("skills.0")} />

表单状态判断

tsx
form.isValid();            // 整体是否有效(不设错误)
form.isValid("email");     // 单字段是否有效
form.isTouched();          // 是否有任意字段被触碰
form.isDirty();            // 是否被修改(与初始值比较)
form.isDirty("email");     // 单字段是否被修改
form.isSubmitting;         // 是否正在提交

@mantine/notifications 完整 API

安装与初始化

bash
pnpm add @mantine/notifications
tsx
import "@mantine/notifications/styles.css";
import { Notifications } from "@mantine/notifications";

<MantineProvider>
  <Notifications
    position="top-right"     // 位置:top-left / top-center / top-right / bottom-left / ...
    limit={5}                // 最多同时显示数(超出进入队列)
    autoClose={4000}         // 默认自动关闭毫秒
    zIndex={1000}
    containerWidth={400}
    pauseResetOnHover        // 鼠标悬停暂停 + 重置定时器
  />
  {children}
</MantineProvider>;

notifications.show

tsx
import { notifications } from "@mantine/notifications";

notifications.show({
  id: "save-success",        // 可选 ID(用于后续 hide/update)
  title: "保存成功",
  message: "你的修改已同步",
  color: "green",
  icon: <IconCheck size={18} />,
  autoClose: 3000,           // 单位 ms,false 不自动关闭
  withCloseButton: true,
  withBorder: false,
  position: "top-right",     // 覆盖容器位置
  loading: false,
  classNames: { /* CSS Modules 类名 */ },
  styles: { /* 内联样式 */ },
  onClose: (notif) => console.log("已关闭", notif),
});

notifications.hide / clean / cleanQueue

ts
notifications.hide("save-success");  // 按 ID 隐藏单条
notifications.clean();               // 清空所有
notifications.cleanQueue();          // 仅清空队列(不清空已显示的)

notifications.update

异步任务进度跟踪经典模式:

ts
const id = notifications.show({
  loading: true,
  title: "上传中",
  message: "请稍候",
  autoClose: false,
  withCloseButton: false,
});

// 异步任务完成后更新
setTimeout(() => {
  notifications.update({
    id,
    color: "green",
    title: "上传成功",
    message: "文件已保存",
    icon: <IconCheck size={18} />,
    loading: false,
    autoClose: 3000,
  });
}, 2000);

@mantine/modals 三种模式

安装与初始化

bash
pnpm add @mantine/modals
tsx
import { ModalsProvider } from "@mantine/modals";

<MantineProvider>
  <ModalsProvider>
    {children}
  </ModalsProvider>
</MantineProvider>;

模式 1:modals.open - 自由内容

tsx
import { modals } from "@mantine/modals";
import { Text, Button } from "@mantine/core";

const handleClick = () => {
  modals.open({
    title: "通用 Modal",
    centered: true,
    size: "md",
    children: (
      <>
        <Text size="sm">这是 Modal 内容。</Text>
        <Button mt="md" onClick={() => modals.closeAll()}>
          关闭
        </Button>
      </>
    ),
  });
};

模式 2:modals.openConfirmModal - 确认对话框

最常用——「确认删除?」「确认提交?」等场景:

tsx
modals.openConfirmModal({
  title: "确认删除",
  centered: true,
  children: (
    <Text size="sm">这条记录将被永久删除,无法恢复。确定继续?</Text>
  ),
  labels: { confirm: "删除", cancel: "取消" },
  confirmProps: { color: "red" },
  onCancel: () => console.log("取消"),
  onConfirm: () => {
    // 异步删除
    deleteRecord(id);
  },
});

模式 3:modals.openContextModal - 注册式

预先注册可复用 Modal、各处用 modals.openContextModal({ modal: 'xxx' }) 调用:

tsx
// 1. 定义 Modal 组件
import { ContextModalProps } from "@mantine/modals";
import { Button, Stack, TextInput } from "@mantine/core";

function CreateUserModal({ context, id, innerProps }: ContextModalProps<{ userId: string }>) {
  return (
    <Stack>
      <TextInput label="姓名" />
      <Button onClick={() => context.closeModal(id)}>关闭</Button>
    </Stack>
  );
}

// 2. 在 ModalsProvider 注册
<ModalsProvider modals={{ createUser: CreateUserModal }}>
  {children}
</ModalsProvider>;

// 3. 全局调用
modals.openContextModal({
  modal: "createUser",
  title: "创建用户",
  innerProps: { userId: "123" },
});

// 4. TypeScript 类型扩展
declare module "@mantine/modals" {
  export interface MantineModalsOverride {
    modals: { createUser: typeof CreateUserModal };
  }
}

多层嵌套

modals.open 自动支持多层嵌套——一个 Modal 内打开另一个 Modal:

ts
// 一键关闭所有
modals.closeAll();

// 关闭单个
modals.close(id);

// 修改已打开
modals.updateModal({ modalId: id, title: "新标题" });

@mantine/spotlight 命令面板

安装与初始化

bash
pnpm add @mantine/spotlight
tsx
import "@mantine/spotlight/styles.css";
import { Spotlight, spotlight } from "@mantine/spotlight";
import { IconSearch, IconHome, IconUser } from "@tabler/icons-react";

const actions = [
  {
    id: "home",
    label: "首页",
    description: "回到首页",
    onClick: () => console.log("跳转首页"),
    leftSection: <IconHome size={18} />,
  },
  {
    id: "profile",
    label: "个人资料",
    description: "查看个人资料",
    onClick: () => console.log("跳转个人资料"),
    leftSection: <IconUser size={18} />,
  },
];

<MantineProvider>
  <Spotlight
    actions={actions}
    nothingFound="无匹配结果"
    highlightQuery
    searchProps={{
      leftSection: <IconSearch size={20} />,
      placeholder: "搜索...",
    }}
    shortcut={["mod + K", "mod + P"]}  // ⌘K / Ctrl+P 触发
    limit={7}
  />
  {children}
</MantineProvider>;

分组

tsx
const actions = [
  {
    group: "页面",
    actions: [
      { id: "home", label: "首页", onClick: () => {} },
      { id: "about", label: "关于", onClick: () => {} },
    ],
  },
  {
    group: "操作",
    actions: [
      { id: "create", label: "创建", onClick: () => {} },
    ],
  },
];

触发方式

ts
spotlight.open();
spotlight.close();
spotlight.toggle();

模糊搜索集成

ts
import Fuse from "fuse.js";

const fuse = new Fuse(actions, { keys: ["label", "description"], threshold: 0.4 });

<Spotlight
  filter={(query, actions) => {
    if (!query) return actions;
    return fuse.search(query).map((result) => result.item);
  }}
/>;

Combobox 深度

Mantine Combobox 是底层 Combobox 组件,用于自定义 Select / Autocomplete 等场景。已经有 8 个开箱即用的组件(Select / MultiSelect / Autocomplete / TagsInput / Pill / PillsInput / TreeSelect),通常只在需要完全自定义时才直接用 Combobox

tsx
import { Combobox, useCombobox, InputBase, Input } from "@mantine/core";
import { useState } from "react";

const data = ["苹果", "香蕉", "橘子", "西瓜"];

export function CustomSelect() {
  const combobox = useCombobox({
    onDropdownClose: () => combobox.resetSelectedOption(),
  });
  const [value, setValue] = useState<string | null>(null);

  return (
    <Combobox
      store={combobox}
      onOptionSubmit={(val) => {
        setValue(val);
        combobox.closeDropdown();
      }}
    >
      <Combobox.Target>
        <InputBase
          component="button"
          type="button"
          pointer
          rightSection={<Combobox.Chevron />}
          onClick={() => combobox.toggleDropdown()}
        >
          {value || <Input.Placeholder>选择</Input.Placeholder>}
        </InputBase>
      </Combobox.Target>
      <Combobox.Dropdown>
        <Combobox.Options>
          {data.map((item) => (
            <Combobox.Option value={item} key={item}>
              {item}
            </Combobox.Option>
          ))}
        </Combobox.Options>
      </Combobox.Dropdown>
    </Combobox>
  );
}

何时用 Combobox:自定义触发器(按钮 / 标签等)、自定义 Options 渲染(含图片 / 卡片等)、自定义筛选 / 分组逻辑——超出 Select / MultiSelect 默认能力时。

DataTable 方案选择

Mantine 的 Table 是基础语义化表格,没有内置分页 / 排序 / 筛选 / 虚拟滚动。生产场景通常用以下两个社区方案:

选项 1:mantine-datatable

mantine-datatable - 流行的简单数据表格。

bash
pnpm add mantine-datatable
tsx
import { DataTable } from "mantine-datatable";

<DataTable
  withTableBorder
  borderRadius="sm"
  striped
  highlightOnHover
  records={data}
  columns={[
    { accessor: "name", title: "姓名" },
    { accessor: "age", title: "年龄", textAlign: "right" },
    { accessor: "email", title: "邮箱" },
  ]}
  totalRecords={data.length}
  recordsPerPage={10}
  page={page}
  onPageChange={setPage}
/>;

选项 2:mantine-react-table

mantine-react-table - 基于 TanStack Table、功能最强大。

bash
pnpm add mantine-react-table @tanstack/react-table
tsx
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";

const table = useMantineReactTable({
  columns: [
    { accessorKey: "name", header: "姓名" },
    { accessorKey: "age", header: "年龄" },
  ],
  data,
  enableSorting: true,
  enableColumnFilters: true,
  enablePagination: true,
  enableRowSelection: true,
});

<MantineReactTable table={table} />;

选择建议

场景推荐
简单后台列表(分页 / 排序 / 选择)mantine-datatable
复杂表格(虚拟滚动 / 列固定 / 树形 / Grouping)mantine-react-table
极致性能 + 自定义TanStack Table + Mantine Table 手动整合
大数据量(10k+)AG Grid Community + Mantine 主题适配

@mantine/dates 完整方案

安装

bash
pnpm add @mantine/dates dayjs
tsx
import "@mantine/dates/styles.css";
import { DatesProvider } from "@mantine/dates";
import "dayjs/locale/zh-cn";

<MantineProvider>
  <DatesProvider settings={{ locale: "zh-cn", firstDayOfWeek: 1, weekendDays: [0, 6] }}>
    {children}
  </DatesProvider>
</MantineProvider>;

常用组件

tsx
import {
  DateInput,
  DatePicker,
  DatePickerInput,
  DateTimePicker,
  MonthPicker,
  MonthPickerInput,
  YearPicker,
  YearPickerInput,
  TimePicker,
  Calendar,
} from "@mantine/dates";

{/* 单日期选择 - 输入框 */}
<DatePickerInput
  label="日期"
  placeholder="选择日期"
  valueFormat="YYYY-MM-DD"
/>

{/* 范围选择 */}
<DatePickerInput
  type="range"
  label="日期范围"
  placeholder="选择范围"
/>

{/* 多选 */}
<DatePickerInput
  type="multiple"
  label="多个日期"
/>

{/* 日期 + 时间 */}
<DateTimePicker label="日期时间" valueFormat="YYYY-MM-DD HH:mm" />

{/* 直接渲染日历(无输入框) */}
<DatePicker />

自定义可选日期

tsx
<DatePickerInput
  label="选择工作日"
  excludeDate={(date) => {
    const day = dayjs(date).day();
    return day === 0 || day === 6;   // 排除周末
  }}
  minDate={new Date(2024, 0, 1)}
  maxDate={new Date(2026, 11, 31)}
/>

@mantine/charts 完整方案

安装

bash
pnpm add @mantine/charts recharts
tsx
import "@mantine/charts/styles.css";

13 种图表

tsx
import {
  BarChart,
  LineChart,
  AreaChart,
  PieChart,
  DonutChart,
  RadarChart,
  ScatterChart,
  BubbleChart,
  FunnelChart,
  RadialBarChart,
  CompositeChart,
  Sparkline,
  Heatmap,
} from "@mantine/charts";

BarChart 示例

tsx
const data = [
  { month: "Jan", smoothies: 1100, juices: 800 },
  { month: "Feb", smoothies: 1200, juices: 750 },
  { month: "Mar", smoothies: 1500, juices: 900 },
];

<BarChart
  h={300}
  data={data}
  dataKey="month"
  series={[
    { name: "smoothies", color: "blue.6", label: "果昔" },
    { name: "juices", color: "teal.6", label: "果汁" },
  ]}
  tickLine="y"
  withLegend
  withTooltip
/>;

LineChart 示例

tsx
<LineChart
  h={300}
  data={data}
  dataKey="month"
  series={[{ name: "sales", color: "blue.6", label: "销量" }]}
  curveType="monotone"
  connectNulls
  withDots
/>;

PieChart / DonutChart

tsx
const pieData = [
  { name: "苹果", value: 400, color: "red.6" },
  { name: "香蕉", value: 300, color: "yellow.6" },
  { name: "橘子", value: 200, color: "orange.6" },
];

<DonutChart data={pieData} withLabels withTooltip />;

@mantine/tiptap 富文本

安装

bash
pnpm add @mantine/tiptap @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link
tsx
import "@mantine/tiptap/styles.css";
import { RichTextEditor, Link } from "@mantine/tiptap";
import { useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";

const editor = useEditor({
  extensions: [StarterKit, Link],
  content: "<p>初始内容</p>",
});

<RichTextEditor editor={editor}>
  <RichTextEditor.Toolbar sticky stickyOffset={60}>
    <RichTextEditor.ControlsGroup>
      <RichTextEditor.Bold />
      <RichTextEditor.Italic />
      <RichTextEditor.Underline />
      <RichTextEditor.Strikethrough />
    </RichTextEditor.ControlsGroup>
    <RichTextEditor.ControlsGroup>
      <RichTextEditor.H1 />
      <RichTextEditor.H2 />
      <RichTextEditor.H3 />
    </RichTextEditor.ControlsGroup>
    <RichTextEditor.ControlsGroup>
      <RichTextEditor.BulletList />
      <RichTextEditor.OrderedList />
      <RichTextEditor.Link />
    </RichTextEditor.ControlsGroup>
  </RichTextEditor.Toolbar>
  <RichTextEditor.Content />
</RichTextEditor>;

Polymorphic Component(component prop)

Mantine 的杀器之一 —— 任何组件都能通过 component prop 改变底层渲染元素

tsx
{/* 默认渲染为 button */}
<Button>点我</Button>

{/* 渲染为 a 标签(保留 Button 所有样式) */}
<Button component="a" href="/about" target="_blank">
  外部链接
</Button>

{/* 与 React Router 集成 */}
import { Link } from "react-router-dom";
<Button component={Link} to="/users">
  用户页
</Button>;

{/* 与 Next.js 集成 */}
import Link from "next/link";
<Button component={Link} href="/users">
  用户页
</Button>;

TypeScript 泛型推导

component prop 决定其余 props 的类型 —— TypeScript 自动检查:

tsx
{/* TS 错误:a 标签没有 type 属性 */}
<Button component="a" type="submit">

{/* TS 错误:button 标签没有 href 属性 */}
<Button component="button" href="/...">

在自定义组件中使用

tsx
import { Box, type BoxProps } from "@mantine/core";

function MyCard(props: BoxProps & { component?: "div" | "section" | "article" }) {
  return <Box {...props} />;
}

CSS Variables 深度

Mantine 生成的 CSS Variables

v7+ Mantine 在根元素 (:root / html) 上自动生成所有 theme token 的 CSS 变量:

css
/* 颜色:每色 10 个 shade */
--mantine-color-blue-0
--mantine-color-blue-1
...
--mantine-color-blue-9

/* 间距 */
--mantine-spacing-xs
--mantine-spacing-sm
--mantine-spacing-md
--mantine-spacing-lg
--mantine-spacing-xl

/* 圆角 */
--mantine-radius-xs
...
--mantine-radius-xl

/* 字号 */
--mantine-font-size-xs
...
--mantine-font-size-xl

/* 字体 */
--mantine-font-family
--mantine-font-family-monospace
--mantine-font-family-headings

/* 阴影 */
--mantine-shadow-xs
...
--mantine-shadow-xl

/* 标题 */
--mantine-h1-font-size
--mantine-h1-line-height
...

自定义 CSS / Tailwind 中引用

css
/* MyComponent.module.css */
.card {
  background: var(--mantine-color-blue-0);
  padding: var(--mantine-spacing-md);
  border-radius: var(--mantine-radius-md);
  box-shadow: var(--mantine-shadow-sm);
  font-family: var(--mantine-font-family);
}

与 Tailwind 整合

Tailwind config 引用 Mantine 变量:

ts
// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary: "var(--mantine-primary-color-filled)",
      },
      spacing: {
        "mantine-md": "var(--mantine-spacing-md)",
      },
    },
  },
};

@mantine/hooks 高频 70+ Hooks 速览

按使用频率排序。详细 API 见 参考Mantine 官网

State 管理 Top 10

tsx
import {
  useDisclosure,        // boolean 开关 [opened, { open, close, toggle }]
  useToggle,            // 多值循环切换 [value, toggle]
  useCounter,           // 数字计数 [count, { increment, decrement, set, reset }]
  useLocalStorage,      // 持久化到 localStorage
  useDebouncedValue,    // 防抖值
  useDebouncedCallback, // 防抖回调
  useUncontrolled,      // 受控/非受控双模式 hook
  useListState,         // 列表 CRUD
  usePagination,        // 分页逻辑
  useInputState,        // input 状态
} from "@mantine/hooks";

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

// useDebouncedValue 经典
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 500);
useEffect(() => fetchData(debouncedSearch), [debouncedSearch]);

// useListState 列表 CRUD
const [tasks, handlers] = useListState([{ id: 1, name: "Task 1" }]);
handlers.append({ id: 2, name: "Task 2" });
handlers.remove(0);
handlers.setItemProp(0, "name", "Updated");

UI 交互 Top 10

tsx
import {
  useClickOutside,    // 点击外部
  useHotkeys,         // 快捷键
  useHover,           // hover 状态
  useFocusTrap,       // 焦点陷阱
  useScrollIntoView,  // 滚动到指定元素
  useElementSize,     // 元素尺寸
  useResizeObserver,  // 监听尺寸变化
  useViewportSize,    // 视口尺寸
  useMediaQuery,      // 媒体查询
  useFullscreen,      // 全屏
} from "@mantine/hooks";

// useClickOutside - 关闭弹出层经典
const ref = useClickOutside(() => setOpened(false));

// useHotkeys - 全局快捷键
useHotkeys([
  ["mod+K", () => spotlight.open()],
  ["mod+S", () => handleSave()],
  ["Escape", () => setOpened(false)],
]);

// useScrollIntoView
const { scrollIntoView, targetRef } = useScrollIntoView({ duration: 600 });
<Button onClick={() => scrollIntoView()}>滚动到目标</Button>
<div ref={targetRef}>目标元素</div>

实用工具 Top 10

tsx
import {
  useClipboard,         // 剪贴板复制
  useDocumentTitle,     // 文档标题
  useDocumentVisibility,// 文档可见性
  useFavicon,           // favicon
  useIdle,              // 用户空闲检测
  useNetwork,           // 网络状态
  useOs,                // 操作系统
  useEyeDropper,        // 取色器
  useColorScheme,       // 系统色彩模式(注意:与 useMantineColorScheme 不同)
  useReducedMotion,     // 减少动画偏好
} from "@mantine/hooks";

// useClipboard
const clipboard = useClipboard({ timeout: 2000 });
<Button onClick={() => clipboard.copy("内容")}>
  {clipboard.copied ? "已复制" : "复制"}
</Button>;

// useIdle - 检测空闲
const idle = useIdle(60000);  // 60 秒空闲
useEffect(() => {
  if (idle) handleAutoLogout();
}, [idle]);

Lifecycle 系列

tsx
import {
  useDidUpdate,         // 跳过首次的 effect
  useIsFirstRender,     // 首次渲染检测
  useIsomorphicEffect,  // SSR 安全 useEffect
  useShallowEffect,     // 浅比较 effect
  useMounted,           // 挂载状态
} from "@mantine/hooks";

// useDidUpdate - search 改变时(但跳过首次)
useDidUpdate(() => {
  fetchData(search);
}, [search]);

Next.js App Router 完整集成

完整 layout.tsx

tsx
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import "@mantine/dates/styles.css";

import type { Metadata } from "next";
import {
  ColorSchemeScript,
  MantineProvider,
  mantineHtmlProps,
} from "@mantine/core";
import { Providers } from "./providers";
import { theme } from "./theme";

export const metadata: Metadata = {
  title: "My Mantine App",
  description: "Modern React App",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN" {...mantineHtmlProps}>
      <head>
        <ColorSchemeScript defaultColorScheme="auto" />
      </head>
      <body>
        <Providers theme={theme}>{children}</Providers>
      </body>
    </html>
  );
}

Providers Client Component

tsx
// app/providers.tsx
"use client";

import { MantineProvider, type MantineThemeOverride } from "@mantine/core";
import { Notifications } from "@mantine/notifications";
import { ModalsProvider } from "@mantine/modals";
import { DatesProvider } from "@mantine/dates";
import "dayjs/locale/zh-cn";

export function Providers({
  theme,
  children,
}: {
  theme: MantineThemeOverride;
  children: React.ReactNode;
}) {
  return (
    <MantineProvider theme={theme} defaultColorScheme="auto">
      <DatesProvider settings={{ locale: "zh-cn", firstDayOfWeek: 1 }}>
        <ModalsProvider>
          <Notifications position="top-right" />
          {children}
        </ModalsProvider>
      </DatesProvider>
    </MantineProvider>
  );
}

在 Server Component 中使用 Mantine

部分纯展示组件可以在 RSC(Server Component)中使用:

tsx
// app/page.tsx (RSC)
import { Container, Title, Text } from "@mantine/core";

export default function HomePage() {
  return (
    <Container size="md" py="xl">
      <Title>欢迎</Title>
      <Text c="dimmed">这是 Server Component</Text>
    </Container>
  );
}

但是——所有交互组件(Button onClick / 表单 / Modal 等)必须放在 Client Component("use client")中。

常见踩坑

踩坑 1:PostCSS 配置文件缺失或错误

症状:响应式 prop(w={{ sm: 200 }})不生效、CSS 中 $mantine-breakpoint-* 编译报错。

解决

  1. 项目根目录必须有 postcss.config.cjs(注意 .cjs 后缀)
  2. 安装 postcss-preset-mantine + postcss-simple-vars
  3. 检查 mantine-breakpoint-* 变量定义完整(xs/sm/md/lg/xl 全部)

踩坑 2:忘了导入 styles.css

症状:组件没有样式。

解决:每个子包都要导入对应 styles.css:

ts
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import "@mantine/dates/styles.css";
import "@mantine/charts/styles.css";
import "@mantine/tiptap/styles.css";
// ...

踩坑 3:CSS Modules 中 var(--mantine-*) 不生效

症状:CSS 变量名拼写正确但没有效果。

解决:检查是否在 MantineProvider 包裹的子树内。Mantine 的 CSS Variables 是注入到 :root / [data-mantine-color-scheme] 上的,不在 Provider 内的页面(如静态 404 页)不会有变量

踩坑 4:SSR Hydration Warning

症状:Next.js 控制台「Hydration failed because the server rendered HTML didn't match」红字。

解决

  1. <html> 上 spread {...mantineHtmlProps}
  2. <head> 中渲染 <ColorSchemeScript defaultColorScheme="auto" />
  3. useComputedColorScheme("light", { getInitialValueInEffect: true }) 而非默认 hook

踩坑 5:Polymorphic component TypeScript 报错

症状<Button component={CustomLink} to="/..."> TS 报错 to 不存在。

解决

tsx
// 方法 1:用泛型显式标注
<Button<typeof CustomLink> component={CustomLink} to="/...">

// 方法 2:as 断言
<Button component={CustomLink as any} to="/...">

// 方法 3:用 Box 替代
<Box component={CustomLink} to="/..." className="some-button-class">

踩坑 6:Tailwind 与 Mantine 类名优先级冲突

症状:Tailwind class(如 bg-blue-500)被 Mantine 默认样式覆盖。

解决

  1. Tailwind 配置 important: true(或限定 important: '#root'
  2. 用 Mantine classNames prop 代替 Tailwind className
  3. 用 Mantine style prop 内联(最高优先级)
  4. 在 PostCSS 中调整 layer 顺序(Tailwind layer 后于 Mantine)

踩坑 7:Notifications / Modals 在 SSR 中报错

症状:Next.js dev 服务器报「window is not defined」或「Cannot read properties of undefined」。

解决

  1. Notifications / ModalsProvider 放在 "use client" 的 Providers 组件中
  2. 不要在 Server Component 中调用 notifications.show() / modals.open()

踩坑 8:暗色模式下自定义组件样式不变

症状:手动写的样式只适配浅色。

解决:在 CSS Modules 中加 [data-mantine-color-scheme="dark"] 选择器或用 light-dark(...) mixin:

css
.card {
  background: light-dark(white, var(--mantine-color-dark-7));
  color: light-dark(black, white);
}

踩坑 9:useForm 类型推导失败

症状form.getInputProps('email') 类型不准确。

解决:显式标注 useForm 泛型:

tsx
interface FormValues {
  email: string;
  password: string;
}

const form = useForm<FormValues>({
  initialValues: { email: "", password: "" },
});

踩坑 10:dates 组件 locale 不生效

症状:日期选择器显示英文星期。

解决

  1. 安装 dayjsimport 'dayjs/locale/zh-cn'
  2. DatesProvider 包裹并设置 settings={{ locale: 'zh-cn' }}
  3. Next.js App Router 中 dayjs locale 必须在 "use client" 组件中导入

踩坑 11:Checkbox / Switch / Radio getInputProps 不绑定

症状:表单中复选框点击没反应。

解决:加 { type: 'checkbox' } 参数:

tsx
<Checkbox {...form.getInputProps("agree", { type: "checkbox" })} />
<Switch {...form.getInputProps("enabled", { type: "checkbox" })} />

踩坑 12:bundle 体积过大

症状:生产构建后 chunk 文件 1MB+。

解决

  1. 只安装实际用到的子包(不是装全家桶)
  2. import { Button } from '@mantine/core' 命名导入(已经支持 Tree Shaking)
  3. 图标库用 @tabler/icons-react 而非 react-icons(前者 Tree Shaking 更好)
  4. 检查是否引入了 @mantine/dates 的所有 locale(只 import 需要的)

下一步

  • 参考:130+ 组件清单、70+ hooks 清单、createTheme 完整选项、Styles API 详解、TypeScript 类型
  • Mantine 官网:实时 Demo + CodeSandbox 在线编辑
  • Mantine UI:123 个免费业务级组件(导航 / 营销页 / 博客等)
  • Mantine Help:常见问题 FAQ