the gentleduck ecosystem

How primitives, variants, hooks, motion, and libs compose into a unified developer tooling stack.

The Stack

GentleDuck is not a single library. It is an ecosystem of focused packages that solve specific problems independently but follow shared conventions that make them stronger together.

Loading diagram...


Core Layer

@gentleduck/libs

The foundation. A collection of tiny, framework-agnostic utilities shared across the entire ecosystem.

  • cn -- Conditional class name merging. The same pattern as clsx + tailwind-merge but as a single import.
  • Slot -- Polymorphic component composition. Render a component as any element type with proper ref forwarding.
  • Portal -- Mount components outside the DOM tree. Used by dialogs, tooltips, and popovers.
  • filtered-object, group-array, parse-date -- Small helpers that avoid pulling in heavy utility libraries.

Every GentleDuck package imports from @gentleduck/libs for shared logic. Zero external dependencies.

@gentleduck/variants

A type-safe class name generator for component styling. Think of it as a faster, smaller alternative to class-variance-authority.

import { cva } from '@gentleduck/variants'
 
const button = cva('inline-flex items-center rounded font-medium', {
  variants: {
    size: {
      sm: 'h-8 px-3 text-xs',
      md: 'h-9 px-4 text-sm',
      lg: 'h-10 px-6 text-base',
    },
    variant: {
      primary: 'bg-primary text-primary-foreground',
      secondary: 'bg-secondary text-secondary-foreground',
      ghost: 'hover:bg-accent hover:text-accent-foreground',
    },
  },
  defaultVariants: { size: 'md', variant: 'primary' },
})
 
button({ size: 'lg', variant: 'ghost' })
// => "inline-flex items-center rounded font-medium h-10 px-6 text-base hover:bg-accent hover:text-accent-foreground"
import { cva } from '@gentleduck/variants'
 
const button = cva('inline-flex items-center rounded font-medium', {
  variants: {
    size: {
      sm: 'h-8 px-3 text-xs',
      md: 'h-9 px-4 text-sm',
      lg: 'h-10 px-6 text-base',
    },
    variant: {
      primary: 'bg-primary text-primary-foreground',
      secondary: 'bg-secondary text-secondary-foreground',
      ghost: 'hover:bg-accent hover:text-accent-foreground',
    },
  },
  defaultVariants: { size: 'md', variant: 'primary' },
})
 
button({ size: 'lg', variant: 'ghost' })
// => "inline-flex items-center rounded font-medium h-10 px-6 text-base hover:bg-accent hover:text-accent-foreground"

Currently benchmarked at ~7x faster than CVA with full TypeScript inference.

@gentleduck/hooks

Purpose-built React hooks for real-world problems:

HookPurpose
use-debounceDebounce values for search inputs
use-composed-refsMerge multiple refs into one
use-media-queryResponsive queries in code
use-is-mobileMobile detection
use-copy-to-clipboardClipboard API
use-stable-idSSR-safe ID generation
use-on-open-changeModal state management

Each hook is independently importable. No monolithic import required.


Behavior Layer

@gentleduck/primitives

Unstyled, accessibility-first component primitives. These handle the hard parts -- focus trapping, keyboard navigation, ARIA attributes, click-outside dismissal -- so you bring only your design.

Includes primitives for:

  • Dialog, Drawer, Sheet -- Modal and dismissible surfaces
  • Popover, Tooltip -- Positioned floating elements (built on Floating UI)
  • Slider -- Range input with full keyboard support
  • Navigation Menu -- Accessible menu navigation patterns

Every component in the registry-ui-duckui package is built on these primitives. If you want to build your own design system, start here.

@gentleduck/motion

Animation and transition utilities designed to pair with duck-variants. Handles enter/exit animations, mount/unmount transitions, and staggered sequences.

Motion primitives are used by Dialog, Sheet, Drawer, Tooltip, Popover, and Dropdown Menu for their open/close animations.

@gentleduck/vim

Keyboard navigation engine inspired by vim's modal editing. Powers the command palette, menu navigation, and keyboard-driven interfaces across duck-ui.


How They Compose

The power of the ecosystem is in composition. Here is how a single component -- Button -- uses the stack:

Loading diagram...

A more complex component like Command composes multiple layers:

Loading diagram...

Each layer is independently useful, but the composition patterns are where the ecosystem creates the most value.


Adopting Incrementally

You do not need to adopt everything. Each package is published independently on npm:

# Just need class name generation?
bun add @gentleduck/variants
 
# Just need hooks?
bun add @gentleduck/hooks
 
# Want headless primitives?
bun add @gentleduck/primitives
 
# Want the full component library?
bunx @gentleduck/cli init button dialog input
# Just need class name generation?
bun add @gentleduck/variants
 
# Just need hooks?
bun add @gentleduck/hooks
 
# Want headless primitives?
bun add @gentleduck/primitives
 
# Want the full component library?
bunx @gentleduck/cli init button dialog input

Start with what solves your biggest pain point. Expand as your needs grow.