DEV Community

DevForge Templates
DevForge Templates

Posted on

Why I Chose Mantine Over shadcn/ui for Every Dashboard Project

I've built around a dozen admin panels and dashboards in the last year. For the first few, I used shadcn/ui. It's excellent software. But I kept hitting the same friction: every dashboard needed tables with sorting and pagination, form validation, toast notifications, date pickers, and dark mode. Each time, I was installing 5-8 additional packages on top of shadcn's primitives and wiring them together myself.

Then I tried Mantine. I haven't gone back for dashboard work since.

This isn't a "shadcn bad" post. shadcn is the right choice for a lot of projects. But for dashboards specifically, Mantine saves me days of integration work per project. Here's why.

Where shadcn Wins

Let me be clear about what shadcn does well, because it does a lot well.

Full control over markup and styling. Every component is copied into your project as source code. You own it completely. No fighting a library's abstractions when you need something custom.

Tailwind-native. If your team already thinks in Tailwind classes, shadcn components feel like a natural extension of your CSS workflow. No context switching between utility classes and a component library's prop-based styling.

Perfect for custom design systems. When a designer hands you a Figma file with bespoke components that don't map to any library's defaults, shadcn gives you styled primitives to build from. You're assembling Lego bricks, not modifying someone else's furniture.

Small bundle footprint. You only ship what you use, and each component is minimal by design.

For marketing sites, landing pages, and products with strong custom branding -- shadcn is hard to beat.

Where Mantine Wins for Dashboards

Dashboards have different constraints. You're not designing a unique brand experience. You're building functional interfaces where users manage data, review analytics, and configure settings. Speed matters more than pixel-level control.

Here's where Mantine's "batteries included" approach pays off.

DataTable with Server-Side Everything

The single biggest time sink in dashboard development is tables. Not simple tables -- tables with server-side sorting, pagination, column resizing, row selection, and CSV export. With shadcn, you'd reach for TanStack Table and wire up the integration yourself.

Mantine's DataTable (via mantine-datatable) gives you all of this out of the box:

import { DataTable } from "mantine-datatable";

function UsersTable() {
  const [page, setPage] = useState(1);
  const [sortStatus, setSortStatus] = useState<DataTableSortStatus<User>>({
    columnAccessor: "createdAt",
    direction: "desc",
  });

  const { data, isLoading } = useQuery({
    queryKey: ["users", page, sortStatus],
    queryFn: () =>
      api.get("/users", {
        page,
        sortBy: sortStatus.columnAccessor,
        order: sortStatus.direction,
      }),
  });

  return (
    <DataTable
      records={data?.users}
      totalRecords={data?.total}
      page={page}
      onPageChange={setPage}
      recordsPerPage={20}
      sortStatus={sortStatus}
      onSortStatusChange={setSortStatus}
      columns={[
        { accessor: "name", sortable: true },
        { accessor: "email", sortable: true },
        { accessor: "role", sortable: true },
        {
          accessor: "createdAt",
          sortable: true,
          render: (user) => dayjs(user.createdAt).format("MMM D, YYYY"),
        },
      ]}
      selectedRecords={selectedRecords}
      onSelectedRecordsChange={setSelectedRecords}
      fetching={isLoading}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Sorting, pagination, row selection, loading state -- all handled through props. The equivalent setup with shadcn + TanStack Table is around 150-200 lines of boilerplate before you get the same functionality.

Forms with Zod Validation

Mantine's useForm hook integrates directly with Zod through mantine-form-zod-resolver. It handles nested objects, arrays, and field-level error display without extra wiring:

import { useForm, zodResolver } from "@mantine/form";
import { z } from "zod";

const schema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().email("Invalid email"),
  address: z.object({
    street: z.string().min(1, "Required"),
    city: z.string().min(1, "Required"),
  }),
  tags: z.array(z.string()).min(1, "At least one tag required"),
});

