React UI Component Libraries Compared: shadcn/ui, Radix, Headless UI, daisyUI, Flowbite and MUI | How-to Guide

shadcn/ui, Radix, Headless UI, daisyUI, Flowbite and MUI differ mainly in styling: Radix and Headless UI are unstyled, daisyUI and Flowbite are pre-styled Tailwind, MUI is Material Design, and shadcn/ui copies styled code into your project. Here is the same six components in each, plus which to choose.

The short version

These six libraries solve different problems, which is why "which is best" has no single answer. Two of them (Radix Primitives and Headless UI) give you behavior and accessibility with no styling. Two (daisyUI and Flowbite) give you a finished Tailwind look. MUI gives you a complete design system with its own styling engine. shadcn/ui is not a package at all: it copies styled component source into your project, built on a headless layer underneath.

All facts below (versions, licences, requirements) were checked against each project's official documentation, GitHub repository and npm listing in October 2026. Versions move quickly, so treat the numbers as "at the time of writing".

Comparison table

shadcn/uiRadix PrimitivesHeadless UIdaisyUIFlowbite ReactMUI (Material UI)
What you installa CLI that copies component source into your reponpm packages (radix-ui or one per component)@headlessui/reactdaisyui Tailwind pluginflowbite-react@mui/material plus Emotion
Styling modelTailwind classes in code you own; theme via CSS variablesunstyled; you add classes, state via data-state attributesunstyled; state via data-* attributes, designed for TailwindCSS component classes (btn, modal) and themespre-styled Tailwind components, themeableMaterial Design, CSS-in-JS (sx prop, theme object)
Version (Oct 2026)CLI shadcn 4.x (no runtime package)radix-ui 1.8; individual packages 1.x and 2.x2.25.x0.12 (pre-1.0)9.x
LicenceMITMITMITMITMITMIT (paid MUI X Pro/Premium are separate)
React neededyes16.8 to 1918 or 19no (plain HTML and CSS)18 or 1917, 18 or 19
Tailwind neededyes (v4 in current docs)nono, but intended for ityes, Tailwind 4 for daisyUI 5yes, Tailwind 3 or 4no
Accessibility approachinherits from its primitive layer (Base UI or Radix)follows WAI-ARIA Authoring Practices; keyboard and focus built indescribed as "fully accessible"; keyboard and focus built inrelies on native elements (<dialog>, <details>); you add ARIA and behaviorReact components with Floating UI positioning; test keyboard use yourselfARIA and keyboard handling per component, documented per component
Has a toastyes, via Sonneryes (Toast)noyes (layout class only)yes (Toast)yes (Snackbar)

We left bundle size out of the table on purpose. The numbers depend heavily on which components you import, whether your bundler tree-shakes them, and (for daisyUI) how much CSS Tailwind generates for the classes you use. A single "kB" figure per library would mislead more than it helps. Measure your own build: run your bundler's analyzer before and after adding the components you actually need.

Each library in detail

shadcn/ui

What it is. shadcn/ui describes itself as "not a component library" but "how you build your component library". You run its CLI (npx shadcn init, then npx shadcn add button) and it writes the component source into your project. From then on the code is yours to edit; there is no shadcn runtime package to upgrade.

Styling. Tailwind CSS classes inside the copied files, with colors and radii as CSS variables so you can re-theme in one place. Variants are usually defined with class-variance-authority.

Under the hood. The interactive parts sit on a headless library. According to the shadcn/ui changelog, new projects default to Base UI since July 2026, and Radix "is not being deprecated": both are supported. Its toast is built on Sonner, a separate open-source toast package.

Licence and version. MIT (GitHub repository shadcn-ui/ui). The CLI package shadcn was at 4.x on npm at the time of writing.

Watch out for. Because you own the code, you also own the upkeep. Fixes upstream do not reach your copies automatically, and different projects drift apart over time.

Radix Primitives

What it is. A set of unstyled, accessible React components (dialog, dropdown menu, tabs, switch, toast, tooltip, popover and more). You can install everything from the single radix-ui package (1.8 at the time of writing) or one package per component, such as @radix-ui/react-dialog.

