Why I rewrote my starter template to use mantine instead of shadcn
When shadcn was released, it generated a lot of buzz, and many people quickly adopted it. It spread like wildfire and has become almost unstoppable. However, it seems like many use it simply because it’s the most popular choice—at least that’s my impression.
I appreciate shadcn’s philosophy of owning your components. It allows for easy customization without being entirely dependent on the library authors, but this can also be a double-edged sword. It feels like many have forgotten the original purpose of component libraries—to help us write less code.
One of my biggest issues with shadcn is that its components are overly verbose and not as mature as some of the older, established options. More than once, I’ve found myself having to build my own autocomplete component using shadcn or Radix primitives, and I can’t say it’s ever been an enjoyable experience.
That’s why I prefer Mantine over shadcn, primarily for two reasons:
- Mantine offers a more extensive set of polished components.
- Mantine components are simpler and less verbose.
I recently finished rewriting my template to use Mantine instead of shadcn, and I’d like to share some examples to back up my points. If you’re interested, you can check out the entire commit:
Form with shadcn:
const form = useForm<Schema>({
resolver: zodResolver(schema),
reValidateMode: "onSubmit",
defaultValues: {
firstName: user!.firstName,
lastName: user!.lastName
}
});
return (
<div className="max-w-screen-sm">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-y-2"
>
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First name</FormLabel>
<FormControl>
<Input {...field}></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
></FormField>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last name</FormLabel>
<FormControl>
<Input {...field}></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
></FormField>
<Button type="submit">Update profile</Button>
</form>
</Form>
<ErrorFeedback data={errors} />
</div>
);
Same form with mantine:
const form = useForm({
initialValues: {
firstName: "",
lastName: "",
},
validate: zodResolver(schema),
});
return (
<div className="max-w-screen-sm">
<form
onSubmit={form.onSubmit(onSubmit)}
className="flex flex-col gap-y-2"
>
<TextInput {...form.getInputProps('firstName')} label="First name" />
<TextInput {...form.getInputProps('lastName')} label="Last name" />
<Button type="submit">Update profile</Button>
</form>
<ErrorFeedback data={errors} />
</div>
);
With all due respect to shadcn, why on earth would I choose to write more code when I can achieve the same results with far less effort?
Mantine, on the other hand, gives you practically everything you could need. Want a dropdown? Done. Need it to support searching? Just add a prop. Want it to handle multiselect? Another prop, and you’re good to go. On top of that, you get a whole set of incredibly useful hooks.
Plus, there’s a Tailwind plugin for Mantine, so you can seamlessly integrate the two for smooth interoperability.
Here's the plugin which I use and it works perfectly:
This allows you to use mantine variables as tailwind classes, for example:
primary is coming from mantine's color scheme.
Anyways, that's just my mini rant about shadcn and why I decided to just stop following the hype and use what I'll be actually productive with.
I hope you can find some wisdom in this.
Member discussion