Skip to main content
Refine provides comprehensive support for Mantine, a modern React component library with over 100 customizable components. The @refinedev/mantine package offers pre-built components that integrate seamlessly with Refine’s core functionality.

Installation

npm install @refinedev/mantine @mantine/core @mantine/hooks @mantine/form @mantine/notifications @emotion/react @tabler/icons-react dayjs

Setup

Basic Setup

Wrap your application with Mantine’s providers:
App.tsx
import { Refine } from "@refinedev/core";
import {
  ThemedLayout,
  RefineThemes,
  useNotificationProvider,
  notificationProvider,
} from "@refinedev/mantine";
import { MantineProvider } from "@mantine/core";
import { NotificationsProvider } from "@mantine/notifications";
import dataProvider from "@refinedev/simple-rest";

function App() {
  return (
    <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles>
      <NotificationsProvider position="top-right">
        <Refine
          dataProvider={dataProvider("https://api.example.com")}
          notificationProvider={useNotificationProvider}
          resources={[
            {
              name: "products",
              list: "/products",
              create: "/products/create",
              edit: "/products/edit/:id",
              show: "/products/show/:id",
            },
          ]}
        >
          <ThemedLayout>
            {/* Your pages here */}
          </ThemedLayout>
        </Refine>
      </NotificationsProvider>
    </MantineProvider>
  );
}

export default App;

Available Themes

Refine provides pre-configured Mantine themes:
import { RefineThemes } from "@refinedev/mantine";

// Available themes:
RefineThemes.Blue
RefineThemes.Purple
RefineThemes.Magenta
RefineThemes.Red
RefineThemes.Orange
RefineThemes.Yellow

Custom Theme with Dark Mode

Mantine has excellent dark mode support:
import { MantineProvider, ColorSchemeProvider } from "@mantine/core";
import { useLocalStorage } from "@mantine/hooks";

function App() {
  const [colorScheme, setColorScheme] = useLocalStorage({
    key: "mantine-color-scheme",
    defaultValue: "light",
  });

  const toggleColorScheme = (value) =>
    setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"));

  return (
    <ColorSchemeProvider
      colorScheme={colorScheme}
      toggleColorScheme={toggleColorScheme}
    >
      <MantineProvider
        theme={{
          colorScheme,
          primaryColor: "blue",
          fontFamily: "Inter, sans-serif",
        }}
        withNormalizeCSS
        withGlobalStyles
      >
        {/* Your app */}
      </MantineProvider>
    </ColorSchemeProvider>
  );
}

Layout Components

ThemedLayout

The main layout component with sidebar, header, and content:
import { ThemedLayout } from "@refinedev/mantine";

function App() {
  return (
    <Refine
      // ... other props
    >
      <ThemedLayout>
        {/* Your routes */}
      </ThemedLayout>
    </Refine>
  );
}

Custom Layout Components

import {
  ThemedLayout,
  ThemedSider,
  ThemedHeader,
  ThemedTitle,
} from "@refinedev/mantine";

function App() {
  return (
    <ThemedLayout
      Sider={() => <ThemedSider />}
      Header={() => <ThemedHeader sticky />}
      Title={({ collapsed }) => (
        <ThemedTitle collapsed={collapsed} text="My Admin" />
      )}
    >
      {/* Your routes */}
    </ThemedLayout>
  );
}

CRUD Components

List

Display list with Mantine Table:
pages/products/list.tsx
import { List, EditButton, ShowButton, DeleteButton } from "@refinedev/mantine";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { Table, Group, Pagination } from "@mantine/core";
import { useMemo } from "react";

