Skip to main content

gentleduck/primitives

Unstyled, accessible React primitives for building serious component libraries and product-grade interaction systems.

What this package is

gentleduck/primitives is a set of low-level React building blocks for teams that need:

  • Correct keyboard and screen reader behavior.
  • Composition via asChild, slotting, and scoped contexts.
  • Controlled and uncontrolled state.
  • Animation-aware mount/unmount.
  • A path from raw primitive to polished design-system component.

This is not a CSS framework. It is an interaction and accessibility layer.


When to use primitives

Use primitives for:

  • A reusable design system across apps.
  • Complex overlays (dialogs, popovers, menus).
  • UI with strict accessibility requirements.
  • Heavy visual customization where pre-styled libraries get in the way.

For quick, fixed styling with minimal customization, use pre-styled components instead.


Capability model

LayerResponsibilityTypical primitives
FoundationRendering abstraction and compositionPrimitive Elements, Slot, Direction
Interaction infraFocus, dismissal, mounting, positioningFocus Scope, Dismissable Layer, Presence, Portal, Popper
Product patternsOverlays, menus, inputs, selectionDialog, Popover, Menu, Select, Slider

This layering is why primitives scale in large codebases.


How teams usually adopt

  1. Start with one high-value wrapper (Dialog, Popover, Menu).
  2. Standardize wrapper rules: forwardRef, className merge, defaults, no hidden primitive props.
  3. Add design tokens and motion classes via data-state.
  4. Document wrapper contracts in your design-system package.
  5. Add more primitives once keyboard and screen reader tests pass.

Bundle size

gentleduck primitives are 50-92% smaller than the Radix equivalents. Shared internals (Slot, Presence, Popper, focus-scope) are deduplicated across all primitives. Measured sizes: Alert Dialog 1.6 KB vs 18.6 KB, Popover 2.4 KB vs 19.6 KB, Dialog 3.1 KB vs 10.6 KB. Installing 5 Radix primitives costs about 25 KB in duplicated internals; with gentleduck the shared internals load once.

See benchmark results for per-component numbers and methodology.


Architecture snapshot

Loading diagram...

Higher-level components compose lower-level ones. Drop one level lower when you need custom behavior.


Production checklist

Before shipping primitive-based wrappers:

  • Verify title/description semantics for dialogs.
  • Verify keyboard loops and escape behavior.
  • Verify focus restoration after close.
  • Verify click-outside handling for nested overlays.
  • Verify reduced-motion behavior.
  • Verify RTL behavior if your app supports Arabic/Hebrew/Persian.

Internal examples

Production patterns from the internal registry.

1) Guarded async dialog

2) Side-aware popover

3) Dropdown menu selection semantics

4) Tooltip delay behavior

5) Controlled select

6) Alert dialog confirmation


Learn next