React-Admin vs Refine: Feature Comparison

François Zaninotto
#react#react-admin

You are starting a new React project, and wondering which admin framework to use? This article will help you choose between React-Admin and Refine. The benchmark is based on the documentation of both frameworks in mid-2023.

Disclaimer: We are the editors of react-admin, so expect bias. We will try to be as objective as possible, though, and provide a link to every react-admin feature we mention.

Feature Comparison

FeatureReact-AdminRefine
Low-codeβœ…πŸŸ‘
Backend agnosticβœ…βœ…
CRUDβœ…βœ…
Usabilityβœ…πŸŸ‘
Relationshipsβœ…πŸ›‘
Form Handlingβœ…πŸŸ‘
Undoβœ…πŸ›‘
Securityβœ…βœ…
i18nβœ…πŸŸ‘
Realtimeβœ…πŸŸ‘
Themingβœ…πŸŸ‘
Preferencesβœ…πŸ›‘
Scaffoldingβœ…βœ…
Editable Datagridβœ…πŸŸ‘
Search & Filteringβœ…πŸ›‘
Audit logβœ…πŸŸ‘
Calendarβœ…πŸ›‘
Tree viewβœ…πŸ›‘
Breadcrumbβœ…πŸŸ‘
AI-powered componentsβœ…πŸ›‘
Guided Toursβœ…πŸ›‘
CLIβœ…βœ…
HeadlessπŸ›‘βœ…
SSR🟑🟑
Performanceβœ…πŸŸ‘
TypeScriptβœ…βœ…
Stableβœ…πŸ›‘
Sustainableβœ…πŸ›‘

This table uses the following symbols:

  • βœ… Full support out of the box
  • 🟑 Partial support, or support with additional development
  • πŸ›‘ No support

That's a long list! Let's dive into the details.

History

React-admin was created an open-sourced in 2016 by Marmelab, a French web agency that already authored its precursor, ng-admin. Marmelab has used react-admin in production with more than 50 clients. We've heard of about 15,000 companies using react-admin in the wild. More than 300,000 user sessions are powered by react-admin every day. React-admin is actively developed and maintained by a dedicated team at Marmelab.

Refine was created in 2021 and started as a react-admin clone. It is developed by a Turkish company called Pankod.

Low-Code

Both react-admin and Refine aim to reduce the boilerplate needed to build common features with React. Both are very efficient with basic CRUD operations. The main difference lies in the amount of code required to build both simple and complex features.

React-admin adopts a component-first API, allowing developers to build complex user interfaces by assembling components like Legos. For a user list view, react-admin needs 15 lines of code:

import * as React from 'react';
import { List, Datagrid, TextField, UrlField, EmailField } from 'react-admin';

export const UserList = () => (
    <List>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="name" />
            <EmailField source="email" />
            <TextField source="phone" />
            <UrlField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    </List>
);

<List> fetches the users from the API, and creates a context with the data for the <Datagrid> to use. This means you don't have to manually plug components - they just work together.

If you need full control over the UI, you can also use the low-level hooks API of react-admin:

import * as React from 'react';
import {
    useListController,
    Datagrid,
    TextField,
    EmailField,
    UrlField,
} from 'react-admin';

export const UserList = () => {
    const { data, total, isLoading, sort } = useListController();
    return (
        <Datagrid
            data={data}
            total={total}
            isLoading={isLoading}
            sort={sort}
            bulkActionButtons={false}
        >
            <TextField source="id" />
            <TextField source="name" />
            <EmailField source="email" />
            <TextField source="phone" />
            <UrlField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    );
};

Refine, on the other hand, only provides the last option: using hooks and manually passing data to components. For a user list view, Refine requires more than 30 lines of code:

import React from 'react';
import { List, useDataGrid, DateField, UrlField } from '@refinedev/mui';
import { DataGrid } from '@mui/x-data-grid';

const UserList = () => {
    const { dataGridProps } = useDataGrid();

    const columns = [
        { field: 'id', headerName: 'Id' },
        { field: 'name', headerName: 'Name' },
        {
            field: 'email',
            headerName: 'Email',
            renderCell: function render({ row }) {
                return <EmailField value={row.email} />;
            },
        },
        { field: 'phone', headerName: 'Phone At' },
        {
            field: 'website',
            headerName: 'Website',
            renderCell: function render({ row }) {
                return <UrlField value={row.website} />;
            },
        },
        { field: 'company.name', headerName: 'Company' },
    ];

    return (
        <List>
            <DataGrid {...dataGridProps} columns={columns} autoHeight />
        </List>
    );
};

This directly translates to the amount of boilerplate code required to implement the same feature, which is significantly higher with Refine than with react-admin. That's because react-admin focuses on developer experience. And that doesn't come at the expense of flexibility: react-admin is fully customizable, and you can always drop down to the low-level hooks API to build your own components.

Backend Agnostic

Both frameworks are backend agnostic and can be used with any data source (REST or GraphQL). They both provide connectors for the most popular API formats, although react-admin provides more connectors:

