Introduction
Refine provides an integration package for Mantine library. This package provides a set of ready to use components and hooks that connects Refine with Mantine components. While Refine's integration offers a set of components and hooks, it is not a replacement for the Mantine UI packages, you will be able to use all the features of Mantine in the same way you would use it in a regular React application. Refine's integration only provides components and hooks for an easier usage of Mantine components in combination with Refine's features and functionalities.
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,react-router@^7.0.2
Content: import { Refine, Authenticated } from "@refinedev/core"; import { ErrorComponent, ThemedLayoutV2, RefineThemes, useNotificationProvider, AuthPage } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import dataProvider from "@refinedev/simple-rest"; import routerProvider, { NavigateToResource, } from "@refinedev/react-router"; import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; import authProvider from "./auth-provider"; import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products"; const App: React.FC = () => { return ( <BrowserRouter> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine notificationProvider={useNotificationProvider} routerProvider={routerProvider} dataProvider={dataProvider( "https://api.fake-rest.refine.dev", )} authProvider={authProvider} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create", meta: { canDelete: true, }, }, ]} > <Routes> <Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}> <Route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route index element={<NavigateToResource resource="products" />} /> <Route path="/products" element={<Outlet />}> <Route index element={<ProductList />} /> <Route path="create" element={<ProductCreate />} /> <Route path=":id" element={<ProductShow />} /> <Route path=":id/edit" element={<ProductEdit />} /> </Route> <Route path="*" element={<ErrorComponent />} /> </Route> </Route> <Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}> <Route path="/login" element={( <AuthPage type="login" formProps={{ initialValues: { email: "demo@refine.dev", password: "demodemo", }, }} /> )} /> <Route path="/register" element={<AuthPage type="register" />} /> <Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} /> <Route path="/reset-password" element={<AuthPage type="resetPassword" />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </NotificationsProvider> </MantineProvider> </BrowserRouter> ); }; export default App;
Content: export * from "./list"; export * from "./show"; export * from "./edit"; export * from "./create";
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); };
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductEdit = () => { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); };
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductCreate = () => { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); };
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { // auto login at first time if (typeof (window as any).authenticated === "undefined") { (window as any).authenticated = true; } return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
Installation
Installing the package is as simple as just by running the following command without any additional configuration:
- npm
- pnpm
- yarn
npm i @refinedev/mantine @refinedev/react-table @mantine/core@5 @mantine/hooks@5 @mantine/form@5 @mantine/notifications@5 @emotion/react@11 @tabler/icons-react @tanstack/react-table
pnpm add @refinedev/mantine @refinedev/react-table @mantine/core@5 @mantine/hooks@5 @mantine/form@5 @mantine/notifications@5 @emotion/react@11 @tabler/icons-react @tanstack/react-table
yarn add @refinedev/mantine @refinedev/react-table @mantine/core@5 @mantine/hooks@5 @mantine/form@5 @mantine/notifications@5 @emotion/react@11 @tabler/icons-react @tanstack/react-table
Version Support
Refine's Mantine integration currently uses Mantine v5.
Usage
We'll wrap our app with the <MantineProvider />
to make sure we have the theme available for our app, then we'll use the layout components to wrap them around our routes. Check out the examples below to see how to use Refine's Mantine integration.
- React Router
- Next.js
- Remix
React Router
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,@refinedev/react-router@latest,react-router@^7.0.2
Content: import { Refine, Authenticated } from "@refinedev/core"; import { ErrorComponent, ThemedLayoutV2, RefineThemes, useNotificationProvider, AuthPage } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import dataProvider from "@refinedev/simple-rest"; import routerProvider, { NavigateToResource, } from "@refinedev/react-router"; import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; import authProvider from "./auth-provider"; import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products"; const App: React.FC = () => { return ( <BrowserRouter> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine notificationProvider={useNotificationProvider} routerProvider={routerProvider} dataProvider={dataProvider( "https://api.fake-rest.refine.dev", )} authProvider={authProvider} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create", meta: { canDelete: true, }, }, ]} > <Routes> <Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}> <Route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route index element={<NavigateToResource resource="products" />} /> <Route path="/products" element={<Outlet />}> <Route index element={<ProductList />} /> <Route path="create" element={<ProductCreate />} /> <Route path=":id" element={<ProductShow />} /> <Route path=":id/edit" element={<ProductEdit />} /> </Route> <Route path="*" element={<ErrorComponent />} /> </Route> </Route> <Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}> <Route path="/login" element={( <AuthPage type="login" formProps={{ initialValues: { email: "demo@refine.dev", password: "demodemo", }, }} /> )} /> <Route path="/register" element={<AuthPage type="register" />} /> <Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} /> <Route path="/reset-password" element={<AuthPage type="resetPassword" />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </NotificationsProvider> </MantineProvider> </BrowserRouter> ); }; export default App;
Content: export * from "./list"; export * from "./show"; export * from "./edit"; export * from "./create";
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); };
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductEdit = () => { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); };
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductCreate = () => { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); };
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { // auto login at first time if (typeof (window as any).authenticated === "undefined") { (window as any).authenticated = true; } return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
Next.js
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,@refinedev/nextjs-router@latest
Content: import React from "react"; import { Refine } from "@refinedev/core"; import routerProvider from "@refinedev/nextjs-router/pages"; import dataProvider from "@refinedev/simple-rest"; import type { AppProps } from "next/app"; import { RefineThemes, ThemedLayoutV2, useNotificationProvider } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import authProvider from "../src/auth-provider"; export type ExtendedNextPage = NextPage & { noLayout?: boolean; }; type ExtendedAppProps = AppProps & { Component: ExtendedNextPage; }; function App({ Component, pageProps }: ExtendedAppProps) { const renderComponent = () => { if (Component.noLayout) { return <Component {...pageProps} />; } return ( <ThemedLayoutV2> <Component {...pageProps} /> </ThemedLayoutV2> ); } return ( <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine routerProvider={routerProvider} dataProvider={dataProvider("https://api.fake-rest.refine.dev")} notificationProvider={useNotificationProvider} authProvider={authProvider} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create" }, ]} options={{ syncWithLocation: true }} > {renderComponent()} </Refine> </NotificationsProvider> </MantineProvider> ); } export default App;
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; import authProvider from "../../src/auth-provider"; export default function ProductList() { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); }; /** * Same check can also be done via `<Authenticated />` component. * But we're using a server-side check for a better UX. */ export const getServerSideProps = async () => { const { authenticated } = await authProvider.check(); if (!authenticated) { return { redirect: { destination: "/login", permanent: false, }, }; } return { props: {}, }; } interface IProduct { id: string; name: string; price: number; description: string; }
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; import { Title } from "@mantine/core"; import authProvider from "../../src/auth-provider"; export default function 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); }; /** * Same check can also be done via `<Authenticated />` component. * But we're using a server-side check for a better UX. */ export const getServerSideProps = async () => { const { authenticated } = await authProvider.check(); if (!authenticated) { return { redirect: { destination: "/login", permanent: false, }, }; } return { props: {}, }; }
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; import authProvider from "../../../src/auth-provider"; export default function ProductEdit() { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); }; /** * Same check can also be done via `<Authenticated />` component. * But we're using a server-side check for a better UX. */ export const getServerSideProps = async () => { const { authenticated } = await authProvider.check(); if (!authenticated) { return { redirect: { destination: "/login", permanent: false, }, }; } return { props: {}, }; }
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; import authProvider from "../../src/auth-provider"; export default function ProductCreate() { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); }; /** * Same check can also be done via `<Authenticated />` component. * But we're using a server-side check for a better UX. */ export const getServerSideProps = async () => { const { authenticated } = await authProvider.check(); if (!authenticated) { return { redirect: { destination: "/login", permanent: false, }, }; } return { props: {}, }; }
Content: import React from "react"; import { AuthPage } from "@refinedev/mantine"; import authProvider from "../src/auth-provider"; import type { ExtendedNextPage } from "./_app"; const Login: ExtendedNextPage = () => { return <AuthPage type="login" />; }; Login.noLayout = true; export default Login; /** * Same check can also be done via `<Authenticated />` component. * But we're using a server-side check for a better UX. */ export const getServerSideProps = async () => { const { authenticated } = await authProvider.check(); if (authenticated) { return { redirect: { destination: "/products", permanent: false, }, }; } return { props: {}, };
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { // auto login at first time if (typeof (window as any).authenticated === "undefined") { (window as any).authenticated = true; } return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
Remix
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,@refinedev/remix-router@latest
Content: import React from "react"; import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, } from "@remix-run/react"; import { Refine } from "@refinedev/core"; import routerProvider from "@refinedev/remix-router"; import dataProvider from "@refinedev/simple-rest"; import { RefineThemes, ThemedLayoutV2, useNotificationProvider } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import authProvider from "./auth-provider"; export default function App() { return ( <html lang="en"> <head> <Meta /> <Links /> </head> <body> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine routerProvider={routerProvider} dataProvider={dataProvider("https://api.fake-rest.refine.dev")} authProvider={authProvider} notificationProvider={useNotificationProvider} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create", }, ]} options={{ syncWithLocation: true }} > <Outlet /> </Refine> </NotificationsProvider> </MantineProvider> <ScrollRestoration /> <Scripts /> <LiveReload /> </body> </html> ); }
Content: import { ThemedLayoutV2 } from "@refinedev/mantine"; import { Outlet } from "@remix-run/react"; import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import authProvider from "../auth-provider"; export default function AuthenticatedLayout() { // `<ThemedLayoutV2>` is only applied to the authenticated users return ( <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> ); } /** * We're checking if the current session is authenticated. * If not, we're redirecting the user to the login page. * This is applied for all routes that are nested under this layout (_protected). */ export async function loader({ request }: LoaderFunctionArgs) { const { authenticated, redirectTo } = await authProvider.check(request); if (!authenticated) { throw redirect(redirectTo ?? "/login"); } return {}; }
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export default function ProductList() { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; import { Title } from "@mantine/core"; export default function 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); };
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export default function ProductEdit() { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); };
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export default function ProductCreate() { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); };
Content: import { Outlet } from "@remix-run/react"; import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import { authProvider } from "~/authProvider"; export default function AuthLayout() { // no layout is applied for the auth routes return <Outlet />; } /** * If the current session is authenticated, we're redirecting the user to the home page. * Alternatively, we could also use the `Authenticated` component inside the `AuthLayout` to handle the redirect. * But, server-side redirects are more performant. */ export async function loader({ request }: LoaderFunctionArgs) { const { authenticated, redirectTo } = await authProvider.check(request); if (authenticated) { throw redirect(redirectTo ?? "/"); } return {}; }
Content: import { AuthPage } from "@refinedev/mantine"; export default function LoginPage() { return <AuthPage type="login" />; }
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { // auto login at first time if (typeof (window as any).authenticated === "undefined") { (window as any).authenticated = true; } return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
Tables
Mantine offers styled table primitives but lacks the table management solution. Refine recommends using @refinedev/react-table
package which is built on top of Refine's useTable
hook and Tanstack Table's useTable
hook to enable features from pagination to sorting and filtering. Refine's documentations and examples of Mantine uses @refinedev/react-table
package for table management but you have the option to use any table management solution you want.
import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine";
import {
Box,
Group,
ScrollArea,
Select,
Table,
Pagination,
} from "@mantine/core";
const columns = [
{ id: "id", header: "ID", accessorKey: "id" },
{
id: "name",
header: "Name",
accessorKey: "name",
meta: { filterOperator: "contains" },
},
{ id: "price", header: "Price", accessorKey: "price" },
{
id: "actions",
header: "Actions",
accessorKey: "id",
enableColumnFilter: false,
enableSorting: false,
cell: function render({ getValue }) {
return (
<Group spacing="xs" noWrap>
<ShowButton hideText recordItemId={getValue() as number} />
<EditButton hideText recordItemId={getValue() as number} />
<DeleteButton hideText recordItemId={getValue() as number} />
</Group>
);
},
},
];
export const ProductList = () => {
const {
getHeaderGroups,
getRowModel,
setOptions,
refineCore: {
setCurrent,
pageCount,
current,
tableQuery: { data: tableData },
},
} = useTable<IProduct>({ columns });
return (
<ScrollArea>
<List>
<Table highlightOnHover>
<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>
<br />
<Pagination
position="right"
total={pageCount}
page={current}
onChange={setCurrent}
/>
</List>
</ScrollArea>
);
};
interface IProduct {
id: string;
name: string;
price: number;
description: string;
}
Forms
Refine provides a seamless integration with the useForm
hook of @mantine/form
from validation to submission via the useForm
hook exported from the @refinedev/mantine
package.
import { Create, useForm } from "@refinedev/mantine";
import { TextInput, NumberInput } from "@mantine/core";
export const ProductCreate = () => {
const { saveButtonProps, getInputProps, errors } = useForm({
initialValues: {
name: "",
material: "",
price: 0,
},
});
return (
<Create saveButtonProps={saveButtonProps}>
<form>
<TextInput
mt={8}
id="name"
label="Name"
placeholder="Name"
{...getInputProps("name")}
/>
<TextInput
mt={8}
id="material"
label="Material"
placeholder="Material"
{...getInputProps("material")}
/>
<NumberInput
mt={8}
id="price"
label="Price"
placeholder="Price"
{...getInputProps("price")}
/>
</form>
</Create>
);
};
@refinedev/mantine
also offers hooks to implement different types of forms such as useModalForm
, useDrawerForm
and useStepsForm
hooks. Additionally useSelect
hook is also provided to make it easier to implement form fields with relational data. The useSelect
hook of @refinedev/mantine
leverage the useSelect
hook from the @refinedev/core
package.
Notifications
Mantine has its own built-in notification system through @mantine/notifications
package which works seamlessly with its UI elements. Refine also provides a seamless integration with Mantine's notification system and show notifications for related actions and events. This integration is provided by the notificationProvider
hook exported from the @refinedev/mantine
package which can be directly used in the notificationProvider
prop of the <Refine />
component.
import { Refine } from "@refinedev/core";
import { useNotificationProvider } from "@refinedev/mantine";
import { NotificationsProvider } from "@mantine/notifications";
const App = () => {
return (
// `@mantine/notifications` also requires a context provider to be used
<NotificationsProvider position="top-right">
<Refine notificationProvider={useNotificationProvider}>
{/* ... */}
</Refine>
</NotificationsProvider>
);
};
Predefined Components and Views
Layouts, Menus and Breadcrumbs
Refine provides Layout components that can be used to implement a layout for the application. These components are crafted using Mantine's components and includes Refine's features and functionalities such as navigation menus, headers, authentication, authorization and more.
- React Router
- Next.js
- Remix
React Router
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,react-router@^7.0.2
Content: import { Refine } from "@refinedev/core"; import dataProvider from "@refinedev/simple-rest"; import routerProvider from "@refinedev/react-router"; import { BrowserRouter, Route, Routes, Outlet } from "react-router"; import { ErrorComponent, ThemedLayoutV2, RefineThemes, useNotificationProvider, AuthPage } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import { ProductList } from "./pages/products/list"; export default function App() { return ( <BrowserRouter> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine routerProvider={routerProvider} dataProvider={dataProvider("https://api.fake-rest.refine.dev")} notificationProvider={useNotificationProvider} resources={[ { name: "products", list: "/products", } ]} > <Routes> <Route // The layout will wrap all the pages inside this route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route path="/products" element={<ProductList />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </NotificationsProvider> </MantineProvider> </BrowserRouter> ); };
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Next.js
Code Example
Dependencies: @refinedev/chakra-ui@^2.26.17,@tabler/icons-react@^3.1.0,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@refinedev/react-hook-form@^4.8.12,@chakra-ui/react@^2.5.1,@refinedev/nextjs-router@latest
Content: import { Refine } from "@refinedev/core"; import routerProvider from "@refinedev/nextjs-router/pages"; import dataProvider from "@refinedev/simple-rest"; import type { AppProps } from "next/app"; import { ThemedLayoutV2, RefineThemes, useNotificationProvider, } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; function App({ Component, pageProps }: AppProps) { return ( <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine routerProvider={routerProvider} dataProvider={dataProvider("https://api.fake-rest.refine.dev")} notificationProvider={useNotificationProvider} resources={[ { name: "products", list: "/products", }, ]} > <ThemedLayoutV2> <Component {...pageProps} /> </ThemedLayoutV2> </Refine> </NotificationsProvider> </MantineProvider> ); } export default App;
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { GetManyResponse, useMany } from "@refinedev/core"; import { List, DateField, } from "@refinedev/chakra-ui"; import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, HStack, Text, } from "@chakra-ui/react"; import { Pagination } from "../components/pagination"; export default function ProductList() { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "price", header: "Price", accessorKey: "price", }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <List> <TableContainer whiteSpace="pre-line"> <Table variant="simple"> <Thead> {getHeaderGroups().map((headerGroup) => ( <Tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <Th key={header.id}> <Text> {flexRender( header.column.columnDef .header, header.getContext(), )} </Text> </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> </TableContainer> <Pagination current={current} pageCount={pageCount} setCurrent={setCurrent} /> </List> ); };
Remix
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,@refinedev/remix-router@latest
Content: import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, } from "@remix-run/react"; import { Refine } from "@refinedev/core"; import routerProvider from "@refinedev/remix-router"; import dataProvider from "@refinedev/simple-rest"; import { RefineThemes, ThemedLayoutV2, useNotificationProvider } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; export default function App() { return ( <html lang="en"> <head> <Meta /> <Links /> </head> <body> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine routerProvider={routerProvider} dataProvider={dataProvider("https://api.fake-rest.refine.dev")} notificationProvider={useNotificationProvider} resources={[ { name: "products", list: "/products", }, ]} > <Outlet /> </Refine> </NotificationsProvider> </MantineProvider> <ScrollRestoration /> <Scripts /> <LiveReload /> </body> </html> ); }
Content: import { ThemedLayoutV2 } from "@refinedev/mantine"; import { Outlet } from "@remix-run/react"; import { LoaderFunctionArgs, redirect } from "@remix-run/node"; /** * Routes starting with `_layout` will have their children rendered inside the layout. */ export default function Layout() { return ( <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> ); }
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export default function ProductList() { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
<ThemedLayoutV2 />
component consists of a header, sider and a content area. The sider have a navigation menu items for the defined resources of Refine, if an authentication provider is present, it will also have a functional logout button. The header contains the app logo and name and also information about the current user if an authentication provider is present.
Additionally, Refine also provides a <Breadcrumb />
component that uses the Mantine's component as a base and provide appropriate breadcrumbs for the current route. This component is used in the basic views provided by Refine's Mantine package automatically.
Buttons
Refine's Mantine integration offers variety of buttons that are built above the <Button />
component of Mantine and includes many logical functionalities such as;
- Authorization checks
- Confirmation dialogs
- Loading states
- Invalidation
- Navigation
- Form actions
- Import/Export and more.
You can use buttons such as <EditButton />
or <ListButton />
etc. in your views to provide navigation for the related routes or <DeleteButton />
and <SaveButton />
etc. to perform related actions without having to worry about the authorization checks and other logical functionalities.
An example usage of the <EditButton />
component is as follows:
import React from "react";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { List, EditButton } from "@refinedev/mantine";
const columns = [
{ id: "id", header: "ID", accessorKey: "id" },
{ id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains" } },
{ id: "price", header: "Price", accessorKey: "price" },
{
id: "actions",
header: "Actions",
accessorKey: "id",
cell: function render({ getValue }) {
return <EditButton hideText recordItemId={getValue() as number} />;
},
},
];
export const ProductList = () => {
const table = useTable({ columns });
return ( /* ... */ );
};
The list of provided buttons are:
<CreateButton />
<EditButton />
<ListButton />
<ShowButton />
<CloneButton />
<DeleteButton />
<SaveButton />
<RefreshButton />
<ImportButton />
<ExportButton />
Many of these buttons are already used in the views provided by Refine's Mantine integration. If you're using the basic view elements provided by Refine, you will have the appropriate buttons placed in your application out of the box.
Views
Views are designed as wrappers around the content of the pages in the application. They are designed to be used within the layouts and provide basic functionalities such as titles based on the resource, breadcrumbs, related actions and authorization checks. Refine's Mantine integration uses components such as <Box />
, <Card />
and <Group />
to provide these views and provides customization options by passing related props to these components.
The list of provided views are:
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,react-router@^7.0.2
Content: import { Refine } from "@refinedev/core"; import { ErrorComponent, ThemedLayoutV2, RefineThemes, useNotificationProvider } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import dataProvider from "@refinedev/simple-rest"; import routerProvider, { NavigateToResource, } from "@refinedev/react-router"; import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products"; const App: React.FC = () => { return ( <BrowserRouter> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine notificationProvider={useNotificationProvider} routerProvider={routerProvider} dataProvider={dataProvider( "https://api.fake-rest.refine.dev", )} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create", meta: { canDelete: true, }, }, ]} > <Routes> <Route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route index element={<NavigateToResource resource="products" />} /> <Route path="/products" element={<Outlet />}> <Route index element={<ProductList />} /> <Route path="create" element={<ProductCreate />} /> <Route path=":id" element={<ProductShow />} /> <Route path=":id/edit" element={<ProductEdit />} /> </Route> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </NotificationsProvider> </MantineProvider> </BrowserRouter> ); }; export default App;
Content: export * from "./list"; export * from "./show"; export * from "./edit"; export * from "./create";
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); };
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductEdit = () => { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); };
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductCreate = () => { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); };
Fields
Refine's Mantine also provides field components to render values with appropriate design and format of Mantine. These components are built on top of respective Mantine components and also provide logic for formatting of the values. While these components might not always be suitable for your use case, they can be combined or extended to provide the desired functionality.
The list of provided field components are:
<BooleanField />
<DateField />
<EmailField />
<FileField />
<MarkdownField />
<NumberField />
<TagField />
<TextField />
<UrlField />
import { useShow } from "@refinedev/core";
import {
Show,
TextField,
NumberField,
MarkdownField,
} from "@refinedev/mantine";
import { Title } from "@mantine/core";
export const ProductShow = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title mt="xs" order={5}>
Name
</Title>
<TextField value={record?.name} />
<Title mt="xs" order={5}>
Description
</Title>
<MarkdownField value={record?.description} />
<Title mt="xs" order={5}>
Price
</Title>
<NumberField
value={record?.price}
options={{ style: "currency", currency: "USD" }}
/>
</Show>
);
};
Auth Pages
Auth pages are designed to be used as the pages of the authentication flow of the application. They offer an out of the box solution for the login, register, forgot password and reset password pages by leveraging the authentication hooks of Refine. Auth page components are built on top of basic Mantine components such as <TextInput />
and <Card />
etc.
The list of types of auth pages that are available in the UI integrations are:
<AuthPage type="login" />
<AuthPage type="register" />
<AuthPage type="forgot-password" />
<AuthPage type="reset-password" />
An example usage of the <AuthPage />
component is as follows:
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,react-router@^7.0.2
Content: import { Refine, Authenticated } from "@refinedev/core"; import { ErrorComponent, ThemedLayoutV2, RefineThemes, useNotificationProvider, AuthPage } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; import dataProvider from "@refinedev/simple-rest"; import routerProvider, { NavigateToResource, } from "@refinedev/react-router"; import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; import authProvider from "./auth-provider"; import { ProductList } from "./pages/products"; const App: React.FC = () => { return ( <BrowserRouter> <MantineProvider theme={RefineThemes.Blue} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> <Refine notificationProvider={useNotificationProvider} routerProvider={routerProvider} dataProvider={dataProvider( "https://api.fake-rest.refine.dev", )} authProvider={authProvider} resources={[ { name: "products", list: "/products", }, ]} > <Routes> <Route element={<Authenticated key="inner" fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}> <Route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route index element={<NavigateToResource resource="products" />} /> <Route path="/products" element={<ProductList />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Route> <Route element={<Authenticated key="outer" fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}> <Route path="/login" element={( <AuthPage type="login" formProps={{ initialValues: { email: "demo@refine.dev", password: "demodemo", }, }} /> )} /> <Route path="/register" element={<AuthPage type="register" />} /> <Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} /> <Route path="/reset-password" element={<AuthPage type="resetPassword" />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </NotificationsProvider> </MantineProvider> </BrowserRouter> ); }; export default App;
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", } ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { AuthPage } from "@refinedev/mantine"; export const LoginPage = () => { return ( <AuthPage type="login" formProps={{ initialValues: { email: "demo@refine.dev", password: "demodemo", }, }} /> ); };
Content: import { AuthPage } from "@refinedev/mantine"; export const RegisterPage = () => { return <AuthPage type="register" />; };
Content: import { AuthPage } from "@refinedev/mantine"; export const ForgotPasswordPage = () => { return <AuthPage type="forgotPassword" />; };
Content: import { AuthPage } from "@refinedev/mantine"; export const ResetPasswordPage = () => { return <AuthPage type="resetPassword" />; };
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
Error Components
Refine's Mantine integration also provides an <ErrorComponent />
component that you can use to render a 404 page in your app. While these components does not offer much functionality, they are provided as an easy way to render an error page with a consistent design language.
An example usage of the <ErrorComponent />
component is as follows:
import { ErrorComponent } from "@refinedev/mantine";
const NotFoundPage = () => {
return <ErrorComponent />;
};
Theming
Since Refine offers application level components such as layout, sidebar and header and page level components for each action, it is important to have it working with the styling of Mantine. All components and providers exported from the @refinedev/mantine
package will use the current theme of Mantine without any additional configuration.
Additionally, Refine also provides a set of carefully crafted themes for Mantine which outputs a nice UI with Refine's components with light and dark theme support. These themes are exported as RefineThemes
object from the @refinedev/mantine
package and can be used in <MantineProvider />
component of Mantine.
Code Example
Dependencies: @refinedev/mantine@^2.28.21,@refinedev/core@^4.45.1,@refinedev/react-router@latest,@refinedev/simple-rest@^4.5.4,@refinedev/react-table@^5.6.4,@tanstack/react-table@^8.2.6,@tabler/icons-react@^3.1.0,@emotion/react@^11.8.2,@mantine/core@^5.10.4,@mantine/hooks@^5.10.4,@mantine/form@^5.10.4,@mantine/notifications@^5.10.4,react-router@^7.0.2
Content: import { Refine, Authenticated } from "@refinedev/core"; import { ErrorComponent, ThemedLayoutV2, useNotificationProvider, AuthPage } from "@refinedev/mantine"; import dataProvider from "@refinedev/simple-rest"; import routerProvider, { NavigateToResource, } from "@refinedev/react-router"; import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; import authProvider from "./auth-provider"; import { ThemeProvider } from "./theme-provider"; import { ProductList, ProductCreate, ProductEdit, ProductShow } from "./pages/products"; const App: React.FC = () => { return ( <BrowserRouter> <ThemeProvider> <Refine notificationProvider={useNotificationProvider} routerProvider={routerProvider} dataProvider={dataProvider( "https://api.fake-rest.refine.dev", )} authProvider={authProvider} resources={[ { name: "products", list: "/products", show: "/products/:id", edit: "/products/:id/edit", create: "/products/create", meta: { canDelete: true, }, }, ]} > <Routes> <Route element={<Authenticated fallback={<Navigate to="/login" />}><Outlet /></Authenticated>}> <Route element={ <ThemedLayoutV2> <Outlet /> </ThemedLayoutV2> } > <Route index element={<NavigateToResource resource="products" />} /> <Route path="/products" element={<Outlet />}> <Route index element={<ProductList />} /> <Route path="create" element={<ProductCreate />} /> <Route path=":id" element={<ProductShow />} /> <Route path=":id/edit" element={<ProductEdit />} /> </Route> <Route path="*" element={<ErrorComponent />} /> </Route> </Route> <Route element={<Authenticated fallback={<Outlet />}><NavigateToResource resource="products" /></Authenticated>}> <Route path="/login" element={( <AuthPage type="login" formProps={{ initialValues: { email: "demo@refine.dev", password: "demodemo", }, }} /> )} /> <Route path="/register" element={<AuthPage type="register" />} /> <Route path="/forgot-password" element={<AuthPage type="forgotPassword" />} /> <Route path="/reset-password" element={<AuthPage type="resetPassword" />} /> <Route path="*" element={<ErrorComponent />} /> </Route> </Routes> </Refine> </ThemeProvider> </BrowserRouter> ); }; export default App;
Content: import { RefineThemes } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; import { MantineProvider, Global } from "@mantine/core"; export const ThemeProvider = ({ children }) => ( <MantineProvider // Available themes: Blue, Purple, Magenta, Red, Orange, Yellow, Green // Change the line below to change the theme theme={RefineThemes.Magenta} withNormalizeCSS withGlobalStyles > <Global styles={{ body: { WebkitFontSmoothing: "auto" } }} /> <NotificationsProvider position="top-right"> {children} </NotificationsProvider> </MantineProvider> );
Content: export * from "./list"; export * from "./show"; export * from "./edit"; export * from "./create";
Content: import React from "react"; import { useTable } from "@refinedev/react-table"; import { ColumnDef, flexRender } from "@tanstack/react-table"; import { List, ShowButton, EditButton, DeleteButton } from "@refinedev/mantine"; import { Box, Group, ScrollArea, Select, Table, Pagination } from "@mantine/core"; export const ProductList = () => { const columns = React.useMemo( () => [ { id: "id", header: "ID", accessorKey: "id", }, { id: "name", header: "Name", accessorKey: "name", meta: { filterOperator: "contains", }, }, { id: "material", header: "Material", accessorKey: "material", }, { id: "price", header: "Price", accessorKey: "price", }, { id: "actions", header: "Actions", accessorKey: "id", enableColumnFilter: false, enableSorting: false, cell: function render({ getValue }) { return ( <Group spacing="xs" noWrap> <ShowButton hideText recordItemId={getValue() as number} /> <EditButton hideText recordItemId={getValue() as number} /> <DeleteButton hideText recordItemId={getValue() as number} /> </Group> ); }, }, ], [], ); const { getHeaderGroups, getRowModel, setOptions, refineCore: { setCurrent, pageCount, current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { initialSorter: [ { field: "id", order: "desc", }, ], }, }); return ( <ScrollArea> <List> <Table highlightOnHover> <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> <br /> <Pagination position="right" total={pageCount} page={current} onChange={setCurrent} /> </List> </ScrollArea> ); };
Content: import { useShow } from "@refinedev/core"; import { Show, TextField, NumberField, MarkdownField } from "@refinedev/mantine"; 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="xs" order={5}>Name</Title> <TextField value={record?.name} /> <Title mt="xs" order={5}>Material</Title> <TextField value={record?.material} /> <Title mt="xs" order={5}>Description</Title> <MarkdownField value={record?.description} /> <Title mt="xs" order={5}>Price</Title> <NumberField value={record?.price} options={{ style: "currency", currency: "USD" }} /> </Show> ); };
Content: import { Edit, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductEdit = () => { const { saveButtonProps, getInputProps, errors, refineCore: { queryResult, autoSaveProps }, } = useForm({ initialValues: { name: "", material: "", price: 0, }, refineCoreProps: { autoSave: { enabled: true, }, }, }); return ( <Edit saveButtonProps={saveButtonProps} autoSaveProps={autoSaveProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Edit> ); };
Content: import { Create, useForm } from "@refinedev/mantine"; import { TextInput, NumberInput } from "@mantine/core"; export const ProductCreate = () => { const { saveButtonProps, getInputProps, errors, } = useForm({ initialValues: { name: "", material: "", price: 0, }, }); return ( <Create saveButtonProps={saveButtonProps}> <form> <TextInput mt={8} id="name" label="Name" placeholder="Name" {...getInputProps("name")} /> <TextInput mt={8} id="material" label="Material" placeholder="Material" {...getInputProps("material")} /> <NumberInput mt={8} id="price" label="Price" placeholder="Price" {...getInputProps("price")} /> </form> </Create> ); };
Content: const authProvider = { login: async ({ username, password }) => { (window as any).authenticated = true; return { success: true }; }, check: async () => { // auto login at first time if (typeof (window as any).authenticated === "undefined") { (window as any).authenticated = true; } return { authenticated: Boolean((window as any).authenticated) }; }, logout: async () => { (window as any).authenticated = false; return { success: true }; }, register: async () => { return { success: true }; }, forgotPassword: async () => { return { success: true }; }, resetPassword: async () => { return { success: true }; }, getIdentity: async () => ({ id: 1, name: "John Doe", avatar: "https://i.pravatar.cc/300"}) }; export default authProvider;
To learn more about the theme configuration of Mantine, please refer to the official documentation.
Inferencer
You can automatically generate views for your resources using @refinedev/inferencer
. Inferencer exports the MantineListInferencer
, MantineShowInferencer
, MantineEditInferencer
, MantineCreateInferencer
components and finally the MantineInferencer
component, which combines all in one place.
To learn more about Inferencer, please refer to the Mantine Inferencer docs.