Lesson 1: Why Primitives
Understand the real engineering cost of interaction components and why headless primitives are a strategic foundation.
Lesson 1 of 10: This lesson frames primitives as an engineering strategy, not a UI trend.
The actual problem
Teams usually underestimate overlay and selection components because demos look simple. In production, each component requires:
- Keyboard interaction contracts.
- Screen reader semantics.
- Focus containment and restoration.
- Layering and stacking rules.
- Dismiss behavior under nested surfaces.
- Motion that does not break unmount timing.
Building this from scratch once is expensive. Maintaining it across product growth is far more expensive.
Complexity profile: Dialog as an example
A production dialog is not "render a box in the center". It includes:
- Trigger semantics (
aria-haspopup,aria-expanded,aria-controls). - Content semantics (
role="dialog", labelled and described connections). - Focus trap and tab loop.
- Focus restore after close.
- Body scroll lock.
- Escape and outside interaction handling.
- Hidden background semantics for assistive tech.
- Exit animation support before unmount.
If any one of these fails, users notice quickly.
Why headless primitives exist
Headless primitives separate concerns cleanly:
- Primitive layer: behavior, accessibility, interaction lifecycle.
- Design-system layer: visual language, motion style, spacing/tokens.
- App layer: business flows and feature logic.
This separation lets teams move faster without breaking interaction correctness.
Build-vs-wrap decision
Use primitives when:
- You need consistent interaction behavior across many teams.
- You need custom UI style with no framework lock-in.
- You care about long-term maintainability and accessibility quality.
Use pre-styled components when:
- You need speed for a small app with low customization needs.
- Interaction edge cases are minimal and design constraints are fixed.
The hidden risk of ad hoc components
Without primitives, teams often copy-paste component code and drift begins:
- Slightly different keyboard behavior per surface.
- Inconsistent ARIA labeling patterns.
- Close behavior differences across dialogs/popovers.
- Animation races that cause focus loss.
Drift multiplies QA effort and makes regressions harder to diagnose.
What gentleduck/primitives gives you
- Accessibility patterns implemented once and reused.
- Compound APIs that support composition without hacks.
- Controlled and uncontrolled state support.
- Data attributes for styling state transitions.
- Infra primitives (
Presence,Dismissable Layer,Focus Scope,Popper) you can use directly.
Mental model for this course
Treat primitives like low-level platform APIs:
- You do not redesign their behavioral contract.
- You do wrap them with your design-system conventions.
- You do test wrapper behavior in realistic workflows.
Lab
Build a simple comparison in your playground:
- One dialog built with raw
divlogic. - One dialog built with
Dialogprimitive. - Test both with keyboard and screen reader.
Write down which edge cases broke in the raw version. That list is your adoption justification.