API FormatReact-AdminRefine
AirTableπŸ›‘βœ…
AppWriteβœ…βœ…
AWS Amplifyβœ…πŸ›‘
Blitz-jsβœ…πŸ›‘
Configurable Identity Property REST Clientβœ…πŸ›‘
coreBOSβœ…πŸ›‘
Directusβœ…βœ…
Django Rest Frameworkβœ…πŸ›‘
ElideπŸ›‘βœ…
Eveβœ…πŸ›‘
Express & Mongooseβœ…πŸ›‘
Express & Sequelizeβœ…πŸ›‘
FakeRestβœ…πŸ›‘
Feathersjsβœ…πŸ›‘
Firebase Firestoreβœ…βœ…
Firebase Realtime Databaseβœ…πŸ›‘
GeoServerβœ…πŸ›‘
Google Sheetsβœ…πŸ›‘
GraphQL (generic)βœ…πŸ›‘
GraphQL (simple)βœ…βœ…
HALβœ…πŸ›‘
Hasuraβœ…βœ…
Hydra / JSON-LDβœ…πŸ›‘
IndexedDB (via LocalForage)βœ…πŸ›‘
IndexedDBβœ…πŸ›‘
JSON APIβœ…πŸ›‘
JSON HALβœ…πŸ›‘
JSON serverβœ…πŸ›‘
LocalStorageβœ…πŸ›‘
LocalStorage (via LocalForage)βœ…πŸ›‘
Loopback3βœ…πŸ›‘
Loopback4βœ…πŸ›‘
Loopback4 CRUDβœ…πŸ›‘
MedusaπŸ›‘βœ…
Mixerβœ…πŸ›‘
Moleculer Microservicesβœ…πŸ›‘
NestJS CRUDβœ…βœ…
ODataβœ…πŸ›‘
OpenCRUDβœ…πŸ›‘
Parseβœ…πŸ›‘
PostGraphileβœ…πŸ›‘
PostgRESTβœ…πŸ›‘
Prisma v1βœ…πŸ›‘
Prisma v2 (GraphQL)βœ…πŸ›‘
Prisma v2 (REST)βœ…πŸ›‘
ProcessMaker3βœ…πŸ›‘
REST-HAPIβœ…πŸ›‘
Sails.jsβœ…πŸ›‘
SQLiteβœ…πŸ›‘
RESTβœ…βœ…
Spring Bootβœ…πŸ›‘
Strapiβœ…βœ…
Supabaseβœ…βœ…
SurrealDBβœ…πŸ›‘
TreeQL / PHP-CRUD-APIβœ…πŸ›‘
WooCommerce REST APIβœ…πŸ›‘

They both allow combining multiple data sources in a single application.

React-admin has one major advantage: the ability to use an in-browser data provider based on a local database, FakeRest. This lets React developers work in isolation before the backend is ready. It also lets them build offline-first applications, which can be a requirement for some use cases.

CRUD

Both react-admin and Refine provide a set of components and hooks to build CRUD applications. They both provide the same features:

  • List views with pagination, sorting, and filtering
  • Edition and creation views with form validation
  • Deletion
  • Bulk actions

If you need to build a basic phpMyAdmin-like CRUD application, both frameworks will do the job.

That being said, you will need more code to build the same feature with Refine than with react-admin (see the "Low-Code" section above).

Usability

React-admin and Refine both build up on UI kits like Material-UI. The usability of the base components depends on the UI kit. As both frameworks can work with Material-ui, they have equal usability in theory.

But the composition of base components can lead to pages that are more or less usable. React-admin is built with a user-centric approach, and addresses usability issues in several ways:

  • by providing higher-level components that combine base components in the most usable way (e.g. List layouts, filter layouts, Form layouts, complex menus, etc.)
  • by choosing theme defaults that work best for desktop, where most B2B apps and admins are used, rather than for mobile. This results in denser pages, with more information displayed at once.
  • by updating the UI as soon as possible (see performance below)
  • by providing real-world examples with large amounts of data and complex forms

react-admin example UI

Refine, on the other hand, is "headless" by design. It provides a set of low-level hooks and lets the developer build the UI from scratch. Its default theme is designed for mobile and contains so large margins on desktop that users need to scroll constantly.

Refine example UI

A good example of poor usability by default in Refine is the blog post edition form above: the "delete" button is close to the "save" button, which can lead to accidental deletions. In react-admin, the "delete" button is far away from the "save" button. This is a small detail, but there are many little problems like that in Refine, and they make a big difference in the usability of the application. Check the demos of both frameworks to see the difference.

Besides, as Refine doesn't provide many components, developers need more time and expertise to reach the same level of usability as react-admin.

Relationships

Most admins and B2B apps are built over a relational data model. Displaying and editing data from related entities is a very common requirement.

React-admin provides hooks and components to handle one-to-many, many-to-one, and many-to-many relationships. For instance, to fetch and display the author of a post, you can use the <ReferenceField> component:

import { List, Datagrid, TextField, ReferenceField } from 'react-admin';

