Compatibility

The components, their client scripts and their known differences from shadcn/ui.

Every component is translated from the upstream shadcn/ui registry for every Base UI style and compared with upstream's rendering in screenshot tests. This table lists what you can install today; the components are experimental, like the project.

ComponentClient JSKnown differences
AccordionNoneBuilt on native <details>/<summary>: no JavaScript. Items share a name so only one is open unless multiple is set (Baseline 2024: Chrome 120, Firefox 130, Safari 17.2).
Controlled state (value, onValueChange) is not supported; defaultValue opens items by their value. The trigger is the <summary> (no h3 around it; browsers expose the expanded state without aria-expanded), and arrow keys do not move between items.
Opening and closing are not animated. Closed panels stay in the page, so find-in-page can reveal them.
Accepts class instead of className.
AlertNoneAccepts class instead of className.
Alert DialogNoneBuilt on the native <dialog> with Invoker Commands (command/commandfor): no JavaScript, but requires Baseline 2025 browsers (Chrome 135, Firefox 144, Safari 26.2).
Controlled state (open, defaultOpen, onOpenChange) is not supported, and the trigger does not reflect the open state (aria-expanded). Triggers and close buttons support render, e.g. render={<Button variant="outline" />}.
The overlay is the dialog's ::backdrop (the overlay component renders nothing); closing animates out, but only Chromium keeps the popup and backdrop in the top layer meanwhile (elsewhere the backdrop disappears at once); like Base UI, outside clicks do not close it.
Focus handling is the browser's: after the last control, Tab moves to the browser UI before wrapping (page content stays inert), where Base UI keeps focus inside the popup.
Accepts class instead of className.
Aspect RatioNoneAccepts class instead of className.
AttachmentNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
Avatar/shadcn/avatar.jsThe image covers the fallback once it loads, so avatars work without JavaScript. Hiding an image that fails to load (the fallback shows instead) and removing the fallback after a load need the client script /shadcn/avatar.js (<script type="module" src="/shadcn/avatar.js">); without it a broken image shows its alt text over the fallback.
The fallback's delay and onLoadingStatusChange are not supported.
Accepts class instead of className.
BadgeNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
BreadcrumbNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
BubbleNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
ButtonNoneRenders a native <button> with type="button" by default, like Base UI; pass type="submit" for form submission.
render is supported on the server; a non-button target such as render={<a href="/docs" />} keeps its native role (no role="button" or tabindex), since the client-side button behavior Base UI adds is not shipped.
focusableWhenDisabled is not supported.
Accepts class instead of className.
Button GroupNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
CardNoneAccepts class instead of className.
CheckboxNoneBuilt on a native <input type="checkbox"> inside the styled root: no JavaScript, and the value is submitted with forms. id, name, value, disabled, required, form and aria-* go to the input, so <Label for> works as usual.
checked/defaultChecked set the initial state; onCheckedChange, readOnly, indeterminate and render are not supported.
Accepts class instead of className.
Collapsible/shadcn/collapsible.jsWhen CollapsibleTrigger is the first child of Collapsible, it is built on native <details>/<summary>: no JavaScript, but every other child is hidden while closed, not only CollapsibleContent, and state attributes (data-open, data-panel-open) are not rendered (use open: variants).
With the trigger anywhere else, the trigger is a <button> and the content is hidden while closed, like Base UI; toggling needs the client script /shadcn/collapsible.js (<script type="module" src="/shadcn/collapsible.js">), and Base UI's state attributes are rendered.
CollapsibleTrigger does not support render; style it with class (for example buttonVariants()). open/defaultOpen set the initial state; onOpenChange and disabled are not supported.
Accepts class instead of className.
Combobox/shadcn/combobox.js, /shadcn/input-group.jsOpening, filtering, keyboard highlighting and selection need the client script /shadcn/combobox.js (<script type="module" src="/shadcn/combobox.js">); without it the input shows the initial selection and the form submits the initial value. The list is a native popover placed with CSS anchor positioning.
Items come from items and the ComboboxList render function (or static children); values are compared by their string form (itemToStringValue, a { label, value } object's value, or the string). Filtering follows Base UI's default (contains, ignoring case, accents and punctuation); filter, limit, filteredItems and inline are not supported. New chips of a multiple combobox are copied from the ComboboxValue render function's output for the first item, with its label replaced. Controlled state (open, onValueChange, inputValue) is not supported.
Accepts class instead of className.
useComboboxAnchor() returns a CSS anchor name for ComboboxChips (ref) and ComboboxContent (anchor) instead of a React ref.
Context Menu/shadcn/menu.jsA right click on the trigger area opens the menu at the pointer; this needs the client script /shadcn/menu.js (<script type="module" src="/shadcn/menu.js">), without which the browser's own context menu appears. The menu is a native popover placed with CSS anchor positioning, and the script adds the menu behavior (focus, arrow keys, typeahead, checkbox and radio items, submenus). Long presses on touch screens do not open it.
Items have no onClick on the server: use render for links (render={<a href="/settings" />}) or form buttons. Controlled state (open, onOpenChange, checked/onCheckedChange, value/onValueChange), modal (page scroll is not locked) and openOnHover on the root are not supported.
Accepts class instead of className.
Date Picker (Lite)NoneA lite alternative to a date picker built from calendar (not a port), with no JavaScript: upstream's Input as a native date input (type can also be datetime-local, month or week) with a calendar icon, submitting ISO values.
The browser draws the calendar popup, which cannot be styled (it follows color-scheme in dark mode) and differs between browsers; the field shows the browser's date format rather than a placeholder. Date ranges and several months are not supported.
Accepts class instead of className.
DialogNoneBuilt on the native <dialog> with Invoker Commands (command/commandfor): no JavaScript, but requires Baseline 2025 browsers (Chrome 135, Firefox 144, Safari 26.2).
Controlled state (open, defaultOpen, onOpenChange) is not supported, and the trigger does not reflect the open state (aria-expanded). Triggers and close buttons support render, e.g. render={<Button variant="outline" />}.
The overlay is the dialog's ::backdrop (the overlay component renders nothing); closing animates out, but only Chromium keeps the popup and backdrop in the top layer meanwhile (elsewhere the backdrop disappears at once); outside clicks close the dialog only where closedby is supported.
Focus handling is the browser's: after the last control, Tab moves to the browser UI before wrapping (page content stays inert), where Base UI keeps focus inside the popup.
Accepts class instead of className.
DirectionNoneDirectionProvider carries the direction through Hono context and renders no element, like Base UI; set dir on an element too. The generated components follow the CSS direction, so they do not need it.
Drawer/shadcn/drawer.jsBuilt on the native <dialog> with Invoker Commands (command/commandfor), like Dialog: opening, closing, Escape and outside clicks need no JavaScript (Baseline 2025 browsers). The overlay is the dialog's ::backdrop (the overlay component renders nothing), and the viewport renders nothing.
Swiping to close needs the client script /shadcn/drawer.js (<script type="module" src="/shadcn/drawer.js">). Snap points, nested drawer stacking, swipe areas and modal={false} are not supported (the drawer always opens as a modal dialog). Controlled state (open, defaultOpen, onOpenChange) is not supported.
Accepts class instead of className.
Dropdown Menu/shadcn/menu.jsThe menu is a native popover placed with CSS anchor positioning, so it opens without JavaScript (placement needs Chrome 135, Firefox 147 or Safari 26.2). The client script /shadcn/menu.js (<script type="module" src="/shadcn/menu.js">) adds the menu behavior: focus handling, arrow keys, typeahead, checkbox and radio items, submenus and closing after a choice.
Items have no onClick on the server: use render for links (render={<a href="/settings" />}) or form buttons. Controlled state (open, onOpenChange, checked/onCheckedChange, value/onValueChange), modal (page scroll is not locked) and openOnHover on the root are not supported.
Accepts class instead of className.
EmptyNoneAccepts class instead of className.
FieldNoneAccepts class instead of className.
Selectors for checkbox and radio roles ([role=checkbox]) match the generated controls by data-slot, as their roots carry no role.
Checked-state styles (has-data-checked:) follow the native :checked state of the generated controls.
Hover Card/shadcn/hover.jsOpening on hover or keyboard focus needs the client script /shadcn/hover.js (<script type="module" src="/shadcn/hover.js">); without it the card does not open and the trigger works as a plain link. The card is a native popover placed with CSS anchor positioning.
Controlled state (open, onOpenChange) is not supported.
Accepts class instead of className.
InputNoneClient-side field state attributes (data-dirty, data-touched, data-focused, data-filled, data-valid) and the auto-generated id are not rendered.
Accepts class instead of className.
Input Group/shadcn/input-group.jsAccepts class instead of className.
Clicking an addon focuses the input with the client script /shadcn/input-group.js (<script type="module" src="/shadcn/input-group.js">); without it the addon is not clickable.
Input OTP (Lite)NoneA lite alternative to input-otp (not a port), with no JavaScript: one native text input over the slots, as in daisyUI, so typing, pasting, one-time-code autofill (autocomplete="one-time-code") and form validation (minlength, pattern, required) come from the browser.
One component with maxLength slots instead of InputOTPGroup, InputOTPSlot and InputOTPSeparator; the whole group is highlighted while focused (upstream highlights the active slot, with a blinking caret). Characters are aligned with tabular digits: set a monospace font (class="font-mono") for letters.
Accepts class instead of className.
ItemNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
KbdNoneAccepts class instead of className.
LabelNoneAccepts class instead of className.
MarkerNonerender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
Menubar/shadcn/menu.jsThe menu is a native popover placed with CSS anchor positioning, so it opens without JavaScript (placement needs Chrome 135, Firefox 147 or Safari 26.2). The client script /shadcn/menu.js (<script type="module" src="/shadcn/menu.js">) adds the menu behavior: focus handling, arrow keys, typeahead, checkbox and radio items, submenus and closing after a choice.
Items have no onClick on the server: use render for links (render={<a href="/settings" />}) or form buttons. Controlled state (open, onOpenChange, checked/onCheckedChange, value/onValueChange), modal (page scroll is not locked) and openOnHover on the root are not supported.
Moving between menus with the arrow keys and switching menus by hovering while one is open need the client script /shadcn/menu.js (<script type="module" src="/shadcn/menu.js">); without it each menu still opens with its trigger. modal is not supported.
Accepts class instead of className.
MessageNoneAccepts class instead of className.
Native SelectNoneAccepts class instead of className.
Navigation Menu/shadcn/navigation-menu.jsOpening items on hover, click and the keyboard needs the client script /shadcn/navigation-menu.js (<script type="module" src="/shadcn/navigation-menu.js">); without it only the top-level links work. The content shows in a native popover placed with CSS anchor positioning under the open item's trigger.
Controlled state (value, defaultValue, onValueChange) is not supported. Switching items does not animate the popup's size.
Accepts class instead of className.
PaginationNoneAccepts class instead of className.
PopoverNoneBuilt on the native popover attribute with Invoker Commands and CSS anchor positioning: no JavaScript, but placement next to the trigger needs Chrome 135, Firefox 147 or Safari 26.2 (elsewhere the popover opens centered). Outside clicks and Escape close it.
Controlled state (open, defaultOpen, onOpenChange) and openOnHover are not supported. side, align, sideOffset and alignOffset place the popover; on collision it flips to the opposite side, but data-side keeps the requested side. Focus is not moved into the popover, and the positioner's classes are not rendered.
Accepts class instead of className.
ProgressNoneServer-rendered: the progressbar is not linked to ProgressLabel (Base UI links them on the client); pass aria-label or aria-labelledby. Function children of ProgressValue are not supported.
Accepts class instead of className.
Radio GroupNonevalue/defaultValue set the initial selection; onValueChange and readOnly are not supported.
Built on native <input type="radio"> elements sharing the group's name (generated unless given): no JavaScript, arrow keys move the selection, and the value is submitted with forms. id, disabled and aria-* go to the input.
Accepts class instead of className.
Scroll Area/shadcn/scroll-area.jsThe custom scrollbars need the client script /shadcn/scroll-area.js (<script type="module" src="/shadcn/scroll-area.js">); without it the area scrolls with the browser's own scrollbar.
overflowEdgeThreshold is not supported.
Accepts class instead of className.
SelectNoneBuilt on a customizable native <select> (appearance: base-select): no JavaScript, keyboard selection and typeahead are the browser's (Space and the arrow keys open it, Enter does not), and name/value are submitted with forms. Chrome 135, Firefox 149 and Safari 27 or later show upstream's design; older browsers show a classic select with the trigger's styles.
SelectTrigger renders the <select> (so id and aria-* given to it reach the form control), and <SelectContent> must be a direct child of <Select>. value/defaultValue set the initial selection; onValueChange, multiple, items, value render functions and aligning the selected item with the trigger (alignItemWithTrigger) are not supported: the list opens below the trigger. Classes given to SelectContent are not applied to the list.
With an inverted menu color (a preset option), the list keeps the page's colors: the browser's picker cannot take upstream's dark class, while the other menus are inverted.
Accepts class instead of className.
SeparatorNoneAccepts class instead of className.
SheetNoneBuilt on the native <dialog> with Invoker Commands (command/commandfor): no JavaScript, but requires Baseline 2025 browsers (Chrome 135, Firefox 144, Safari 26.2).
Controlled state (open, defaultOpen, onOpenChange) is not supported, and the trigger does not reflect the open state (aria-expanded). Triggers and close buttons support render, e.g. render={<Button variant="outline" />}.
The overlay is the dialog's ::backdrop (the overlay component renders nothing); closing animates out, but only Chromium keeps the popup and backdrop in the top layer meanwhile (elsewhere the backdrop disappears at once); outside clicks close the dialog only where closedby is supported.
Focus handling is the browser's: after the last control, Tab moves to the browser UI before wrapping (page content stays inert), where Base UI keeps focus inside the popup.
Accepts class instead of className.
Sidebar/shadcn/hover.js, /shadcn/sidebar.jsrender is supported on the server (element or function), like Base UI.
Accepts class instead of className.
Toggling (the trigger, the rail and Ctrl/Cmd+B), remembering the state in the sidebar_state cookie and the mobile sheet need the client script /shadcn/sidebar.js (<script type="module" src="/shadcn/sidebar.js">); without it the sidebar shows in its initial state on wide screens and not at all on narrow ones.
On the server the state comes from open or defaultOpen (read the cookie to restore it); onOpenChange is not supported, and useSidebar() only reports that state (its setters throw on the server). The mobile sheet is rendered once, empty: the script moves the sidebar's content into it while it is open.
SkeletonNoneAccepts class instead of className.
Slider/shadcn/slider.jsEach thumb holds a native <input type="range"> (focusable, keyboard-operable and submitted with name). Pointer dragging, keeping range thumbs in order and moving the thumbs as values change need the client script /shadcn/slider.js (<script type="module" src="/shadcn/slider.js">); without it the initial values are shown and submitted.
Controlled state (onValueChange), format, locale and largeStep are not supported (Page Up and Page Down use the browser's step).
Accepts class instead of className.
SpinnerNoneAccepts class instead of className.
SwitchNoneBuilt on a native <input type="checkbox" role="switch"> inside the styled root: no JavaScript, and the value is submitted with forms. id, name, value, disabled, required, form and aria-* go to the input, so <Label for> works as usual.
checked/defaultChecked set the initial state; onCheckedChange, readOnly and render are not supported.
Accepts class instead of className.
TableNoneAccepts class instead of className.
Selectors for checkbox and radio roles ([role=checkbox]) match the generated controls by data-slot, as their roots carry no role.
Tabs/shadcn/tabs.jsSwitching tabs needs the client script /shadcn/tabs.js (<script type="module" src="/shadcn/tabs.js">); without it the selected panel is shown. Pointer, Enter, Space, arrow keys, Home and End work like Base UI.
Inactive panels are rendered with hidden (Base UI does not render them). Controlled state (onValueChange) and render are not supported; value/defaultValue set the selected tab.
Accepts class instead of className.
TextareaNoneAccepts class instead of className.
Toast/shadcn/toast.jsToasts are added in the browser with toast from the client script (import { toast } from "/shadcn/toast.js", then toast.add({ title, description, type, actionProps })), or declaratively with data-toast-trigger buttons (data-toast-title, data-toast-description, data-toast-type). The script copies the toast markup from templates that <Toaster> renders, so edit the components as usual.
On the server, pass toasts to <Toaster toasts={[{ title: "Saved" }]} /> (for example flash messages after a form post) or use a per-response createToastManager() with <Toaster toastManager={manager}>; they show without JavaScript, and the script times them out. The exported toast is shared by every request on the server, so its add() throws there. Action onClick and other function props, and useToastManager() state updates, work only through /shadcn/toast.js.
Accepts class instead of className.
ToggleNoneA label around a visually hidden native checkbox (the pressed state is its checked state): no JavaScript, and name/value are submitted with forms. aria-* goes to the input, so icon-only toggles need aria-label as upstream.
Space toggles, Enter does not (a checkbox, not a button); pressed/defaultPressed set the initial state; onPressedChange and render are not supported.
Accepts class instead of className.
Toggle GroupNoneItems are native radios sharing a name (checkboxes with multiple): the pressed item of a single-selection group cannot be released by pressing it again, and arrow keys select as they move. defaultValue sets the pressed items; value/onValueChange are not supported.
A label around a visually hidden native checkbox (the pressed state is its checked state): no JavaScript, and name/value are submitted with forms. aria-* goes to the input, so icon-only toggles need aria-label as upstream.
Space toggles, Enter does not (a checkbox, not a button); pressed/defaultPressed set the initial state; onPressedChange and render are not supported.
Accepts class instead of className.
Tooltip/shadcn/hover.jsOpening on hover or keyboard focus needs the client script /shadcn/hover.js (<script type="module" src="/shadcn/hover.js">); without it the tooltip does not open, but the trigger's aria-describedby still exposes its text. The popup is a native popover placed with CSS anchor positioning.
Unlike Base UI, the popup has role="tooltip" and describes the trigger. Controlled state (open, onOpenChange) is not supported.
Accepts class instead of className.

Not available yet

These upstream components depend on React libraries or on features Hono JSX does not have yet:

The generated compatibility manifest, with the classification and parity status of every component, is compatibility.json in the repository.