export const ProductList = () => {
  const columns = useMemo<ColumnDef<any>[]>(
    () => [
      {
        id: "id",
        accessorKey: "id",
        header: "ID",
      },
      {
        id: "name",
        accessorKey: "name",
        header: "Name",
      },
      {
        id: "price",
        accessorKey: "price",
        header: "Price",
      },
      {
        id: "actions",
        header: "Actions",
        cell: ({ row }) => (
          <Group spacing="xs" noWrap>
            <EditButton hideText recordItemId={row.original.id} />
            <ShowButton hideText recordItemId={row.original.id} />
            <DeleteButton hideText recordItemId={row.original.id} />
          </Group>
        ),
      },
    ],
    [],
  );

  const {
    getHeaderGroups,
    getRowModel,
    setPageIndex,
    getState,
    getPageCount,
  } = useTable({
    columns,
  });

  return (
    <List>
      <Table>
        <thead>
          {getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th key={header.id}>
                  {flexRender(
                    header.column.columnDef.header,
                    header.getContext(),
                  )}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {getRowModel().rows.map((row) => (
            <tr key={row.id}>
              {row.getVisibleCells().map((cell) => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </Table>
      <Pagination
        position="right"
        total={getPageCount()}
        page={getState().pagination.pageIndex + 1}
        onChange={(page) => setPageIndex(page - 1)}
      />
    </List>
  );
};

Create

Create form with Mantine components:
pages/products/create.tsx
import { Create, useForm } from "@refinedev/mantine";
import { TextInput, NumberInput, Textarea } from "@mantine/core";

export const ProductCreate = () => {
  const {
    getInputProps,
    saveButtonProps,
    refineCore: { formLoading },
  } = useForm({
    initialValues: {
      name: "",
      price: 0,
      description: "",
    },
    validate: {
      name: (value) => (value.length < 2 ? "Name is too short" : null),
      price: (value) => (value <= 0 ? "Price must be positive" : null),
    },
  });

  return (
    <Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
      <TextInput
        mt="sm"
        label="Name"
        placeholder="Product name"
        {...getInputProps("name")}
      />
      <NumberInput
        mt="sm"
        label="Price"
        placeholder="0.00"
        precision={2}
        {...getInputProps("price")}
      />
      <Textarea
        mt="sm"
        label="Description"
        placeholder="Product description"
        minRows={4}
        {...getInputProps("description")}
      />
    </Create>
  );
};

Edit

Edit form with automatic data fetching:
pages/products/edit.tsx
import { Edit, useForm } from "@refinedev/mantine";
import { TextInput, NumberInput } from "@mantine/core";

export const ProductEdit = () => {
  const {
    getInputProps,
    saveButtonProps,
    refineCore: { queryResult },
  } = useForm({
    initialValues: {
      name: "",
      price: 0,
    },
    validate: {
      name: (value) => (value.length < 2 ? "Name is too short" : null),
      price: (value) => (value <= 0 ? "Price must be positive" : null),
    },
  });

  return (
    <Edit saveButtonProps={saveButtonProps}>
      <TextInput
        mt="sm"
        disabled
        label="ID"
        {...getInputProps("id")}
      />
      <TextInput
        mt="sm"
        label="Name"
        placeholder="Product name"
        {...getInputProps("name")}
      />
      <NumberInput
        mt="sm"
        label="Price"
        placeholder="0.00"
        precision={2}
        {...getInputProps("price")}
      />
    </Edit>
  );
};

Show

Display single record:
pages/products/show.tsx
import {
  Show,
  TextField,
  NumberField,
  DateField,
} from "@refinedev/mantine";
import { useShow } from "@refinedev/core";
import { Title } from "@mantine/core";

export const ProductShow = () => {
  const { queryResult } = useShow();
  const { data, isLoading } = queryResult;
  const record = data?.data;

  return (
    <Show isLoading={isLoading}>
      <Title order={5}>ID</Title>
      <TextField value={record?.id} />

      <Title mt="md" order={5}>
        Name
      </Title>
      <TextField value={record?.name} />

      <Title mt="md" order={5}>
        Price
      </Title>
      <NumberField
        value={record?.price}
        options={{
          style: "currency",
          currency: "USD",
        }}
      />

      <Title mt="md" order={5}>
        Created At
      </Title>
      <DateField value={record?.createdAt} />
    </Show>
  );
};

Field Components

Mantine field components for displaying data:
import {
  TextField,
  NumberField,
  DateField,
  EmailField,
  UrlField,
  BooleanField,
  TagField,
  MarkdownField,
} from "@refinedev/mantine";
import { Stack } from "@mantine/core";

function FieldExamples() {
  return (
    <Stack spacing="sm">
      <TextField value="Simple text" />
      
      <NumberField
        value={1234.56}
        options={{ style: "currency", currency: "USD" }}
      />
      
      <DateField value="2024-01-15" format="LL" />
      
      <EmailField value="[email protected]" />
      
      <UrlField value="https://example.com" />
      
      <BooleanField value={true} />
      
      <TagField value="Active" color="green" />
      
      <MarkdownField value="# Hello\n\nThis is **markdown**" />
    </Stack>
  );
}

Button Components

Mantine styled action buttons:
import {
  CreateButton,
  EditButton,
  ShowButton,
  DeleteButton,
  ListButton,
  RefreshButton,
  SaveButton,
  CloneButton,
  ExportButton,
  ImportButton,
} from "@refinedev/mantine";
import { Group } from "@mantine/core";

function ButtonExamples() {
  return (
    <Group spacing="xs">
      <CreateButton resource="products" />
      <EditButton resource="products" recordItemId="1" />
      <ShowButton resource="products" recordItemId="1" />
      <DeleteButton resource="products" recordItemId="1" />
      <ListButton resource="products" />
      <RefreshButton resource="products" />
      <SaveButton />
      <CloneButton resource="products" recordItemId="1" />
      <ExportButton />
      <ImportButton />
    </Group>
  );
}

Advanced Features

import { useModalForm, CreateButton } from "@refinedev/mantine";
import { Modal, TextInput } from "@mantine/core";

export const ProductList = () => {
  const {
    modal: { visible, close, title },
    getInputProps,
    saveButtonProps,
  } = useModalForm({
    refineCoreProps: { action: "create" },
    initialValues: {
      name: "",
      price: 0,
    },
    validate: {
      name: (value) => (value.length < 2 ? "Name is too short" : null),
    },
  });

  return (
    <>
      <CreateButton onClick={() => modal.show()} />
      
      <Modal opened={visible} onClose={close} title={title}>
        <TextInput
          mt="sm"
          label="Name"
          placeholder="Product name"
          {...getInputProps("name")}
        />
        <Group position="right" mt="md">
          <SaveButton {...saveButtonProps} />
        </Group>
      </Modal>
    </>
  );
};

Notifications

import { useNotification } from "@refinedev/core";
import { Button } from "@mantine/core";

function CustomNotification() {
  const { open } = useNotification();

  const handleClick = () => {
    open?.({
      type: "success",
      message: "Success!",
      description: "Operation completed successfully",
    });
  };

  return <Button onClick={handleClick}>Show Notification</Button>;
}

Dark Mode Toggle

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

function ColorSchemeToggle() {
  const { colorScheme, toggleColorScheme } = useMantineColorScheme();
  const dark = colorScheme === "dark";

  return (
    <ActionIcon
      variant="outline"
      color={dark ? "yellow" : "blue"}
      onClick={() => toggleColorScheme()}
      title="Toggle color scheme"
    >
      {dark ? <IconSun size={18} /> : <IconMoon size={18} />}
    </ActionIcon>
  );
}

Auto-Save Indicator

import { AutoSaveIndicator, useForm } from "@refinedev/mantine";
import { TextInput } from "@mantine/core";

export const ProductEdit = () => {
  const {
    getInputProps,
    refineCore: { autoSaveProps },
  } = useForm({
    refineCoreProps: {
      autoSave: {
        enabled: true,
        debounce: 2000,
      },
    },
  });

  return (
    <>
      <AutoSaveIndicator {...autoSaveProps} />
      <TextInput mt="sm" label="Name" {...getInputProps("name")} />
    </>
  );
};
import { List } from "@refinedev/mantine";
import { useTable } from "@refinedev/react-table";
import { TextInput, Select, Group } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { useState } from "react";

export const ProductList = () => {
  const [search, setSearch] = useState("");
  const [status, setStatus] = useState("");

  const { refineCore: { setFilters } } = useTable({
    columns: [],
  });

  const handleSearch = () => {
    setFilters([
      {
        field: "name",
        operator: "contains",
        value: search,
      },
      {
        field: "status",
        operator: "eq",
        value: status,
      },
    ]);
  };

  return (
    <List>
      <Group mb="md">
        <TextInput
          placeholder="Search products"
          icon={<IconSearch size={16} />}
          value={search}
          onChange={(e) => setSearch(e.currentTarget.value)}
        />
        <Select
          placeholder="Status"
          data={[
            { value: "active", label: "Active" },
            { value: "inactive", label: "Inactive" },
          ]}
          value={status}
          onChange={setStatus}
        />
        <Button onClick={handleSearch}>Search</Button>
      </Group>
      {/* Table here */}
    </List>
  );
};

Responsive Design

Mantine has excellent responsive utilities:
import { Grid, Container, useMantineTheme } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";

function ResponsiveLayout() {
  const theme = useMantineTheme();
  const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);

  return (
    <Container size="xl">
      <Grid>
        <Grid.Col span={mobile ? 12 : 6}>
          {/* Content */}
        </Grid.Col>
        <Grid.Col span={mobile ? 12 : 6}>
          {/* Content */}
        </Grid.Col>
      </Grid>
    </Container>
  );
}

Next Steps

Mantine Docs

Official Mantine documentation

Examples

See complete Mantine examples

Theming

Learn about Mantine theming

Hooks

Explore Refine hooks

Build docs developers (and LLMs) love