Styling. None by default. Each part renders a plain element you can style with any method. State is exposed as data attributes, for example data-state="open" or data-highlighted, which pair well with Tailwind's data-[state=open]: variants.

Accessibility. Radix's documentation says the primitives follow the WAI-ARIA Authoring Practices, provide keyboard support and manage focus (for example, trapping focus inside an open dialog and returning it on close).

Licence and requirements. MIT. The npm peer dependency range covers React 16.8 through 19.

Watch out for. You write every style yourself. Radix does not ship a Button primitive: use a native <button>, or the Slot utility to merge props into your own element.

Headless UI

What it is. Unstyled components from the Tailwind CSS team. The React package, @headlessui/react (2.2 at the time of writing), includes Button, Checkbox, Combobox, Dialog, Disclosure, Fieldset, Input, Listbox, Menu (dropdown), Popover, Radio Group, Select, Switch, Tabs, Textarea and Transition.

Styling. Unstyled. Components expose state through data attributes (data-open, data-checked, data-selected, data-focus), so you style them with Tailwind variants like data-checked:bg-blue-600 (Tailwind 4 syntax) or data-[checked]:bg-blue-600 (Tailwind 3).

Accessibility. Its site describes the components as "completely unstyled, fully accessible". Keyboard navigation, focus trapping in dialogs and ARIA wiring are built in.

Licence and requirements. MIT. React 18 or 19 (npm peer dependency).

Watch out for. The set is smaller than Radix: there is no toast, tooltip or slider, so you will need another package for those.

daisyUI

What it is. A Tailwind CSS plugin that adds component class names such as btn, modal, dropdown, tabs, toggle and toast, plus a set of color themes. It ships CSS, not React components, so it works with any framework or none.

Styling. Semantic classes with modifiers (btn btn-primary btn-sm) that you can still combine with regular Tailwind utilities. Themes swap through CSS variables.

Requirements. daisyUI 5 (first released in February 2025, 5.x at the time of writing) targets Tailwind CSS 4; its upgrade guide has you update Tailwind first. You install it with a single @plugin "daisyui"; line in your CSS.

Accessibility. Because it is CSS only, behavior comes from native HTML: the modal uses the <dialog> element (Escape closes it and the browser handles focus), dropdowns use <details> or the popover API. Anything beyond that (ARIA for tabs, announcing toasts) is your job.

Licence. MIT.

Watch out for. In React you wire up state yourself (for example, calling showModal() through a ref). There is no keyboard arrow-key handling for menus or tabs unless you add it.

Flowbite React

What it is. React components (flowbite-react) styled with Tailwind, from the Flowbite project. It includes Button, Modal, Dropdown, Tabs, ToggleSwitch, Toast, Navbar, Sidebar, Datepicker and more.

Styling. Pre-styled with Tailwind classes, customized through a theme object and a theme prop on each component.

Requirements and version. Version 0.12 at the time of writing: it has not reached 1.0, so expect breaking changes between minor versions. The npm peer dependencies are React 18 or 19 and Tailwind CSS 3 or 4. It uses Floating UI for dropdown and tooltip positioning.

Accessibility. Components render the expected roles for common patterns, but we did not find a published accessibility statement comparable to Radix's. Test keyboard and screen reader behavior for the components you rely on.

Licence. MIT (the open-source library; Flowbite also sells separate Pro blocks and design files).

MUI (Material UI)

What it is. In its own words, "an open-source React component library that implements Google's Material Design." It is the largest set in this comparison, including data display, inputs, navigation, layout and feedback components.

Styling. CSS-in-JS. By default it uses Emotion (@emotion/react and @emotion/styled are peer dependencies of @mui/material 9). You theme it with createTheme and style one-offs with the sx prop. It can sit alongside Tailwind, but the two theming systems do not share tokens unless you connect them.

Version and licence. Version 9 (9.0.0 was published in April 2026). The core library is MIT. Advanced data grid, date and chart features in MUI X Pro and Premium are commercial licences.

Accessibility. Components ship with ARIA attributes and keyboard handling, and the component pages in MUI's docs describe accessibility behavior per component.