export const PostList = () => (
    <List>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="title" />
            <ReferenceField source="user_id" reference="users" />
        </Datagrid>
    </List>
);

<ReferenceField> deduplicates and aggregates the API calls to fetch the authors of all the posts in the list in one call.

In edition and creation views, react-admin lets you edit related records in sub-forms, for instance with <ReferenceManyInput>:

import {
    Edit,
    SimpleForm,
    TextInput,
    NumberInput,
    ReferenceInput,
    SelectInput,
} from 'react-admin';
import { ReferenceManyInput } from '@react-admin/ra-relationships';

const ProductEdit = () => (
    <Edit mutationMode="optimistic">
        <SimpleForm>
            <TextInput source="name" />
            <NumberInput source="price" />
            <ReferenceInput source="category_id" reference="categories" />
            <ReferenceManyInput reference="variants" target="product_id">
                <SimpleFormIterator inline>
                    <TextInput source="sku" />
                    <SelectInput source="size" choices={sizes} />
                    <SelectInput source="color" choices={colors} />
                    <NumberInput source="stock" defaultValue={0} />
                </SimpleFormIterator>
            </ReferenceManyInput>
        </SimpleForm>
    </Edit>
);

React-admin offers a dozen components dedicated to relationships, and they are great time savers when using a relational model.

Refine, on the other hand, doesn't provide any support for relationships. You have to manually fetch the related resources, handle loading and error cases, optimize the data fetching, and so on. This is a lot of boilerplate code, and it's easy to get it wrong.

Form Handling

The ability to edit and create entities implies the ability to handle forms. This means being able to arrange inputs in complex form layouts, handling default values and validation, and hooking the form state with the API.

React-admin relies on react-hook-form for the low-level form API. It provides a set of components and hooks on top of it to hide the complexity of binding form inputs to the form state. For instance, to create a form allowing you to edit a Book entity, you just need a few components:

import {
    Edit,
    SimpleForm,
    TextInput,
    ReferenceInput,
    required,
} from 'react-admin';

export const BookEdit = () => (
    <Edit>
        <SimpleForm>
            <TextInput source="id" validate={required()} />
            <TextInput source="title" validate={required()} />
            <ReferenceInput
                source="user_id"
                reference="users"
                validate={required()}
            />
            <TextInput source="body" multiline />
        </SimpleForm>
    </Edit>
);

In comparison, Refine only provides a single useForm hook, which forces you to manually bind inputs to the form state. Even for simple forms, this leads to a lot of boilerplate. For instance, to create the same form as above, you need to write:

import React from 'react';
import { Edit, useAutocomplete } from '@refinedev/mui';
import { TextField, Autocomplete, Box } from '@mui/material';
import { useForm } from '@refinedev/react-hook-form';
import { Controller } from 'react-hook-form';

const BookEdit = () => {
    const {
        saveButtonProps,
        refineCore: { queryResult },
        register,
        control,
        formState: { errors },
    } = useForm();

    const samplesData = queryResult?.data?.data;

    const { autocompleteProps: userAutocompleteProps } = useAutocomplete({
        resource: 'users',
        defaultValue: samplesData?.user?.id,
    });

    return (
        <Edit saveButtonProps={saveButtonProps}>
            <Box
                component="form"
                sx={{ display: 'flex', flexDirection: 'column' }}
                autoComplete="off"
            >
                <TextField
                    {...register('id', {
                        required: 'This field is required',
                    })}
                    error={!!(errors as any)?.id}
                    helperText={(errors as any)?.id?.message}
                    margin="normal"
                    fullWidth
                    InputLabelProps={{ shrink: true }}
                    type="number"
                    label="Id"
                    name="id"
                    disabled
                />
                <TextField
                    {...register('title', {
                        required: 'This field is required',
                    })}
                    error={!!(errors as any)?.title}
                    helperText={(errors as any)?.title?.message}
                    margin="normal"
                    fullWidth
                    InputLabelProps={{ shrink: true }}
                    type="text"
                    label="Title"
                    name="title"
                />
                <Controller
                    control={control}
                    name="user"
                    rules={{ required: 'This field is required' }}
                    // eslint-disable-next-line
                    defaultValue={null as any}
                    render={({ field }) => (
                        <Autocomplete
                            {...userAutocompleteProps}
                            {...field}
                            onChange={(_, value) => {
                                field.onChange(value);
                            }}
                            getOptionLabel={item => {
                                return (
                                    userAutocompleteProps?.options?.find(
                                        p =>
                                            p?.id?.toString() ===
                                            item?.id?.toString(),
                                    )?.name ?? ''
                                );
                            }}
                            isOptionEqualToValue={(option, value) =>
                                value === undefined ||
                                option?.id?.toString() ===
                                    (value?.id ?? value)?.toString()
                            }
                            renderInput={params => (
                                <TextField
                                    {...params}
                                    label="Author"
                                    margin="normal"
                                    variant="outlined"
                                    error={!!(errors as any)?.author?.id}
                                    helperText={
                                        (errors as any)?.author?.id?.message
                                    }
                                    required
                                />
                            )}
                        />
                    )}
                />
                <TextField
                    {...register('body')}
                    error={!!(errors as any)?.body}
                    helperText={(errors as any)?.body?.message}
                    margin="normal"
                    fullWidth
                    InputLabelProps={{ shrink: true }}
                    type="text"
                    label="Body"
                    name="body"
                    multiline
                />
            </Box>
        </Edit>
    );
};

React-admin form handling goes way further than a simple wrapper on top of react-hook-form:

  • It provides 6 form layout components to arrange inputs in a list, tabs, or accordion, including the ability to highlight invalid input groups
  • It provides 30+ specialized form inputs for handling arrays, rich text, dates, relationships, translatable text, etc.
  • It allows to configure forms through a serializable JSON Schema, which can be used to generate forms automatically
  • It provides advanced features like nested forms, auto-save, undo, warn when unsaved changes, etc.

If your application has more than a few forms, you will save a lot of time using react-admin.

Undo

React-admin lets users cancel every update or deletion, and revert to the previous state, during a "grace" period. This undo feature is enabled by default, doesn't need any particular backend support, and works with all data providers.

Even though updates appear immediately due to Optimistic Updates (see performance), react-admin only sends them to the server after a short delay (about 5 seconds). During this delay, the user can undo the action, and react-admin will never send the update.

Refine doesn't provide any undo feature.

Security

Both react-admin and refine offer basic security with the ability to restrict access to resources based on the user's authentication status. Both frameworks also offer fine-grained permissions, to let you control access to resources and actions based on the user's role.

They also provide connectors to various authentication backends:

BackendReact-AdminRefine
Amplifyβœ…πŸ›‘
AppwriteπŸ›‘βœ…
Auth0βœ…βœ…
Casdoorβœ…πŸ›‘
Cognitoβœ…πŸ›‘
Directusβœ…πŸ›‘
Google Authβœ…βœ…
Keycloakβœ…βœ…
MSALβœ…πŸ›‘
Strapiβœ…βœ…
Supabaseβœ…βœ…

In addition, react-admin provides specialized components with baked-in role-based access control (RBAC) support, so you just need to define the roles and permissions in your application, and the components will automatically hide the UI elements the user doesn't have access to. These components are part of the ra-rbac enterprise package.

Both components will let you implement any kind of security model. Sometimes you will find existing connectors for your authentication backend, sometimes you will have to write your own.

I18n

Both frameworks allow to translate the user interface in a backend-agnostic way, thanks to an i18nProvider. They recommend a different default backend (polyglot for react-admin, i18Next for Refine), but they all support the expected features of an i18n backend (pluralization, interpolation, etc.).

They provide a set of hooks and components to deal with interface translation;

React-AdminRefine
useTranslate, useLocaleState, <LocalesMenuButton>useTranslate, useGetLocale, useSetLocale

React-admin displays a language switcher automatically as soon as you define more than one locale. This component remembers the chosen locale across sessions. Refine doesn't provide such a component, you have to build it by hand.

React-admin provides translation messages for almost 40 locales, while Refine only supports one:

LanguageReact-AdminRefine
Arabicβœ…πŸ›‘
Armenianβœ…πŸ›‘
Belarusianβœ…πŸ›‘
Brazilian Portugueseβœ…πŸ›‘
Bulgarianβœ…πŸ›‘
Catalanβœ…πŸ›‘
Chineseβœ…πŸ›‘
Chineseβœ…πŸ›‘
Czechβœ…πŸ›‘
Danishβœ…πŸ›‘
Dutchβœ…πŸ›‘
Englishβœ…βœ…
Estonianβœ…πŸ›‘
Farsiβœ…πŸ›‘
Finnishβœ…πŸ›‘
Frenchβœ…πŸ›‘
Germanβœ…πŸ›‘
Greekβœ…πŸ›‘
Hebrewβœ…πŸ›‘
Hindiβœ…πŸ›‘
Hungarianβœ…πŸ›‘
Indonesianβœ…πŸ›‘
Italianβœ…πŸ›‘
Japaneseβœ…πŸ›‘
Koreanβœ…πŸ›‘
Latvianβœ…πŸ›‘
Lithuanianβœ…πŸ›‘
Malayβœ…πŸ›‘
Norwegianβœ…πŸ›‘
Polishβœ…πŸ›‘
Portugueseβœ…πŸ›‘
Romanianβœ…πŸ›‘
Russianβœ…πŸ›‘
Slovakβœ…πŸ›‘
Spanishβœ…πŸ›‘
Swedishβœ…πŸ›‘
Turkishβœ…πŸ›‘
Ukrainianβœ…πŸ›‘
Vietnameseβœ…πŸ›‘

Realtime

Modern web apps are expected to be reactive and to update the UI in real-time when the data changes on the server. This is especially true for admin apps, which are used by several users at the same time.

Both react-admin and Refine provide a set of hooks to handle real-time updates. They use a similar approach to allow developers to subscribe to data changes through a backend-agnostic adapter. It's part of the Data Provider in react-admin, and it's a standalone Realtime Provider in refine.

The real-time features are part of the ra-realtime enterprise package on react-admin and are available for free in Refine.

The term "Real-time" covers many different features:

FeatureReact-AdminRefine
Live List Updatesβœ…βœ…
Live Edition Updatesβœ…βœ…
Live Show Updatesβœ…βœ…
Publish/subscribeβœ…βœ…
Reactive Data Provider hooksβœ…βœ…
Locksβœ…πŸ›‘
Menu badgesβœ…πŸ›‘
Server-sent notificationsβœ…πŸ›‘

Refine provides two "live modes" to avoid overwriting other users' changes. React-admin doesn't even propose the destructive mode, as no user would ever need it: When a live update is received on a record, react-admin always asks the user to confirm the update.

React-admin provides "reactive" data provider hooks, that let you subscribe to data changes in the data provider.

Here is the list of hooks and components provided by both frameworks:

React-adminRefine
usePublishusePublish
useSubscribeuseSubscription
useSubscribeCallbackuseOne
useSubscribeToRecorduseList
useSubscribeToRecordListuseInfiniteList
useLockuseMany
useUnlock
useGetLock
useGetLockLive
useGetLocks
useGetLocksLive
useLockOnMount
useLockOnCall
useGetListLive
useGetOneLive
<ListLive>
<EditLive>
<ShowLive>
<MenuLive>
<MenuLiveItemLink>

So Refine offers basic real-time features for free, while react-admin provides complete, battle-tested features as part of the enterprise package.

Theming

Both react-admin and refine let you tweak the look and feel of the application.

Theming in react-admin

Refine offers basic support, and react-admin offers full support.

FeatureReact-AdminRefine
Customize fonts, colors, marginsβœ…βœ…
Themeable componentsβœ…πŸ›‘
Dark modeβœ…πŸŸ‘
Toggle Theme Buttonβœ…πŸŸ‘
Default to browser preferencesβœ…πŸ›‘

React-admin supports style overrides, i.e. theming beyond the basic features offered by refine. For instance, you can override the style of the title in the app bar in a custom theme as follows:

import { defaultTheme } from 'react-admin';

const theme = {
    ...defaultTheme,
    components: {
        ...defaultTheme.components,
        RaAppBar: {
            styleOverrides: {
                root: {
                    '& ..RaAppBar-title': {
                        fontWeight: 500,
                    },
                },
            },
        },
    },
};

const App = () => <Admin theme={theme}>// ...</Admin>;

React-admin fully supports dark mode: you can define both a light and dark theme in your admin, and users will automatically see the theme that corresponds to their browser preferences. They will also be able to switch between themes with a button in the app bar - and this choice will be persisted across sessions.

import { Admin, defaultTheme } from 'react-admin';

const lightTheme = defaultTheme;
const darkTheme = { ...defaultTheme, palette: { mode: 'dark' } };

const App = () => (
    <Admin
        dataProvider={...}
        theme={lightTheme}
        darkTheme={darkTheme}
    >
        // ...
    </Admin>
);

Preferences

React-admin offers built-in tools to manage and store user preferences. This includes:

  • A backend-agnostic store that persists user choices across sessions
  • Store-enabled components, persisting their state in the store (e.g. <ToggleThemeButton>, <LocalesMenuButton, etc)
  • A Saved Queries feature, to let users save their favorite filters
  • A set of hooks to let you add preferences to your own components
  • The ability to configure the UI of certain components right in the browser, adding a no-code layer on top of react-admin.

Refine has no support for user preferences.

Scaffolding

Both react-admin and Refine let you build fully-functional CRUD interfaces in no time by introspecting the API and generating the code for you. This feature is called Guessers in react-admin, and Inferencers in Refine.

React-admin guessers

The main difference is that react-admin guessers generates concise and readable code, while the pages generated by Refine's inferencers use a lot of code and are therefore harder to customize.

Editable Datagrid

Both frameworks provide a way to display a list of records in a table, and let users edit them in place. This feature is part of the ra-editable-datagrid enterprise package in react-admin and is available for free in Refine.

However, Refine only supports Editable Table in Ant Design - not in Material-UI. And its support is limited to a single hook, useEditableTable, which doesn't provide any UI component. You have to build the UI yourself. On the other hand, react-admin provides an <EditableDatagrid> component, which leverages the existing library of Input components and even works for editing related records.

Search & Filtering

Searching for records is a crucial requirement in admin and B2B apps.

React-admin offers various search experiences out-of-the-box:

  • The Filter Button/Form Combo
  • The <FilterList> Sidebar
  • The <StackedFilters> Dialog
  • The Global <Search>

These components hide the inner complexity of the filtering mechanism. They are also highly customizable and can be used as building blocks to create your own search experience.

import { SavedQueriesList, FilterLiveSearch, FilterList, FilterListItem } from 'react-admin';
import { Card, CardContent } from '@mui/material';
import MailIcon from '@mui/icons-material/MailOutline';
import CategoryIcon from '@mui/icons-material/LocalOffer';

export const PostFilterSidebar = () => (
    <Card sx={{ order: -1, mr: 2, mt: 9, width: 200 }}>
        <CardContent>
            <SavedQueriesList />
            <FilterLiveSearch >
            <FilterList label="Subscribed to newsletter" icon={<MailIcon />}>
                <FilterListItem label="Yes" value={{ has_newsletter: true }} />
                <FilterListItem label="No" value={{ has_newsletter: false }} />
            </FilterList>
            <FilterList label="Category" icon={<CategoryIcon />}>
                <FilterListItem label="Tests" value={{ category: 'tests' }} />
                <FilterListItem label="News" value={{ category: 'news' }} />
                <FilterListItem label="Deals" value={{ category: 'deals' }} />
                <FilterListItem label="Tutorials" value={{ category: 'tutorials' }} />
            </FilterList>
        </CardContent>
    </Card>
);

Refine only offers one component, <FilterDropdown>, and only for Ant Design. For other UI kits, there is nothing to let you build a search experience.

Audit Log

A common feature of admin and B2B apps is the ability to track changes on records and display a timeline of these changes. This allows tracking who changed what, and when.

Both react-admin and Refine provide hooks to record and query the history of changes on a record. The audit log feature is part of the ra-audit-log enterprise package for react-admin and is available for free in Refine.

But there are many differences.

In addition to hooks, react-admin provides UI components to display a timeline, an event list, of the timeline of changes for a given record. In total, react-admin offers 12 React components to build and customize your event log UI. Refine only provides hooks, and you have to build the UI yourself.

React-admin provides adapters to automatically record user actions in the event log, compatible with any API backend. In Refine, you have to record each action manually.

So Refine offers a very basic headless audit log solution for free, while react-admin provides a complete solution with a user interface in an enterprise package.

Calendar

If you need to display and manipulate events, drag and resize appointments, and browse a calendar, which framework should you choose?

React-admin offers a full-featured <Calendar> component, which supports:

  • month, week, and day views
  • list view
  • drag and resize events
  • whole-day events
  • creating an event by clicking on the calendar
  • edition of event title, and metadata
  • events spanning multiple days
  • recurring events
  • background events
  • theming
  • locales and timezones
  • resource time grid (e.g. rooms) (requires an additional license from Full Calendar)

React-admin's <Calendar> module lets you not only visualize events, but also create, edit, reschedule, and delete them.

Building a Calendar page with react-admin takes a few lines of code:

import React from "react";
import {
  Admin,
  Resource,
  List,
  SimpleForm,
  TextInput,
  DateTimeInput,
} from "react-admin";
import { CompleteCalendar } from "@react-admin/ra-calendar";

import dataProvider from "./dataProvider";

const EventList = () => (
  <CompleteCalendar>
    <SimpleForm>
      <TextInput source="title" autoFocus />
      <DateTimeInput source="start" />
      <DateTimeInput source="end" />
    </SimpleForm>
  </CompleteCalendar>
);

export const Basic = () => (
  <Admin dataProvider={dataProvider}>
    <Resource name="events" list={EventList} />
  </Admin>
);

The <Calendar> component is part of the ra-calendar enterprise package.

Refine doesn't provide any calendar hook or component. They have a working example of integration of Ant Design's Calendar component, but it's not a reusable component, it's not documented, and you're reduced to a manual integration. Furthermore, this component is in read-only mode, you can't create or edit events.

Tree View

To manage directories, categories, and any other hierarchical data, admins often rely on tree structures. Navigating and editing tree structures can be tricky, so it's better to rely on robust, tested components.

React-admin provides a complete solution for managing Tree structures:

  • A set of specialized hooks at the data provider level
  • a <Tree> component to render a read-only tree
  • a <TreeWithDetails> component to render a tree with a detail panel. It also allows editing the tree structure (add or remove nodes, move them in the hierarchy, etc.)
  • a <TreeInput> component to select a node in a tree

The tree features are part of the ra-tree enterprise package in react-admin.

Refine doesn't provide any hook, component, or example for tree structures.

Breadcrumbs are a common UI pattern in apps with a deep navigation structure. They let users know where they are in the app and allow them to navigate back to a previous page.

Both React-admin and refine provide a <Breadcrumb> component and allow for nested resources. React-admin's component is part of the ra-navigation enterprise package, Refine's is available for free.

The two frameworks take different approaches to breadcrumbs:

  • In react-admin, each page can define its location in the site hierarchy, and the <Breadcrumb> component will display it. This is a very flexible approach, as it lets you define the breadcrumb for each page, customize the labels according to the loaded data, etc. In addition, the breadcrumb is automatically synchronized with the tree menu, so users are never lost.
  • In Refine, the site hierarchy is defined in the admin configuration and doesn't have access to dynamic data. Breadcrumb items can't be customized. Non-CRUD pages can't have a breadcrumb.

React-admin's approach is more complete, but also more complex. It's not a great fit for simple applications, but do simple applications need a breadcrumb?

AI-powered components

