shadcn/ui, Radix, Headless UI, daisyUI, Flowbite y MUI se diferencian sobre todo en los estilos: Radix y Headless UI no tienen estilos, daisyUI y Flowbite son Tailwind con estilos previos, MUI es Material Design y shadcn/ui copia código con estilos en tu proyecto. Aquí tienes los mismos seis componentes en cada una y cuál elegir.
Estas seis librerías resuelven problemas distintos, y por eso "cuál es la mejor" no tiene una única respuesta. Dos de ellas (Radix Primitives y Headless UI) te dan comportamiento y accesibilidad sin estilos. Dos (daisyUI y Flowbite) te dan un aspecto de Tailwind ya terminado. MUI te da un sistema de diseño completo con su propio motor de estilos. shadcn/ui ni siquiera es un paquete: copia en tu proyecto el código fuente de componentes con estilos, construidos sobre una capa headless.
Todos los datos de abajo (versiones, licencias, requisitos) se comprobaron en la documentación oficial, el repositorio de GitHub y la ficha de npm de cada proyecto en octubre de 2026. Las versiones cambian rápido, así que toma las cifras como "en el momento de escribir esto".
| shadcn/ui | Radix Primitives | Headless UI | daisyUI | Flowbite React | MUI (Material UI) | |
|---|---|---|---|---|---|---|
| Qué instalas | una CLI que copia el código fuente de los componentes en tu repositorio | paquetes npm (radix-ui o uno por componente) | @headlessui/react | plugin de Tailwind daisyui | flowbite-react | @mui/material más Emotion |
| Modelo de estilos | clases de Tailwind en código que es tuyo; tema mediante variables CSS | sin estilos; tú añades las clases, el estado va en atributos data-state | sin estilos; el estado va en atributos data-*, pensado para Tailwind | clases CSS de componente (btn, modal) y temas | componentes de Tailwind con estilos previos, personalizables con temas | Material Design, CSS-in-JS (prop sx, objeto de tema) |
| Versión (oct. 2026) | CLI shadcn 4.x (sin paquete en tiempo de ejecución) | radix-ui 1.8; paquetes individuales 1.x y 2.x | 2.2 | 5.x | 0.12 (anterior a 1.0) | 9.x |
| Licencia | MIT | MIT | MIT | MIT | MIT | MIT (MUI X Pro/Premium, de pago, van aparte) |
| React necesario | sí | 16.8 a 19 | 18 o 19 | no (HTML y CSS simples) | 18 o 19 | 17, 18 o 19 |
| Tailwind necesario | sí (v4 en la documentación actual) | no | no, pero está pensado para él | sí, Tailwind 4 para daisyUI 5 | sí, Tailwind 3 o 4 | no |
| Enfoque de accesibilidad | la hereda de su capa de primitivas (Base UI o Radix) | sigue las WAI-ARIA Authoring Practices; teclado y foco integrados | se describe como "totalmente accesible"; teclado y foco integrados | se apoya en elementos nativos (<dialog>, <details>); tú añades ARIA y comportamiento | componentes de React con posicionamiento de Floating UI; prueba tú mismo el uso con teclado | ARIA y manejo de teclado en cada componente, documentados componente a componente |
| Tiene toast | sí, mediante Sonner | sí (Toast) | no | sí (solo clase de diseño) | sí (Toast) | sí (Snackbar) |
Dejamos fuera de la tabla el tamaño del bundle a propósito. Las cifras dependen mucho de qué componentes importas, de si tu bundler aplica tree-shaking y (en daisyUI) de cuánto CSS genera Tailwind para las clases que usas. Una única cifra en "kB" por librería confundiría más de lo que ayuda. Mide tu propio build: ejecuta el analizador de tu bundler antes y después de añadir los componentes que de verdad necesitas.
Qué es. shadcn/ui se describe como "not a component library" sino "how you build your component library", es decir, no una librería de componentes, sino la forma de construir la tuya. Ejecutas su CLI (npx shadcn init y luego npx shadcn add button) y escribe el código fuente del componente en tu proyecto. A partir de ahí el código es tuyo para editarlo; no hay ningún paquete de shadcn en tiempo de ejecución que actualizar.
Estilos. Clases de Tailwind CSS dentro de los archivos copiados, con colores y radios como variables CSS para que puedas cambiar el tema en un solo lugar. Las variantes suelen definirse con class-variance-authority.
Por dentro. Las partes interactivas se apoyan en una librería headless. Según el changelog de shadcn/ui, desde julio de 2026 los proyectos nuevos usan Base UI por defecto, y Radix "is not being deprecated", no se va a abandonar: ambas tienen soporte. Su toast está construido sobre Sonner, un paquete de toasts de código abierto independiente.
Licencia y versión. MIT (repositorio de GitHub shadcn-ui/ui). El paquete de la CLI, shadcn, estaba en la 4.x en npm en el momento de escribir esto.
Ojo con. Como el código es tuyo, el mantenimiento también. Las correcciones del proyecto original no llegan solas a tus copias, y los distintos proyectos se van separando con el tiempo.
Qué es. Un conjunto de componentes de React accesibles y sin estilos (diálogo, menú desplegable, pestañas, interruptor, toast, tooltip, popover y más). Puedes instalarlo todo desde el paquete único radix-ui (1.8 en el momento de escribir esto) o un paquete por componente, como @radix-ui/react-dialog.
Estilos. Ninguno por defecto. Cada parte renderiza un elemento simple que puedes estilizar con cualquier método. El estado se expone como atributos data, por ejemplo data-state="open" o data-highlighted, que encajan bien con las variantes data-[state=open]: de Tailwind.
Accesibilidad. La documentación de Radix dice que las primitivas siguen las WAI-ARIA Authoring Practices, ofrecen soporte de teclado y gestionan el foco (por ejemplo, atrapando el foco dentro de un diálogo abierto y devolviéndolo al cerrarlo).
Licencia y requisitos. MIT. El rango de peer dependencies de npm cubre de React 16.8 a 19.
Ojo con. Escribes tú todos los estilos. Radix no incluye una primitiva Button: usa un <button> nativo, o la utilidad Slot para pasar las props a tu propio elemento.
Qué es. Componentes sin estilos del equipo de Tailwind CSS. El paquete de React, @headlessui/react (2.2 en el momento de escribir esto), incluye Button, Checkbox, Combobox, Dialog, Disclosure, Fieldset, Input, Listbox, Menu (desplegable), Popover, Radio Group, Select, Switch, Tabs, Textarea y Transition.
Estilos. Sin estilos. Los componentes exponen el estado mediante atributos data (data-open, data-checked, data-selected, data-focus), así que los estilizas con variantes de Tailwind como data-checked:bg-blue-600 (sintaxis de Tailwind 4) o data-[checked]:bg-blue-600 (Tailwind 3).
Accesibilidad. Su web describe los componentes como "completely unstyled, fully accessible", es decir, totalmente sin estilos y totalmente accesibles. La navegación por teclado, el foco atrapado en los diálogos y el cableado ARIA vienen integrados.
Licencia y requisitos. MIT. React 18 o 19 (peer dependency de npm).
Ojo con. El conjunto es más pequeño que el de Radix: no hay toast, tooltip ni slider, así que necesitarás otro paquete para eso.
Qué es. Un plugin de Tailwind CSS que añade nombres de clase de componente como btn, modal, dropdown, tabs, toggle y toast, además de un conjunto de temas de color. Distribuye CSS, no componentes de React, así que funciona con cualquier framework o sin ninguno.
Estilos. Clases semánticas con modificadores (btn btn-primary btn-sm) que puedes seguir combinando con las utilidades normales de Tailwind. Los temas se cambian mediante variables CSS.
Requisitos. daisyUI 5 (publicado por primera vez en febrero de 2025, 5.x en el momento de escribir esto) está pensado para Tailwind CSS 4; su guía de actualización te pide actualizar Tailwind primero. Se instala con una sola línea @plugin "daisyui"; en tu CSS.
Accesibilidad. Como es solo CSS, el comportamiento viene del HTML nativo: el modal usa el elemento <dialog> (Escape lo cierra y el navegador gestiona el foco), y los desplegables usan <details> o la API popover. Todo lo demás (ARIA para las pestañas, anunciar los toasts) te toca a ti.
Licencia. MIT.
Ojo con. En React conectas tú el estado (por ejemplo, llamando a showModal() a través de una ref). No hay manejo de las flechas del teclado para menús o pestañas a menos que lo añadas.
Qué es. Componentes de React (flowbite-react) con estilos de Tailwind, del proyecto Flowbite. Incluye Button, Modal, Dropdown, Tabs, ToggleSwitch, Toast, Navbar, Sidebar, Datepicker y más.
Estilos. Con estilos previos en clases de Tailwind, personalizables mediante un objeto de tema y una prop theme en cada componente.
Requisitos y versión. Versión 0.12 en el momento de escribir esto: no ha llegado a la 1.0, así que espera cambios incompatibles entre versiones menores. Las peer dependencies de npm son React 18 o 19 y Tailwind CSS 3 o 4. Usa Floating UI para posicionar los desplegables y los tooltips.
Accesibilidad. Los componentes renderizan los roles esperados en los patrones habituales, pero no encontramos una declaración de accesibilidad publicada comparable a la de Radix. Prueba el comportamiento con teclado y lector de pantalla de los componentes de los que dependas.
Licencia. MIT (la librería de código abierto; Flowbite también vende aparte bloques Pro y archivos de diseño).
Qué es. En sus propias palabras, "an open-source React component library that implements Google's Material Design", una librería de componentes de React de código abierto que implementa Material Design de Google. Es el conjunto más grande de esta comparación, con componentes de visualización de datos, entradas, navegación, maquetación y feedback.
Estilos. CSS-in-JS. Por defecto usa Emotion (@emotion/react y @emotion/styled son peer dependencies de @mui/material 9). Defines el tema con createTheme y los estilos puntuales con la prop sx. Puede convivir con Tailwind, pero los dos sistemas de temas no comparten tokens a menos que los conectes.
Versión y licencia. Versión 9 (la 9.0.0 se publicó en abril de 2026). La librería principal es MIT. Las funciones avanzadas de tabla de datos, fechas y gráficos de MUI X Pro y Premium tienen licencias comerciales.
Accesibilidad. Los componentes incluyen atributos ARIA y manejo de teclado, y las páginas de componentes de la documentación de MUI describen el comportamiento de accesibilidad de cada uno.
Ojo con. Alejarse del aspecto Material exige un trabajo de tema considerable, y CSS-in-JS necesita configuración extra con React Server Components.
Abajo, el mismo botón, diálogo, menú desplegable, pestañas, interruptor y toast están escritos seis veces, una con la API documentada de cada librería. Cada fragmento es código original escrito para esta página (no copiado de la documentación de las librerías), usa la versión mayor actual de cada librería y está recortado a las partes que cambian. Las versiones en vivo y clicables se muestran solo para Radix Primitives, porque Radix es la librería instalada en este sitio. Tienen nuestras propias clases de Tailwind, ya que Radix no incluye estilos. No mostramos versiones en vivo de las otras cinco librerías, así que nada aquí imita su aspecto.
La mayoría de las librerías incluyen un botón con variantes. Fíjate en que Radix y Headless UI te dejan el aspecto a ti, mientras que daisyUI expresa las variantes como clases y el resto como props.
// 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>
// 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} />;
}
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 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>
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>
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>
En un diálogo es donde se esconde el trabajo de accesibilidad: atrapar el foco, cerrar con Escape, devolver el foco al disparador, etiquetar. Las librerías headless y MUI lo hacen en JavaScript; daisyUI se apoya en el elemento nativo <dialog>, que gestiona Escape y el foco en el navegador.
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>
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>
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>
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>
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>
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>
Los menús necesitan navegación con flechas, búsqueda al escribir y los roles correctos. Radix, Headless UI, shadcn/ui y MUI se encargan de ello; la versión de daisyUI con <details> se abre y se cierra sin JavaScript, pero no añade navegación con flechas.
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>
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>
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>
<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>
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>
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>
Unas pestañas correctas necesitan role="tablist", role="tab", aria-selected y movimiento con flechas entre pestañas. En daisyUI y MUI conectas tú los paneles y los IDs; las demás los conectan por ti.
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>
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>
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>
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>
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>
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>
Un interruptor es un control de encendido y apagado que se anuncia como role="switch". daisyUI da estilo a una casilla de verificación real, que conserva el comportamiento nativo del formulario; las demás renderizan un botón con el rol switch.
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>
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>
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>
<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>
import { ToggleSwitch } from "flowbite-react";
<ToggleSwitch checked={on} onChange={setOn} label="Email notifications" />
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"
/>
Los toasts son donde más se diferencian las librerías. Algunas te dan un componente que controlas con estado, otras una función que llamas desde cualquier sitio, y Headless UI lo deja fuera por completo.
// 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>
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>
// 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>
{/* 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>
)}
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>
)}
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>}
/>
| Proyecto | Buena opción | Por qué |
|---|---|---|
| Web de marketing o landing page en Tailwind | daisyUI, o Tailwind sin más | Las páginas casi estáticas necesitan botones, tarjetas y un menú, no una capa de componentes en JavaScript. Las clases de daisyUI también funcionan en páginas sin React. |
| Producto SaaS con marca propia | shadcn/ui, o Radix / Headless UI con tus estilos | Obtienes un comportamiento accesible sin heredar el aspecto de otro producto, y puedes ajustarte exactamente a tu sistema de diseño. |
| Construir tu propio sistema de diseño o librería de componentes | Radix Primitives (o Base UI), Headless UI | Las primitivas sin estilos son la capa sobre la que quieres poner tus propios estilos. shadcn/ui es en sí un ejemplo de este enfoque. |
| Herramienta interna, panel de administración, dashboard | MUI, o shadcn/ui | La amplitud de MUI (tablas, selectores de fecha con MUI X, formularios) es lo que más tiempo ahorra cuando el aspecto importa menos que publicar. shadcn/ui si ya usas Tailwind. |
| Prototipo o hackathon con Tailwind | Flowbite React o daisyUI | Componentes con aspecto terminado y muy poco trabajo de estilos. Prevé revisarlo si el prototipo se convierte en producto. |
| Equipo que ya usa Material Design (o una interfaz al estilo Android) | MUI | Implementa Material Design directamente, así que diseñadores y desarrolladores comparten un mismo vocabulario. |
| Requisitos estrictos de accesibilidad (sector público, grandes empresas) | Radix Primitives, Headless UI, MUI, o shadcn/ui sobre ellas | El comportamiento de teclado y foco viene integrado y documentado. Elijas lo que elijas, prueba igualmente con teclado y con lector de pantalla; ninguna librería hace accesible una página por sí sola. |
Para los hooks sobre los que se construyen estos componentes, consulta la chuleta de hooks de React. Para las clases de utilidad que se usan en todos los fragmentos, consulta la chuleta de Tailwind, y para los estados hover, focus y disabled que necesita todo botón, estados de botón en CSS. También puedes explorar en nuestra galería de componentes modales, botones y piezas de navegación listos para usar con React y Tailwind.
Comprobado en octubre de 2026: la documentación y el changelog de shadcn/ui (ui.shadcn.com), el repositorio de GitHub shadcn-ui/ui; la documentación de Radix Primitives, incluida su página general de accesibilidad (radix-ui.com/primitives); Headless UI (headlessui.com); las páginas de instalación, actualización y componentes de daisyUI (daisyui.com); la documentación de MUI Material UI (mui.com); y las fichas del registro de npm de shadcn, radix-ui, @headlessui/react, daisyui, flowbite-react y @mui/material para versiones, licencias y peer dependencies.
No en el sentido habitual. Su propia documentación dice que no es una librería de componentes, sino una forma de construir la tuya: una CLI copia el código fuente de los componentes en tu proyecto, y tú editas y mantienes ese código. Sus partes interactivas se construyen sobre una librería headless, Base UI por defecto en los proyectos nuevos desde julio de 2026, y Radix sigue teniendo soporte.
Ambas son componentes de React accesibles y sin estilos. Radix Primitives tiene un conjunto más grande, con toast, tooltip, slider y toolbar, y admite de React 16.8 a 19. Headless UI viene del equipo de Tailwind CSS, tiene un conjunto más pequeño sin toast ni tooltip y necesita React 18 o 19.
Sí. daisyUI es un plugin de Tailwind CSS que añade nombres de clase como btn, modal y tabs, así que usas esas clases en JSX como cualquier otra clase de Tailwind. No incluye componentes de React, así que el estado, como abrir un modal o cambiar de pestaña, se gestiona en tu propio código React.
Sí. shadcn/ui, Radix Primitives, Headless UI, daisyUI, flowbite-react y el paquete principal Material UI de MUI tienen licencia MIT en el momento de escribir esto (octubre de 2026). Los componentes MUI X Pro y Premium y los bloques Pro de Flowbite son productos comerciales aparte.
Radix Primitives, Headless UI y MUI integran la navegación por teclado y la gestión del foco en sus componentes, y shadcn/ui lo hereda de su capa de primitivas. daisyUI se apoya en elementos HTML nativos, así que el comportamiento ARIA y de teclado lo añades tú. Uses la que uses, prueba con teclado y con lector de pantalla.
Puedes, pero aplican los estilos de forma distinta: MUI usa Emotion y un tema en JavaScript, Tailwind usa clases de utilidad. Funcionan uno al lado del otro, aunque tendrás que mantener tú sincronizados los colores y el espaciado entre los dos temas. Los equipos que lo quieren todo en Tailwind suelen elegir shadcn/ui o una librería headless.
En vivo: {title} de Radix Primitives (librería sin estilos, nuestras clases de Tailwind)
Editar perfil
Pulsa Tab: el foco se queda dentro. Pulsa Escape o haz clic fuera para cerrar.
Nombre visible
Cancelar
Guardar
Cerrar
nada todavía
Opciones
Renombrar
Duplicar
Mover (desactivado)
Mostrar archivos ocultos
Prueba las flechas y la búsqueda al escribir. Última elección: {last}
Ajustes
Cuenta
Facturación
Equipo
Panel de ajustes de la cuenta. Usa las flechas izquierda y derecha sobre las pestañas.
Panel de facturación.
Panel del equipo.
Notificaciones por correo: activadas
Notificaciones por correo: desactivadas
Guardar cambios
Cambios guardados
Guardado {n} vez en esta demo.
Guardado {n} veces en esta demo.
Deshacer el guardado
Deshacer
Empieza gratis · Sin tarjeta de crédito