Watch out for. Moving away from the Material look takes real theming work, and CSS-in-JS needs extra setup with React Server Components.

The same six components in each library

Below, the same button, dialog, dropdown menu, tabs, switch and toast are written six times, once in each library's documented API. Every snippet is original code written for this page (not copied from the libraries' docs), uses each library's current major version, and is trimmed to the parts that differ. Live, clickable versions are shown for Radix Primitives only, because Radix is the library installed on this site. They are styled with our own Tailwind classes, since Radix ships no styles. We do not show live versions of the other five libraries, so nothing here imitates their look.

Button

Most libraries ship a button with variants. Notice how Radix and Headless UI leave the look to you, while daisyUI expresses variants as classes and the rest as props.

Button: shadcn/ui

// after: npx shadcn add button
import { Button } from "@/components/ui/button";

<Button>Save changes</Button>
<Button variant="outline" size="sm">Cancel</Button>
<Button variant="destructive" disabled>Delete</Button>
<Button asChild><a href="/pricing">See pricing</a></Button>

Button: Radix Primitives

// Radix has no Button primitive. Use a native button,
// or Slot to pass props onto your own element.
import { Slot } from "radix-ui";

function Button({ asChild, className, ...props }) {
  const Comp = asChild ? Slot.Root : "button";
  return <Comp className={"rounded-lg bg-black px-4 py-2 text-white " + className} {...props} />;
}

Button: Headless UI

import { Button } from "@headlessui/react";

<Button className="rounded-lg bg-black px-4 py-2 text-white
  data-hover:bg-black/80 data-active:bg-black/70
  data-disabled:opacity-50">
  Save changes
</Button>

Button: daisyUI

<button className="btn btn-primary">Save changes</button>
<button className="btn btn-outline btn-sm">Cancel</button>
<button className="btn btn-error" disabled>Delete</button>
<a className="btn btn-link" href="/pricing">See pricing</a>

Button: Flowbite React

import { Button } from "flowbite-react";

<Button>Save changes</Button>
<Button color="alternative" size="sm">Cancel</Button>
<Button color="red" disabled>Delete</Button>
<Button href="/pricing">See pricing</Button>

Button: MUI

import Button from "@mui/material/Button";

<Button variant="contained">Save changes</Button>
<Button variant="outlined" size="small">Cancel</Button>
<Button variant="contained" color="error" disabled>Delete</Button>
<Button href="/pricing">See pricing</Button>

Dialog (modal)

A dialog is where accessibility work hides: focus trap, Escape to close, returning focus to the trigger, labelling. The headless libraries and MUI do this in JavaScript; daisyUI leans on the native <dialog> element, which handles Escape and focus in the browser.

Dialog (modal): shadcn/ui

