Introduction
The story behind gentleduck — why we rebuilt the React UI stack from primitives up, who it is for, and how the pieces fit together.
A short story
In 2024 a small group of React engineers got tired of the same trade-off. The popular option was to ship a Radix-based component library: accessible, well-tested, but heavy. Every primitive package shipped its own copy of the same internal helpers, and bundle reports kept growing. The other option was to roll components from scratch: smaller, but suddenly we were the people responsible for keyboard traps, screen reader announcements, focus restoration, and ten years of accessibility nuance.
We wanted both. A stack where the primitives were as accessible as Radix, but where shared internals loaded once instead of being duplicated per package. Where the calendar wasn't a 20 KB monolith. Where the CLI didn't only know how to add components — it knew how to update, diff, and remove them. Where the keyboard layer was first-class instead of a userland afterthought.
That stack became gentleduck.
What gentleduck is
gentleduck is an organization of small, single-purpose packages that compose into a full React product surface. Every package is independently versioned, independently published, and independently useful — but they were designed to fit together.
The pieces split along three layers:
Primitives — @gentleduck/primitives
Headless, accessible, ARIA-correct building blocks: Dialog, Popover, Menu, Combobox, Tabs, Accordion, Tooltip, and the rest. The same compound API and asChild ergonomics you know from Radix, but 50–92% smaller per component because shared focus, dismissal, and presence logic loads once. If you only need behavior, you only ship behavior.
Components — @gentleduck/registry-ui
Tailwind-styled components built on top of the primitives. Copy-pasted into your app via the CLI — you own the source. Looks and feels like shadcn/ui because the philosophy is the same: don't lock the design behind a wall of npm. The difference is what's underneath.
Tools
@gentleduck/cli— scaffold, add, update, diff, and remove components. Not justadd.@gentleduck/calendar— a 5 KB headless calendar engine with seven date adapters (Gregorian, Islamic, Persian, Hebrew, Buddhist, Indian, Japanese).@gentleduck/vim— a keyboard command and chord engine with React hooks.@gentleduck/motion— animation tokens that respectprefers-reduced-motion.@gentleduck/variants— a typedcva()variant system.@gentleduck/registry-build— the registry compiler that ships your own component registry.
You can adopt any one of these without adopting the rest.
Who this is for
- Teams migrating off Radix who want the same compound API at a fraction of the bundle size.
- shadcn/ui users who want a richer ecosystem (calendar, CLI updates, vim, registries) without leaving the copy-paste model.
- Library authors who need accessible primitives but ship to the smallest possible footprint.
- Apps with global reach that need non-Gregorian calendars, RTL-correct date pickers, or a keyboard layer that isn't bolted on.
What's next
- Installation — pick a framework and bring gentleduck into your app.
- Packages — what each package does and how it fits with the rest.
- Comparisons — bundle sizes, feature matrices, and migration guides versus Radix, shadcn, and react-day-picker.
- FAQs — short answers to the questions we hear most often.