Our productivity tools deserve all the automation possible. Recent advances in AI allow to automate many tasks that were previously manual. React-admin provides a set of AI-powered components that improve the user experience by accelerating common tasks:

  • <PredictiveTextInput> is an autocompletion powerhouse. From filling out forms to crafting emails, it predicts and provides auto-completion for any user input. Now, users can say goodbye to repetitive typing, spelling errors, and unnecessary delays in their workflow.
  • <SmartRichTextInput> is a WYSIWYG editor taking full advantage of AI capabilities. It not only offers autocorrect but goes several steps further with AI-powered features such as 'Summarize', 'Rephrase', and 'Continue Writing'.

Refine has no such components.

Guided Tours

For complex web apps, you can onboard new users via training or documentation, but the most efficient way is a guided tour directly integrated into the application.

React-admin provides ra-tour, an Enterprise Edition module designed to build interactive tutorials to showcase and explain important features of your apps.

ra-tour

Each step of a guided tour can highlight a particular page element and trigger actions for the user. You can even add more capabilities to the tour player to let you execute your own hooks.

Refine doesn't offer any tour component.

CLI

To automate common development tasks, command-line tools are a must in application frameworks.

Both react-admin and Refine come with a project creation CLI (create-react-admin on one case, create-refine-app on the other). These tools save you a few minutes of setup for new projects and generate an up-to-date project skeleton.

Refine also provides a refine CLI, which is used to generate CRUD pages for your resources, update the refine libraries, start and build the project, and eject the refine dependencies.

React-admin doesn't provide such a CLI, but it provides alternative solutions for all these tasks:

  • For CRUD page scaffolding, react-admin relies on Guessers that dump the code in the browser console. It's your job to copy-paste the code in your project, but it's more flexible, as it lets you name your components the way you want, and customize them.
  • For updating dependencies, react-admin provides nothing special, as package managers (npm, yarn) are very good at this task. React-admin doesn't want to invent new ways of addressing already-solved problems.
  • The same goes for starting and building the project: react-admin relies on the vite package, which is the de-facto standard for single-page React applications.
  • As for the need to eject (or "swizzle", as the Refine team puts it), the react-admin teams think this is a bad idea. If you need to replace a component of the library with one of your own, you probably don't need to start from the framework code, which addresses more use cases than your own, and adds several levels of indirection because it has to be backend agnostic.

So if, at first sight, Refine seems to provide more features, react-admin provides alternative solutions for all of them, and in a more flexible way.

Headless

If you are building a React application, you may wonder what's the best combination of libraries and UI kits for your project.

React-admin is an opinionated framework, which has selected the best dependencies for single-page applications:

  • React-router for routing
  • React-hook-form for form handling
  • React-query for data fetching
  • Material-ui for the UI kit

React-admin is composed of a UI agnostic core (ra-core), on top of which the Material-UI integration is built (ra-ui-material-ui). It's possible to plug another UI kit with the core.

Refine is "headless", and mostly focuses on the core part. It provides lightweight adapters for several UI kits (Ant design, Material-UI, Mantine, Chakra UI), other routers, and even other form frameworks.

Refine supports more than one UI kit, but only in a very basic way. Its integration with each UI kit is nowhere near the integration of react-admin with Material-UI. This directly translates to the number of available components:

  • React-admin: 150+ components
  • Refine: 30 components

Besides, is it a good thing to have several versions of the framework?

By focusing on one set of libraries, react-admin has built a solid integration between them, removing boilerplate to let developers focus on the business logic of their application. React-admin also goes way further than just providing the glue between these libraries. It's a true framework, in the sense that it encourages the best practices, adds features on top of these libraries, and provides a consistent API.

Being tied to material-ui might be seen as a limitation in terms of theming. Material-UI components a very customizable, though, and react-admin provides a theming system to customize the look and feel of your application. So even without choosing another UI kit, you can still build user interfaces that look just like your brand.

In addition, by being too agnostic, refines adds many layers of abstraction and a lot of complexity. By being more focused, react-admin provides a better developer experience and goes way further than Refine in terms of advanced features.

SSR

React-admin is a framework dedicated to Single-Page Apps as it is designed for long lived-applications (admin apps, B2B apps, ERPs), which are not concerned with SEO. It can integrate with full-stack frameworks like Next.js or Remix. It supports Server-Side-Rendering mode, where each page is rendered on the server and sent in HTML to the client, on Remix but not on Next.js.

Refine targets both Single-Page Apps and Server-Side Rendering, and even static sites. It does that by adding an abstraction layer on top of its router to allow integration of the router of the full-stack framework. This lets you use Refine both for admin apps and public-facing websites like e-commerce websites or blogs.

None of the frameworks support React Server Components.

The work to integrate the router of full-stack frameworks isn't hard, so why doesn't react-admin address it? Because the react-admin team thinks it's a bad idea. The CRUD architecture that both frameworks use is not a good fit for public-facing websites. You don't need react-admin or Refine to build a website centered around the concept of pages. You need a server-first approach, an ORM like Prisma, and carefully crafted HTML.

So if you're building a B2B website with strong SEO requirements, both react-admin and Refine are a bad fit.