import {
  Dialog, DialogContent, DialogDescription, DialogFooter,
  DialogHeader, DialogTitle, DialogTrigger, DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

<Dialog>
  <DialogTrigger asChild><Button>Edit profile</Button></DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Edit profile</DialogTitle>
      <DialogDescription>Update your display name.</DialogDescription>
    </DialogHeader>
    {/* form fields */}
    <DialogFooter>
      <DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>
      <Button type="submit">Save</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Dialog (modal): Radix Primitives

import { Dialog } from "radix-ui";

<Dialog.Root>
  <Dialog.Trigger className="btn-dark">Edit profile</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Overlay className="fixed inset-0 bg-black/40" />
    <Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-5">
      <Dialog.Title>Edit profile</Dialog.Title>
      <Dialog.Description>Update your display name.</Dialog.Description>
      {/* form fields */}
      <Dialog.Close>Cancel</Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

Dialog (modal): Headless UI

import { Dialog, DialogPanel, DialogTitle, DialogBackdrop } from "@headlessui/react";
import { useState } from "react";

const [open, setOpen] = useState(false);

<button onClick={() => setOpen(true)}>Edit profile</button>
<Dialog open={open} onClose={setOpen} className="relative z-50">
  <DialogBackdrop className="fixed inset-0 bg-black/40" />
  <div className="fixed inset-0 flex items-center justify-center p-4">
    <DialogPanel className="max-w-md rounded-xl bg-white p-5">
      <DialogTitle className="font-semibold">Edit profile</DialogTitle>
      {/* form fields */}
      <button onClick={() => setOpen(false)}>Cancel</button>
    </DialogPanel>
  </div>
</Dialog>

Dialog (modal): daisyUI

import { useRef } from "react";

const ref = useRef<HTMLDialogElement>(null);

<button className="btn" onClick={() => ref.current?.showModal()}>Edit profile</button>
<dialog ref={ref} className="modal">
  <div className="modal-box">
    <h3 className="text-lg font-bold">Edit profile</h3>
    {/* form fields */}
    <div className="modal-action">
      <form method="dialog"><button className="btn">Cancel</button></form>
    </div>
  </div>
  <form method="dialog" className="modal-backdrop"><button>close</button></form>
</dialog>

Dialog (modal): Flowbite React

import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react";
import { useState } from "react";

const [open, setOpen] = useState(false);

<Button onClick={() => setOpen(true)}>Edit profile</Button>
<Modal show={open} onClose={() => setOpen(false)} size="md">
  <ModalHeader>Edit profile</ModalHeader>
  <ModalBody>{/* form fields */}</ModalBody>
  <ModalFooter>
    <Button color="alternative" onClick={() => setOpen(false)}>Cancel</Button>
    <Button onClick={() => setOpen(false)}>Save</Button>
  </ModalFooter>
</Modal>

Dialog (modal): MUI

import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import { useState } from "react";

const [open, setOpen] = useState(false);

<Button variant="contained" onClick={() => setOpen(true)}>Edit profile</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
  <DialogTitle>Edit profile</DialogTitle>
  <DialogContent>{/* form fields */}</DialogContent>
  <DialogActions>
    <Button onClick={() => setOpen(false)}>Cancel</Button>
    <Button variant="contained" onClick={() => setOpen(false)}>Save</Button>
  </DialogActions>
</Dialog>

Dropdown menu

Menus need arrow-key navigation, type-ahead and correct roles. Radix, Headless UI, shadcn/ui and MUI handle that; daisyUI's <details> version opens and closes without JavaScript but does not add arrow-key navigation.

Dropdown menu: shadcn/ui

import {
  DropdownMenu, DropdownMenuContent, DropdownMenuItem,
  DropdownMenuSeparator, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

<DropdownMenu>
  <DropdownMenuTrigger asChild><Button variant="outline">Options</Button></DropdownMenuTrigger>
  <DropdownMenuContent align="start">
    <DropdownMenuItem onSelect={rename}>Rename</DropdownMenuItem>
    <DropdownMenuItem onSelect={duplicate}>Duplicate</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Dropdown menu: Radix Primitives

import { DropdownMenu } from "radix-ui";

<DropdownMenu.Root>
  <DropdownMenu.Trigger>Options</DropdownMenu.Trigger>
  <DropdownMenu.Portal>
    <DropdownMenu.Content sideOffset={6} className="rounded-lg bg-white p-1 shadow-lg">
      <DropdownMenu.Item onSelect={rename} className="data-[highlighted]:bg-black/5">Rename</DropdownMenu.Item>
      <DropdownMenu.Item onSelect={duplicate}>Duplicate</DropdownMenu.Item>
      <DropdownMenu.Separator className="my-1 h-px bg-black/10" />
      <DropdownMenu.Item disabled>Move</DropdownMenu.Item>
    </DropdownMenu.Content>
  </DropdownMenu.Portal>
</DropdownMenu.Root>

Dropdown menu: Headless UI

import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";

<Menu>
  <MenuButton>Options</MenuButton>
  <MenuItems anchor="bottom start" className="rounded-lg bg-white p-1 shadow-lg">
    <MenuItem>
      <button onClick={rename} className="block w-full data-focus:bg-black/5">Rename</button>
    </MenuItem>
    <MenuItem>
      <button onClick={duplicate} className="block w-full data-focus:bg-black/5">Duplicate</button>
    </MenuItem>
  </MenuItems>
</Menu>

Dropdown menu: daisyUI

<details className="dropdown">
  <summary className="btn">Options</summary>
  <ul className="menu dropdown-content z-1 w-52 rounded-box bg-base-100 p-2 shadow-sm">
    <li><button onClick={rename}>Rename</button></li>
    <li><button onClick={duplicate}>Duplicate</button></li>
  </ul>
</details>

Dropdown menu: Flowbite React

import { Dropdown, DropdownDivider, DropdownItem } from "flowbite-react";

<Dropdown label="Options" dismissOnClick>
  <DropdownItem onClick={rename}>Rename</DropdownItem>
  <DropdownItem onClick={duplicate}>Duplicate</DropdownItem>
  <DropdownDivider />
  <DropdownItem>Delete</DropdownItem>
</Dropdown>

Dropdown menu: MUI

import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useState } from "react";

const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const close = () => setAnchorEl(null);

<Button onClick={(e) => setAnchorEl(e.currentTarget)}>Options</Button>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={close}>
  <MenuItem onClick={() => { rename(); close(); }}>Rename</MenuItem>
  <MenuItem onClick={() => { duplicate(); close(); }}>Duplicate</MenuItem>
</Menu>

Tabs

Correct tabs need role="tablist", role="tab", aria-selected and arrow-key movement between tabs. In daisyUI and MUI you wire panels and IDs yourself; the others connect them for you.

Tabs: shadcn/ui

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

<Tabs defaultValue="account">
  <TabsList>
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="billing">Billing</TabsTrigger>
  </TabsList>
  <TabsContent value="account">Account settings</TabsContent>
  <TabsContent value="billing">Billing settings</TabsContent>
</Tabs>

Tabs: Radix Primitives

import { Tabs } from "radix-ui";

<Tabs.Root defaultValue="account">
  <Tabs.List aria-label="Settings">
    <Tabs.Trigger value="account" className="data-[state=active]:font-semibold">Account</Tabs.Trigger>
    <Tabs.Trigger value="billing" className="data-[state=active]:font-semibold">Billing</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="account">Account settings</Tabs.Content>
  <Tabs.Content value="billing">Billing settings</Tabs.Content>
</Tabs.Root>

Tabs: Headless UI

import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";

<TabGroup>
  <TabList>
    <Tab className="data-selected:font-semibold">Account</Tab>
    <Tab className="data-selected:font-semibold">Billing</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>Account settings</TabPanel>
    <TabPanel>Billing settings</TabPanel>
  </TabPanels>
</TabGroup>

Tabs: daisyUI

const [tab, setTab] = useState("account");

<div role="tablist" className="tabs tabs-box">
  <button role="tab" aria-selected={tab === "account"}
    className={"tab " + (tab === "account" ? "tab-active" : "")}
    onClick={() => setTab("account")}>Account</button>
  <button role="tab" aria-selected={tab === "billing"}
    className={"tab " + (tab === "billing" ? "tab-active" : "")}
    onClick={() => setTab("billing")}>Billing</button>
</div>
<div role="tabpanel">{tab === "account" ? "Account settings" : "Billing settings"}</div>

Tabs: Flowbite React

import { TabItem, Tabs } from "flowbite-react";

<Tabs aria-label="Settings" variant="underline" onActiveTabChange={(i) => console.log(i)}>
  <TabItem active title="Account">Account settings</TabItem>
  <TabItem title="Billing">Billing settings</TabItem>
</Tabs>

Tabs: MUI

import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import { useState } from "react";

const [tab, setTab] = useState(0);

<Tabs value={tab} onChange={(_, next) => setTab(next)} aria-label="Settings">
  <Tab label="Account" id="tab-0" aria-controls="panel-0" />
  <Tab label="Billing" id="tab-1" aria-controls="panel-1" />
</Tabs>
<div role="tabpanel" id={`panel-${tab}`} aria-labelledby={`tab-${tab}`}>
  {tab === 0 ? "Account settings" : "Billing settings"}
</div>

Switch (toggle)

A switch is an on/off control announced as role="switch". daisyUI styles a real checkbox, which keeps native form behavior; the others render a button with the switch role.

Switch (toggle): shadcn/ui

import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";

<div className="flex items-center gap-2">
  <Switch id="email-alerts" checked={on} onCheckedChange={setOn} />
  <Label htmlFor="email-alerts">Email notifications</Label>
</div>

Switch (toggle): Radix Primitives

import { Switch } from "radix-ui";

<Switch.Root id="email-alerts" checked={on} onCheckedChange={setOn}
  className="h-6 w-11 rounded-full bg-black/20 data-[state=checked]:bg-blue-500">
  <Switch.Thumb className="block h-5 w-5 rounded-full bg-white transition-transform data-[state=checked]:translate-x-5" />
</Switch.Root>
<label htmlFor="email-alerts">Email notifications</label>

Switch (toggle): Headless UI

import { Field, Label, Switch } from "@headlessui/react";

<Field className="flex items-center gap-2">
  <Switch checked={on} onChange={setOn}
    className="group h-6 w-11 rounded-full bg-black/20 data-checked:bg-blue-500">
    <span className="block h-5 w-5 rounded-full bg-white transition group-data-checked:translate-x-5" />
  </Switch>
  <Label>Email notifications</Label>
</Field>

Switch (toggle): daisyUI

<label className="flex items-center gap-2">
  <input type="checkbox" className="toggle toggle-primary"
    checked={on} onChange={(e) => setOn(e.target.checked)} />
  Email notifications
</label>

Switch (toggle): Flowbite React

import { ToggleSwitch } from "flowbite-react";

<ToggleSwitch checked={on} onChange={setOn} label="Email notifications" />

Switch (toggle): MUI

import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";

<FormControlLabel
  control={<Switch checked={on} onChange={(e) => setOn(e.target.checked)} />}
  label="Email notifications"
/>

Toast (notification)

Toasts are where the libraries differ most. Some give you a component you control with state, some give you a function you call from anywhere, and Headless UI leaves it out entirely.

Toast (notification): shadcn/ui

// after: npx shadcn add sonner
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";

// once, near the root of your app
<Toaster />

// anywhere
<Button onClick={() => toast("Changes saved", {
  description: "Your profile is up to date.",
  action: { label: "Undo", onClick: undo },
})}>
  Save
</Button>

Toast (notification): Radix Primitives

import { Toast } from "radix-ui";

<Toast.Provider duration={4000}>
  <button onClick={() => setOpen(true)}>Save</button>
  <Toast.Root open={open} onOpenChange={setOpen} className="rounded-lg bg-white p-3 shadow-lg">
    <Toast.Title>Changes saved</Toast.Title>
    <Toast.Description>Your profile is up to date.</Toast.Description>
    <Toast.Action altText="Undo the save" onClick={undo}>Undo</Toast.Action>
  </Toast.Root>
  <Toast.Viewport className="fixed bottom-4 right-4 w-80" />
</Toast.Provider>

Toast (notification): Headless UI

// Headless UI has no toast component. Common choices:
// pair it with a small toast package, or build one from
// Transition plus an aria-live region:
import { Transition } from "@headlessui/react";

<div aria-live="polite" className="fixed bottom-4 right-4">
  <Transition show={open}>
    <div className="rounded-lg bg-white p-3 shadow-lg transition data-closed:opacity-0">
      Changes saved
    </div>
  </Transition>
</div>

Toast (notification): daisyUI

{/* daisyUI positions and stacks; showing and hiding is your state */}
{open && (
  <div className="toast toast-end toast-bottom" role="status">
    <div className="alert alert-success">
      <span>Changes saved</span>
    </div>
  </div>
)}

Toast (notification): Flowbite React

import { Toast, ToastToggle } from "flowbite-react";

{open && (
  <Toast className="fixed bottom-4 right-4">
    <div className="text-sm font-normal">Changes saved</div>
    <ToastToggle onDismiss={() => setOpen(false)} />
  </Toast>
)}

Toast (notification): MUI

import Snackbar from "@mui/material/Snackbar";
import Button from "@mui/material/Button";

<Snackbar
  open={open}
  autoHideDuration={4000}
  onClose={() => setOpen(false)}
  message="Changes saved"
  action={<Button size="small" onClick={undo}>Undo</Button>}
/>

Which to choose by project type

ProjectGood fitWhy
Marketing site or landing page in TailwinddaisyUI, or plain TailwindMostly static pages need buttons, cards and a menu, not a JavaScript component layer. daisyUI's classes also work in non-React pages.
SaaS product with its own brandshadcn/ui, or Radix / Headless UI styled by youYou get accessible behavior without inheriting another product's look, and you can match your design system exactly.
Building your own design system or component libraryRadix Primitives (or Base UI), Headless UIUnstyled primitives are the layer you want to own the styles on top of. shadcn/ui is itself an example of this approach.
Internal tool, admin panel, dashboardMUI, or shadcn/uiMUI's breadth (tables, date pickers via MUI X, forms) saves the most time when the look matters less than shipping. shadcn/ui if you are already on Tailwind.
Prototype or hackathon on TailwindFlowbite React or daisyUIFinished-looking components with very little styling work. Plan to revisit if the prototype becomes a product.
Team that already uses Material Design (or Android-style UI)MUIIt implements Material Design directly, so designers and developers share one vocabulary.
Strict accessibility requirements (public sector, enterprise)Radix Primitives, Headless UI, MUI, or shadcn/ui on thoseKeyboard and focus behavior are built in and documented. Whatever you choose, still test with a keyboard and a screen reader; no library makes a page accessible by itself.

Questions to settle before you pick

For the hooks these components are built on, see the React hooks cheat sheet. For the utility classes used throughout the snippets, see the Tailwind cheat sheet, and for the hover, focus and disabled states every button needs, CSS button states. You can also browse ready-made React and Tailwind modals, buttons and navigation pieces in our component gallery.

Sources

Checked in October 2026: the shadcn/ui documentation and changelog (ui.shadcn.com), the shadcn-ui/ui GitHub repository; Radix Primitives documentation including its accessibility overview (radix-ui.com/primitives); Headless UI (headlessui.com); daisyUI install, upgrade and component pages (daisyui.com); MUI Material UI documentation (mui.com); and the npm registry listings for shadcn, radix-ui, @headlessui/react, daisyui, flowbite-react and @mui/material for versions, licences and peer dependencies.

Frequently asked questions

Is shadcn/ui a component library?

Not in the usual sense. Its own documentation says it is not a component library but a way to build your own: a CLI copies the component source into your project, and you edit and maintain that code. Its interactive parts are built on a headless library, Base UI by default for new projects since July 2026, with Radix still supported.

What is the difference between Radix and Headless UI?

Both are unstyled, accessible React components. Radix Primitives has a larger set, including toast, tooltip, slider and toolbar, and supports React 16.8 to 19. Headless UI comes from the Tailwind CSS team, has a smaller set without a toast or tooltip, and requires React 18 or 19.

Does daisyUI work with React?

Yes. daisyUI is a Tailwind CSS plugin that adds class names such as btn, modal and tabs, so you use those classes in JSX like any other Tailwind class. It does not ship React components, so state such as opening a modal or switching tabs is handled in your own React code.

Are these libraries free for commercial use?

Yes. shadcn/ui, Radix Primitives, Headless UI, daisyUI, flowbite-react and MUI's core Material UI package are all MIT licensed at the time of writing (October 2026). MUI X Pro and Premium components and Flowbite's Pro blocks are separate commercial products.

Which React UI library is best for accessibility?

Radix Primitives, Headless UI and MUI build keyboard navigation and focus management into their components, and shadcn/ui inherits that from its primitive layer. daisyUI relies on native HTML elements, so you add ARIA and keyboard behavior yourself. Whichever you use, test with a keyboard and a screen reader.

Can I use MUI with Tailwind CSS?

You can, but they style things differently: MUI uses Emotion and a JavaScript theme, Tailwind uses utility classes. They work side by side, though you have to keep colors and spacing in sync between the two themes yourself. Teams that want everything in Tailwind usually pick shadcn/ui or a headless library instead.

More in Guides

Related guides and tools

Start building

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap