@bgunnarsson/react-primitives
An unstyled, accessible React component library. Built on Radix UI primitives — components ship with zero CSS and are styled entirely via className in the consuming project.
- Unstyled by default — no opinions on colors, spacing, or typography. Apply Tailwind or any CSS solution via
className.
- Accessible — Radix UI handles ARIA attributes, keyboard navigation, and focus management out of the box.
- Composable — compound component pattern throughout. Each named part (trigger, content, item, etc.) is a separate export that accepts
className.
- Framework-agnostic styling — works with Tailwind CSS v3/v4, CSS Modules, plain CSS, or any other approach.
pnpm add @bgunnarsson/react-primitives
Import components directly from the package:
import { useState } from 'react'
import { Button, Dialog, DialogContent, DialogTitle } from '@bgunnarsson/react-primitives'
export function MyComponent() {
const [open, setOpen] = useState(false)
return (
<>
<Button className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white" onClick={() => setOpen(true)}>
Open dialog
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-6 shadow-xl">
<DialogTitle className="text-lg font-semibold">Hello</DialogTitle>
</DialogContent>
</Dialog>
</>
)
}
If you use the Lightbox component, import its stylesheet once in your app entry or layout:
import 'yet-another-react-lightbox/styles.css'
Place <Toaster> once in your root layout to enable toast notifications:
import { Toaster } from '@bgunnarsson/react-primitives'
export default function Layout({ children }) {
return <>{children}<Toaster /></>
}
pnpm dev # Start Storybook on http://localhost:6006
pnpm build # Build the library (ESM + CJS + types)
pnpm build-storybook # Build static Storybook
95 components across layout, form, navigation, overlay, media, and low-level utility categories.
| Component |
Description |
| AspectRatio |
Container that locks children to a fixed width/height ratio |
| Box |
Polymorphic block-level wrapper with as prop |
| Card |
Flexible container for composing any card layout |
| CodeBlock |
Code display with title, copy button, and optional syntax highlighting |
| Container |
Generic div wrapper accepting all HTML attributes |
| EmptyState |
Zero-data placeholder with icon, title, description, and action |
| Footer |
Semantic <footer> wrapper |
| Grid |
CSS grid container and item wrapper |
| Header |
Semantic <header> wrapper |
| Resizable |
Resizable split-pane layouts (IDE sidebars, chat UIs) |
| ScrollArea |
Scrollable container with custom scrollbars |
| Separator |
Horizontal or vertical dividing line |
| Skeleton |
Loading placeholder element |
| Stack / Flex |
Flex layout with direction, align, justify, gap, and wrap shortcuts |
| Stat |
Metric display with label, value, and help text |
| Table |
Semantic table with Header, Body, Footer, Row, Head, Cell, Caption |
| Text |
Polymorphic text element (p, span, h1–h6) |
| VirtualList |
Windowed list for large datasets — fixed or variable item sizes, vertical or horizontal |
| Component |
Description |
| AudioPlayer |
HTML5 audio wrapper |
| Badge |
Inline label for status, counts, or categories |
| Embed |
Iframe wrapper with safe defaults plus YouTube / Vimeo URL helpers |
| Icon |
SVG icon from sprite or external file |
| Image |
DPR-aware responsive <img> (1x/2x/3x srcset) for image-resizing backends |
| CropImage |
Responsive <picture> with separate mobile / desktop crops, each DPR-aware |
| Lightbox |
Full-screen image viewer with navigation |
| Mark |
Highlighted text — direct wrap or query-based search highlighting |
| Picture |
Responsive <picture> with multiple sources |
| Richtext |
CMS HTML string renderer |
| Spinner |
Animated loading indicator |
| VideoPlayer |
HTML5 video wrapper |
| Component |
Description |
| Button |
Unstyled button element |
| Calendar |
Inline calendar for single, range, or multiple date selection |
| Checkbox |
Accessible checkbox with built-in indicator |
| CheckboxGroup |
Controlled group of labelled checkboxes |
| Combobox |
Searchable dropdown select |
| DatePicker |
Single date picker with calendar popover |
| DateRangePicker |
Two-date range picker with calendar popover |
| Editable |
Click-to-edit inline text with preview/input swap |
| FileInput |
File picker with drag-and-drop support |
| Form |
Context-based field wrapper (label, control, error message) |
| Input |
Text input field |
| InputOTP |
One-time-password / verification-code input |
| Label |
Form field label |
| MaskedInput |
Pattern-masked input (credit card, SSN, custom formats) |
| Mention |
@-style autocomplete primitive (render-prop) for input/textarea |
| NumberInput |
Numeric input with increment/decrement buttons |
| PasswordInput |
Password input with show/hide toggle |
| PhoneInput |
International phone input with country selector, emits E.164 |
| RadioGroup |
Radio button group |
| Rating |
Star / score rating input with keyboard and screen-reader support |
| SearchInput |
Search input with leading icon and clear button |
| Select |
Dropdown select with groups and separators |
| Slider |
Accessible range slider |
| Switch |
Toggle switch |
| TagInput |
Multi-value chip/token input |
| Textarea |
Multi-line text input |
| TimePicker |
Time input with keyboard stepping and 12/24h formats |
| Component |
Description |
| Breadcrumbs |
Navigation trail with current page indicator |
| Link |
Anchor element |
| Menubar |
Desktop-style application menu bar |
| Nav |
Semantic <nav>, <ul>, <li> wrappers |
| NavigationMenu |
Site-header navigation with mega-menu support |
| Pagination |
Page navigation with prev, next, and ellipsis |
| Stepper |
Multi-step progress indicator |
| Tabs |
Tabbed panel interface |
| TreeView |
Hierarchical tree with expand/collapse and selection |
| Component |
Description |
| AlertDialog |
Confirmation dialog for destructive actions |
| Command |
Command palette (⌘K) — inline or as a modal |
| ContextMenu |
Right-click menu |
| Dialog |
Modal dialog |
| Drawer |
Bottom sheet with drag-to-dismiss |
| DropdownMenu |
Button-triggered dropdown menu |
| HoverCard |
Hover-triggered preview popover |
| Popover |
Click-triggered floating panel |
| Sheet |
Sliding panel from any screen edge |
| Toaster |
Toast notification system |
| Tooltip |
Hover/focus tooltip |
| Component |
Description |
| Accordion |
Expandable sections (single or multiple) |
| Carousel |
Touch-friendly slideshow |
| Collapsible |
Single expandable section |
| Progress |
Progress bar (0–100) |
| Toggle |
Pressed/unpressed button |
| ToggleGroup |
Group of toggles (single or multiple selection) |
| Toolbar |
Accessible toolbar with roving-focus keyboard navigation |
| Component |
Description |
| Alert |
Status message with title and description |
| Avatar |
Profile image with text fallback |
| Status |
Live state indicator (online/busy/away/etc.) with optional label |
| Timeline |
Chronological event list (<ol>) with indicators, connectors, and time |
| Component |
Description |
| CopyButton |
Button that copies text to the clipboard with copied-state feedback |
| DirectionProvider |
Declares writing direction (ltr / rtl) for all child Radix primitives |
| ErrorBoundary |
Catches descendant render errors and renders a fallback (with resetKeys and render-prop fallback) |
| FocusScope |
Standalone focus trap with auto-focus and focus restoration |
| Portal |
Renders children into a different part of the DOM tree |
| Slot |
Composition primitive for implementing an asChild prop |
| VisuallyHidden |
Hides content visually while keeping it available to screen readers |