shadcn/ui vs. Mantine: Copy-In Primitives vs. a Batteries-Included Library
120+ components vs. 92 hand-rolled ones, and the two real ARIA defects this codebase shipped with that a component library would have caught on day one.
Mantine is a batteries-included React component library — 120+ components, its own CSS-Modules-based styling engine, no Tailwind — installed as a dependency and themed through a provider. shadcn/ui is the opposite shape: 50-ish components copied into your repo via a CLI, styled with Tailwind utilities you already own. The real trade isn't "which is better," it's install-and-configure a finished system versus hand-own a smaller one and build the gaps yourself.
This storefront chose the second path all the way down: 92 components, zero component-library dependency, and a measured list of exactly what "build the gaps yourself" cost.
The two things being compared
| shadcn/ui | Mantine | |
|---|---|---|
| Distribution | CLI copies .tsx source into your repo | An npm package (@mantine/core + friends) |
| Component count | ~50 primitives, source you edit | 120+ components, hooks, and a full theming system out of the box |
| Styling | Tailwind utility classes | Its own CSS Modules engine — no Tailwind |
| Ownership | You own every copied file from day one | You own a config object; the component internals are the library's |
| Upgrades | No package to bump — re-copy to pick up fixes | npm update, same as any dependency |
| Dark mode | Whatever your Tailwind @theme defines | Built-in MantineProvider colour-scheme switch |
| Best fit | A design system that doesn't look like the defaults, or a Tailwind codebase already | A data-heavy SaaS app (dashboard, forms, tables) wanting full coverage fast |
Mantine's own selling point against shadcn is coverage: a project wiring up a dashboard with dates, tables, notifications and forms can reach for one package instead of assembling primitives. shadcn's counter-selling point is the same one Tailwind's is: nothing you didn't ask for ships, because there's no library — there's only the components you copied.
What zero-dependency actually costs, measured
Every component here is hand-written, not copied from either library. The full census:
| Layer | Components | Client Components |
|---|---|---|
| Atoms | 7 | 0 |
| Molecules | 41 | 19 |
| Organisms | 33 | 5 |
| Templates | 11 | 0 |
| Total | 92 | 24 |
Against Mantine's 120+, this is fewer components — but the count is misleading on its own, because Mantine's 120+ includes primitives this site never needed (date pickers, rich-text editors, notification systems) and excludes the marketing-specific composites (a pricing tier card, a template gallery carousel, a related-products rail) it also never shipped. The comparison that matters isn't the count, it's what each approach makes you build by hand versus hand you finished.
What this repo built by hand, and had to get right without a library's help: a focus trap, a modal, a dropdown, tabs, an accordion, a tooltip, and an accessible mega menu. Mantine ships accessible versions of all seven already wired up.
The two real defects that cost of "build it yourself"
Two of those seven shipped broken before an audit caught them — both are the honest price of not having a library.
The pricing FAQ accordion had two of five WAI-ARIA accordion requirements. FaqItem (src/components/molecules/FaqItem.tsx) is the actual component — real code, not written for this article:
// src/components/molecules/FaqItem.tsx
const [open, setOpen] = useState(false);
const id = useId();
const buttonId = `${id}-button`;
const panelId = `${id}-panel`;
// ...
<button
type="button"
id={buttonId}
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
aria-controls={panelId}
>
{question}
</button>
<div
id={panelId}
role={open ? "region" : undefined}
aria-labelledby={open ? buttonId : undefined}
>
That's the fixed version. Before the audit behind React accordion, it was missing the aria-controls/aria-labelledby pair entirely — a screen reader had no way to announce which panel belonged to which question. A Mantine <Accordion> ships that wiring already correct; this codebase had to find the gap, then close it.
The dashboard's three-way tab switch had three of six. DashboardTabs (src/components/organisms/DashboardTabs.tsx) was missing the roving-tabindex behaviour the WAI-ARIA tabs pattern requires — the tablist wasn't one keyboard stop, so arrowing between tabs didn't work the way a screen-reader user would expect. It rendered correctly, was keyboard-reachable in the loosest sense, and no automated tool in this repo's own pipeline had ever flagged it — full audit in React tabs. Fixed, but the two together are the real cost line: a component library like Mantine buys you out of finding these at all.
Where each one actually wins
| If you… | Choose |
|---|---|
| Need 100+ components fast for a data-heavy SaaS app | Mantine — the coverage is the whole point |
| Have a design system that doesn't look like either library's defaults | shadcn/ui or hand-rolled — you're rewriting the visuals either way |
| Are already committed to Tailwind | shadcn/ui — Mantine brings a second, competing styling system into the same app |
| Want zero JS shipped for anything you don't explicitly render | shadcn/ui — components are tree-shaken like your own code; Mantine ships as a package |
| Have one developer and a deadline, and the components are mostly forms/tables | Mantine |
| Are shipping a template other people will customize | shadcn/ui or hand-rolled — a buyer inherits every dependency you chose for them |
That last row is this repo's actual reason: every one of its 111 products is source another developer takes over, so a component-library dependency becomes their dependency too. gumroad-alternative covers the same "own it vs. depend on it" tradeoff on the commerce side of this same storefront.
The maintenance side nobody counts upfront
Mantine is a versioned dependency, which means it goes through the same lifecycle every dependency does: security patches, breaking changes at major versions, and a package.json line someone has to review before bumping. This repo's entire runtime dependencies block is 13 entries — no component library, no CSS-in-JS engine, no headless-primitives package — specifically so that a buyer who takes over one of its 111 templates inherits the smallest possible dependency surface. A Mantine-based template hands a buyer 120+ components' worth of transitive dependencies to keep current; a shadcn-based or hand-rolled one hands them source files with no upstream to track at all.
That distinction matters more here than in a typical app, because every product this storefront sells is exactly that: code someone else takes over, forks, and maintains going forward. The dependency choice made once, at build time, becomes every future buyer's maintenance burden — which is also why gumroad-alternative frames "own it vs. depend on it" as a commerce-architecture decision, not just a styling one.
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Mantine's MantineProvider styles don't apply | Its CSS wasn't imported (@mantine/core/styles.css) or the provider doesn't wrap the tree | Import the stylesheet once at the root and confirm <MantineProvider> is an ancestor |
| Tailwind and Mantine styles fight each other | Both are global CSS systems targeting the same elements | Scope one to specific routes, or don't run both — pick one styling system per app |
| A hand-rolled accordion/tabs component "looks done" but fails a screen-reader test | Visual completeness and ARIA completeness are unrelated; nothing catches the second without a manual audit | Run an audit against the WAI-ARIA pattern for that specific widget, as this repo did for both examples above |
| shadcn component's Tailwind classes don't compile | The file lives outside Tailwind's configured content paths | Add the copied file's path to the scanned sources |
| Bundle grew after installing a component library | Mantine ships all its CSS and JS as a dependency regardless of how much you render | Audit with a bundle analyzer; import only the sub-packages you use |
| Dark mode looks different between two components | One uses Mantine's colour-scheme system, the other uses Tailwind dark: classes | Don't mix; pick one dark-mode mechanism app-wide |
Frequently asked questions
Does shadcn/ui have as many components as Mantine? No — shadcn ships around 50 primitives; Mantine ships 120+ including hooks, forms, dates, and notifications. shadcn assumes you'll write the composites yourself with Tailwind.
Can I use Mantine and Tailwind together? Technically yes, but they're two separate styling systems competing for the same elements. Most codebases pick one rather than maintain both.
Which one is more accessible out of the box? Mantine, because its components ship with the ARIA wiring and keyboard handling already implemented and tested. shadcn's components (via Radix) are also accessible, but anything you hand-roll instead of copying carries no such guarantee — this post's two defects are the proof.
Is shadcn/ui a dependency the way Mantine is? No. The CLI copies source files into your project once; there's no package to update afterward. Mantine stays a versioned dependency for the life of the project.
Templates in this post
ASoc Catalyst is an AI-automation agency marketing site with an automation-metrics hero, a four-service grid, and a three-tier monthly/yearly pricing table. ASoc Chain is a DeFi-protocol landing template built around a trustless-by-design pitch and an on-chain 3D hero visual. ASoc Cognition markets an AI/neuro consulting studio with a revenue-led hero, a services block, and an AI-capabilities section.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
