Mantine, a library where everything is already there
Building an admin panel usually starts with a week of assembling things that look the same in every project: a sortable table, a date picker, a field with suggestions, a modal, notifications, a form with validation.
Mantine ships almost all of that in one package: over a hundred twenty components, dozens of utility hooks, and a separate form system. There is no copy the code philosophy and no picking parts from several libraries, just one set covering most needs. One exception is worth noting straight away, and it is the table: the Table component gives you the appearance, while sorting and pagination are yours to write.
What comes included
It helps to know what separates this library from sets of visual components, since the difference lies not in button count.
Composite components are the main value. A calendar with date ranges, a select with search and multiple choice, a rich text editor, charts. Those take weeks to build yourself and here arrive ready and tested, though several of them live in packages separate from the base set.
Utility hooks are the second part, often skipped in descriptions. Click outside handling, window size, local storage, debounced values, keyboard shortcuts. The hooks package works independently of the components, so it can be used without the rest of the library.
pnpm add @mantine/core @mantine/hooksThe third part is a form system with validation and support for nested fields and lists. It is a separate package integrated with the components, so a field carrying a validation error looks right with no extra work.
Theming and styles
Theme configuration covers colours, spacing, corner radii, and typography, and the components draw from it.
import { MantineProvider, createTheme } from '@mantine/core'
import '@mantine/core/styles.css'
const theme = createTheme({
primaryColor: 'indigo',
defaultRadius: 'md',
fontFamily: 'Inter, sans-serif',
})
export function App({ children }) {
return <MantineProvider theme={theme}>{children}</MantineProvider>
}The colour palette works differently here from most libraries. You define a colour as ten shades, and components pick the right one depending on context and on light or dark mode. That means dark mode works with no separate configuration.
CSS modules are recommended for styling your own components. Earlier versions rested on a JavaScript based styling solution no longer recommended, so a new project is better started on modules directly.
Theme values are exposed as CSS variables, so your own styles use the same values as the components. That solves the commonest problem when mixing a library with your own code, namely drift between configured colours and hardcoded ones.
Forms
This is where the library clearly beats sets focused on appearance.
import { useForm } from '@mantine/form'
const form = useForm({
mode: 'uncontrolled',
initialValues: { email: '', age: 18 },
validate: {
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid address'),
age: (value) => (value >= 18 ? null : 'Must be an adult'),
},
})
<form onSubmit={form.onSubmit(save)}>
<TextInput label="Email address" {...form.getInputProps('email')} />
<NumberInput label="Age" {...form.getInputProps('age')} />
</form>Uncontrolled mode, set in the configuration, matters practically on larger forms. Changing a value in one field does not rerender everything, which across thirty fields is the difference between smooth typing and noticeable lag.
Validation of nested fields and lists works without tricks, which on forms with repeating sections, order line items for instance, saves considerable code.
Note, though, that on very complex forms and where validation must match a schema used server side, combining this with a separate validation library is often more sensible, so the rules stay shared across both sides.
Working with Next.js
The library works in Next.js, with components requiring the client directive, since they use state and effects.
That means the theme provider and everything under it is client side. With the app router, a sensible arrangement wraps content in the provider inside a layout and leaves server components where interactivity is unnecessary.
The second matter is styles. The library's stylesheet must be imported in the root layout, and server rendering needs the fragment injecting theme variables before the first render. Without it a flash of wrong colours appears, familiar from every library with theming.
The third is size. A hundred twenty components in a package does not mean all reach the output, since imports are selective. Do check what the built bundle actually weighs, though, particularly on a public page where load time counts.
The components that save most
It helps to know which parts of this library repay themselves fastest, since they justify the choice.
The date picker comes first. Ranges, excluded days, minimum and maximum bounds, time zone handling, and localisation form an area where a custom implementation usually ends as a version working for one use case only.
The searchable select comes second. Multiple choice, creating new entries, loading data on demand, and keyboard handling form a requirement list hard to satisfy in under a week.
The rich text editor and the charts come third, both being wrappers over somebody else's libraries and both living in separate packages. You get an appearance matching the theme and consistent behaviour instead of assembling that yourself.
Notifications and modals rank lower, being simpler, and their value lies in consistency. Fifteen places calling a notification through the same call look and behave identically; fifteen custom implementations do not.
The data table, by contrast, is the thing the set does not include, and it is the commonest misunderstanding when choosing this library. The Table component covers appearance, variants, and a scroll container, while sorting, pagination, row selection, and expandable details are yours to write or to take from a community add on such as mantine-datatable. That is a separate project outside the Mantine team, so its release schedule runs independently of the rest.
The rest of the set covers basic components that look similar in every library. Base a choice between libraries on those few items rather than on button variant count.
Bundle size and performance
A complete library carries a price worth knowing before it reaches a public page.
Imports are selective, so only used components reach the output. That works well for basic components and less well for composite ones, since a date picker calendar or a rich text editor pulls considerable code along whatever share of its features you use.
The practical rule: check the built bundle size after adding each composite component. A bundle analysis tool shows what actually occupies space and is often a source of surprise.
On an admin panel behind a login that is rarely a problem, since a user loads the application once and works in it for hours. On a public page the load time difference translates directly into how many people stay.
A sensible split separates the two cases. A public page built with lighter means, a panel on the complete library. What they share is the theme and palette rather than the component set.
Mantine against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Mantine | Composite components, forms, hooks included | Larger bundle, its own styling approach, no data table | Admin panel, form heavy application |
| shadcn/ui | Code lives with you, full control, accessibility | More work on composite components | Product with its own design system |
| daisyUI | Light weight, themes, works in any framework | No logic or composite components | Fast project, any framework |
| HeroUI | Components with animation and behaviour | Smaller set of composite components | Application where appearance counts |
Choosing between the first two rows is the commonest decision and comes down to one question: do you need a date picker, a field with suggestions, and a ready form system, or rather buttons and forms with a look of your own.
For an admin panel with many composite components and an appearance that should be correct rather than exceptional, the first row saves weeks. For a product where appearance is the differentiator, the second gives control a ready library will not without a fight against its styles.
The closest neighbour in scope is Chakra UI, also installed as a dependency and also shipping composite components with accessibility included. The difference lies in styling: here you write CSS modules, there you pass styles as component props. One practical caveat belongs with that comparison: Chakra version 3 is a rewrite from scratch, so a good share of the examples circulating online describe an API that no longer exists, which is easy to forget when estimating how long getting started will take.
A mixed arrangement is worth considering too, and rarely is. An internal panel on a complete library and a public section built from your own components are two projects with different requirements, so one toolset for both is an assumption rather than a necessity. What stays shared is the palette and the typography, which is enough for the product to look coherent.
Adjusting the look
A ready library gives a consistent look that is recognisable. That is an advantage on an internal tool and a problem on a product meant to stand out.
The first level of adjustment is theme configuration: palette, radii, typography, spacing. Changing those four stops an application looking like a documentation example and costs half an hour.
The second level is component default props. Variants and sizes set once in the theme apply everywhere, so a button looks the way you decided without repeating properties in every place.
const theme = createTheme({
components: {
Button: { defaultProps: { size: 'md', radius: 'sm' } },
TextInput: { defaultProps: { size: 'md' } },
},
})The third level is custom styles through CSS modules targeting a component's inner elements. It works and needs attention on upgrades, since inner structure is not part of the public contract.
The fourth is your own components wrapping the library's. That approach is the most change resistant: five of your own components used across the application mean a look change, or even a library change, touches five files rather than two hundred.
That is the sensible order. Start with the theme, add default props, and reach for custom styles once the first two levels fall short.
Common mistakes
The first is fighting the library's styles rather than configuring the theme. Overriding component classes with your own rules works until a version or colour mode changes.
The second is controlled mode forms with many fields. Every change rerenders everything, and the difference registers sooner than expected.
The third is missing the theme handling fragment during server rendering. The result is a flash of wrong colours on first paint, visible on every page entry.
The fourth is importing everything from one place without checking the output size. On a public page that is sometimes the difference between a good and a poor load time measurement.
The fifth is mixing two component libraries in one project. Two styling systems lead to conflicts nobody wants to untangle later.
The sixth is assuming a ready component is fully accessible. The library performs well here, and your own extensions and unusual uses still need checking with a keyboard and a screen reader.
Updates and maintenance
A library installed as a dependency has a different maintenance profile from code copied into a project, and that deserves thought before choosing.
The advantage is that bug fixes and accessibility improvements arrive with a package update. You need neither track nor port them by hand, which matters on composite components, since that is where keyboard handling problems most often appear.
The drawback is dependence on somebody else's schedule. A breaking change requires migration on your time rather than when it suits you. On a major version changing the styling approach that is sometimes several days of work.
Two practical safeguards help. The first is pinning the version and raising it deliberately rather than accepting automatic updates on every install. The second is your own wrapping components, so a change in the library's interface touches a few files rather than the whole project.
Read the changelog before raising a version too. A few minutes of reading saves hours hunting a cause when something breaks after an upgrade done in passing.
FAQ
How does Mantine differ from shadcn/ui?
It is a library installed as a dependency rather than code copied into a project. You get composite components shadcn does not include, at the cost of less control over the code and a larger bundle. Updates arrive with the package rather than requiring recopying.
Does it suit public pages?
Yes, though its strength lies in form heavy applications with composite components. On a landing page, where load time and a distinctive look matter, lighter solutions built on Tailwind CSS are often the better choice.
Must I use this library's form system?
No, the components work with any form library. The built in system integrates best and handles nested fields, while for validation shared with a back end, basing rules on a schema used on both sides is often more sensible.
How does dark mode work?
With no separate configuration. Colours are defined as a set of shades and components pick the right one for the mode, so switching applies across the application. For your own styles, use theme variables rather than hardcoded values.
Does Mantine ship a ready data table?
Not in the base set. The Table component covers appearance, while sorting, pagination, and row selection are yours to write or to take from a community add on such as mantine-datatable. Account for that in the project budget, since on an admin panel it is usually the single most expensive element.
Can the hooks be used alone?
Yes, the hooks package is independent and needs no other part of the library. That is a sensible choice in a project with its own components that wants ready answers for click outside handling, window size, or debounced values.
Documentation sits on the project site, and the source code in the GitHub repository.