Performance

The productivity of a web app is directly correlated to the performance of its framework.

React-admin applications are fast by default, thanks to various performance optimizations:

  • Non-Blocking Data Fetching: Instead of waiting for API data before starting to render the UI, React-admin initiates the rendering process immediately. This strategy ensures a snappy application where user interactions receive instant feedback, outperforming Server-side Rendered apps by eliminating waiting times for server responses.
  • Stale While Revalidate: This technique allows pages to display data from previous requests while newer data is being fetched. In most instances, the fresh data remains the same (e.g., when revisiting a listing page), ensuring users won't notice any delays due to network requests.
  • Local Database Mirror: React-admin populates its internal cache with individual records fetched using dataProvider.getList(). When a user views a specific record, React-admin leverages its internal database to pre-fill the dataProvider.getOne() query response. As a result, record details are displayed instantaneously, without any wait time for server responses.
  • Optimistic Updates: When a user edits a record and hits the "Save" button, React-admin immediately updates its local database and displays the revised data, before sending the update query to the server. The resulting UI changes are instant - no server response wait time is required. The same logic applies to record deletions.
  • Query Deduplication: React-admin identifies instances where multiple components on a page call the same data provider query for identical data. In such cases, it ensures only a single call to the data provider is made.
  • Query Aggregation: React-admin intercepts all calls to dataProvider.getOne() for related data when a <ReferenceField> is used in a list. It aggregates and deduplicates the requested ids, and issues a single dataProvider.getMany() request. This technique effectively addresses the n+1 query problem, reduces server queries, and accelerates list view rendering.
  • Opt-In Query Cache: React-admin provides an option to prevent refetching an API endpoint for a specified duration, which can be used when you're confident that the API response will remain consistent over time.

Refine only uses one of these practices (Stale While Revalidate), and has no performance optimization. As a consequence, Refine apps are slower than react-admin apps.

By the way, the size of the packages on npm isn't a good proxy for the performance. If you look at the source of the react-admin package, you will see that it is mostly documentation - the actual code is in sub-packages like ra-core or ra-ui-materialui. If you want to compare the weights of the frameworks, you must compare the same application implemented with each framework, and unfortunately, there is no such thing. The closest is the e-commerce demo of each framework. The react-admin implementation weighs 780kB, including a local test database of 320kB. The Refine implementation, which has fewer features and no test database, weighs 554kB.

Finally, with 6 years of feedback and experience building large web applications, the react-admin team has gone through numerous profiling sessions, which helped patch the existing slowdowns, and add safeguards to prevent developers from building slow code. Two react-admin hooks give a good example of that optimization effort: useEvent and useAsyncEffect.

TypeScript

Both frameworks are written in TypeScript and expose types so you can detect errors at compile time. You can use either TypeScript or JavaScript to write your application.

Stable

React-admin puts a strong emphasis on backward compatibility. It waits at least 2 years between two major versions and avoids breaking changes in between. It's currently at version 4.11. Among the core design principles is a motto:

Backward Compatibility Is More Important Than New Features

This is because Marmelab uses react-admin for its own projects, and doesn't want to spend time upgrading them every few months. React-admin is used in very large projects, where breaking changes can be very costly.

Refine is still in its early days, and has already released 4 major versions in 2 years. It's currently at version 4.22. This means they have broken backward compatibility three times faster than react-admin.

If you want to build an app that will still work 5 years from now, react-admin is a safer bet.

Sustainable

The popularity of the react-admin framework became a problem in 2020, as it required a dedicated team to deal with support, bug fixes, dependency updates, and so on. So Marmelab decided to add an enterprise layer on top of react-admin and start charging for it. The Enterprise Edition now covers the development cost of react-admin (both open-source and enterprise), so it is sustainable in the long run. React-admin is self-funded and has no debt.

Refine raised 1M$ in 2022 from 500 Istanbul, a Turkish VC. They have no revenue so far - their swag store cannot generate enough revenue to pay the large team they've assembled. They plan to add an Enterprise Edition in the future, and they'll need to sell it at a high price to pay back investors.

In summary, react-admin is already sustainable and uses its revenue to pay the team that builds it. Refine isn't, and its future revenue will serve to make rich investors richer - if it ever manages to become sustainable.

One more word: for the react-admin team, "sustainable" has a second meaning: that of not contributing to climate change. Marmelab also edits GreenFrame, an open-source tool for measuring the carbon emissions of web applications. The react-admin team applies the best practices of green coding, so the apps built with react-admin have low emissions.

Conclusion

You are starting a new React project, and wondering which admin framework to use? We recommend, of course, react-admin for most use cases.

But we are the editors of react-admin, so you shouldn't trust us. The foundation for your next web app is an important choice, so you should do your due diligence and at least:

  • follow the "Getting started" tutorial,
  • go through the documentation and examples,
  • check the issue tracker and the release notes, and
  • ask around for feedback

...for both react-admin and Refine.

If you choose react-admin, we'll be super pleased, and we're confident that you will find it productive and a joy to use.

Did you like this article? Share it!