function UserForm() {
  const form = useForm({
    mode: "uncontrolled",
    validate: zodResolver(schema),
    initialValues: {
      name: "",
      email: "",
      address: { street: "", city: "" },
      tags: [],
    },
  });

  return (
    <form onSubmit={form.onSubmit(handleSubmit)}>
      <TextInput label="Name" {...form.getInputProps("name")} />
      <TextInput label="Email" {...form.getInputProps("email")} />
      <TextInput label="Street" {...form.getInputProps("address.street")} />
      <TextInput label="City" {...form.getInputProps("address.city")} />
      <TagsInput label="Tags" {...form.getInputProps("tags")} />
      <Button type="submit">Save</Button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

getInputProps returns the value, onChange handler, and error message for each field. Nested paths like address.street just work. With shadcn, you'd use React Hook Form + Zod + your own error display wiring -- which is fine, but it's more code and more packages to maintain.

Everything Else You'd Need Separate Packages For

Here's what Mantine includes that you'd need to install individually with shadcn:

  • Notifications: Built-in toast system with notifications.show(). No react-hot-toast or sonner needed.
  • Date pickers: DatePicker, DateTimePicker, DateRangePicker -- all with timezone support. No date-fns adapter or separate date library integration.
  • Rich text editor: Tiptap-based RichTextEditor component, preconfigured with a toolbar.
  • Dropzone: File upload area with preview, MIME type filtering, and size limits.
  • Spotlight: Command palette (Cmd+K) with search. Similar to cmdk but integrated with Mantine's styling.
  • Dark mode: MantineProvider with defaultColorScheme="auto" and a toggle component. Zero CSS work.

That's 5-8 fewer dependencies in your package.json, 5-8 fewer libraries to keep compatible with each other on major version bumps.

60+ Hooks That Actually Get Used

Mantine's hook library (@mantine/hooks) is underrated. These aren't novelty hooks -- they're the ones I actually use in every dashboard:

  • useDebouncedValue -- for search inputs that query an API
  • useLocalStorage -- typed localStorage with SSR safety
  • useIntersection -- infinite scroll triggers
  • useClipboard -- copy-to-clipboard with success state
  • useDisclosure -- modal/drawer open/close with toggle
  • useMediaQuery -- responsive logic without CSS

Each one is small, typed, and tested. You could write them yourself, but you'd be rewriting them for every project.

Bundle Size: The Tradeoff

Let's be honest about the downside. Mantine is heavier than shadcn.

A typical dashboard with shadcn + the required extras (TanStack Table, React Hook Form, sonner, cmdk, date-fns, react-dropzone) comes to roughly 80-120KB gzipped of library code, depending on what you use.

Mantine with the same feature set is roughly 130-160KB gzipped. It's heavier.

But here's the thing: dashboard users are typically on company networks with fast connections. They're not on 3G in rural areas. The 40-50KB difference is imperceptible in practice. And you're shipping one dependency tree instead of eight, which means fewer version conflicts and simpler upgrades.

For a marketing site where every kilobyte matters for Core Web Vitals and SEO? That tradeoff doesn't make sense. For an internal admin panel? I'll take the simpler dependency tree every time.

When to Choose What

Choose shadcn when:

  • You're building a marketing site or consumer-facing product with custom branding
  • Your team is Tailwind-first and wants to stay in that ecosystem
  • You need pixel-perfect control over every component
  • Bundle size is a primary concern (public-facing, SEO-sensitive pages)

Choose Mantine when:

  • You're building dashboards, admin panels, or internal tools
  • You need data tables with sorting, pagination, and selection
  • Shipping speed matters more than custom styling
  • You want one library instead of managing 8 separate packages
  • Dark mode is a requirement (it usually is for dashboards)

The Decision Framework

I think about it this way: shadcn gives you ingredients. Mantine gives you a kitchen with the meals half-prepared. If you're a chef building a unique restaurant menu, you want the ingredients. If you're feeding a team of 50 and need lunch ready by noon, you want the pre-prep.

Most dashboard work is the second scenario. You're not crafting a unique visual identity -- you're building functional tools where consistency and speed matter more than creative control.

I still use shadcn for projects where it fits better. But for the kind of work that pays the bills -- admin panels, data management tools, internal dashboards -- Mantine lets me ship in days instead of weeks. That's why it's my default.

Top comments (0)