Primitives Placement Analysis
Decision (2026-03-19): Option A accepted. Stock shadcn/ui components go into
src/components/canary/primitives/as a peer ofatoms/. Custom components become first-class atoms undersrc/components/canary/atoms/.
Question: Should stock shadcn/ui components go into src/components/canary/primitives/ (peer of atoms) or src/components/canary/atoms/ (alongside first-class atoms)?
Context
Section titled “Context”The callil consolidation introduces 20 files in src/components/ui/ that need to be relocated under the canary namespace. These fall into two categories:
Stock shadcn (13 files) — thin styled wrappers around Radix UI primitives, regenerable via shadcn CLI: collapsible, dropdown-menu, input, label, separator, sheet, sidebar, skeleton, table, tabs, textarea, toggle, tooltip
Note: sonner.tsx was originally in this list (14 files) but is excluded — it is dropped per the styles analysis decision (dead code coupling to next-themes).
Custom components (~6 files) — contain meaningful Arda-specific logic or extended variants: avatar (size variants, badge, group), badge (ghost/link variants), button (icon sizes, OKLch colors), dialog (showCloseButton), input-group (entirely original), card (CardAction)
The custom components will become first-class canary atoms (with stories, tests, StaticConfig/RuntimeConfig split). This analysis concerns only the stock ones.
Additionally, the callil branch already has canary atoms (ArdaBadge, ArdaButton, ArdaDrawer) that wrap stock shadcn components, establishing a two-tier pattern:
- Foundation layer: stock shadcn (currently
@/components/ui/*) - Atom layer: Arda-branded wrappers (currently
@/components/canary/atoms/*)
Options
Section titled “Options”Option A: canary/primitives/ — peer of atoms/molecules/organisms
Section titled “Option A: canary/primitives/ — peer of atoms/molecules/organisms”src/components/canary/ primitives/ ← stock shadcn atoms/ ← Arda atoms (may wrap primitives) molecules/ organisms/Storybook sidebar: Would appear as a separate top-level group if stories are added.
Option B: canary/atoms/primitives/ — subdirectory within atoms
Section titled “Option B: canary/atoms/primitives/ — subdirectory within atoms”src/components/canary/ atoms/ primitives/ ← stock shadcn badge/ ← Arda atoms button/ ... molecules/ organisms/Storybook sidebar: Canary > Atoms > Primitives > Sheet (if stories added).
Option C: Flat in canary/atoms/ — no separation
Section titled “Option C: Flat in canary/atoms/ — no separation”src/components/canary/ atoms/ badge/ ← Arda atom button/ ← Arda atom sheet/ ← stock shadcn (mixed in) tooltip/ ← stock shadcn (mixed in) ... molecules/ organisms/Storybook sidebar: All at same level under Atoms.
Analysis
Section titled “Analysis”Atomic Design Theory
Section titled “Atomic Design Theory”Brad Frost’s atomic design defines five levels: atoms, molecules, organisms, templates, pages. Atoms are “the smallest functional units” — basic HTML elements like buttons, inputs, labels. Below atoms are “sub-atomic particles” (design tokens: colors, spacing, typography) which are not functional on their own.
Stock shadcn components are functional — a Sheet or DropdownMenu works on its own. By Frost’s definition, they ARE atoms. The question is whether the canary design system treats them as first-class atoms or as a vendor dependency that atoms consume.
Storybook Community Practice
Section titled “Storybook Community Practice”The dominant Storybook pattern organizes by atomic level with / separators in story titles: Atoms/Button, Molecules/DataGrid, etc. There is no standard “primitives” tier. Teams that add extra tiers (primitives, quarks, ions) sometimes create confusion about where new components belong.
However, mature design systems commonly separate their vendor/foundation layer. Material UI has “Base UI” (unstyled primitives). Chakra has its headless primitives. shadcn/ui itself positions as a foundation layer that teams customize on top of.
Key Considerations for This Project
Section titled “Key Considerations for This Project”-
Stock primitives should NOT have their own stories. They’re documented by shadcn itself. The Arda atoms that wrap them (ArdaBadge, ArdaDrawer, etc.) are where stories, tests, and documentation belong.
-
Stock primitives should be clearly identifiable as vendor/regenerable. A developer should know at a glance that
sheet.tsxis stock shadcn and can be regenerated, whilebutton.tsxin atoms is Arda-custom. -
Import path clarity matters. When reading an Arda atom that imports from
primitives/, the dependency direction is obvious: atom wraps primitive. When both live flat inatoms/, the relationship is opaque. -
The existing canary structure has no precedent. Today
canary/atoms/contains only grid cell types anddetail-field. There’s no prior convention to break. -
Storybook stories glob (
../src/components/**/*.stories.@(ts|tsx)) will pick up stories from any subdirectory, so placement doesn’t affect discoverability — it only affects sidebar organization via storytitle.
Risk of Each Option
Section titled “Risk of Each Option”| Risk | Option A (peer) | Option B (subdirectory) | Option C (flat) |
|---|---|---|---|
| Breaks atomic design convention | Moderate — adds a 4th tier | Low — stays within atoms | None |
| Vendor code mixed with custom | None | Low | High — hard to distinguish |
| Import path confusion | None | None | High — atoms/sheet looks like Arda code |
| Storybook sidebar clutter | None (no stories) | None (no stories) | Moderate — 14 undocumented components |
| Future shadcn CLI regeneration | Easy — clear directory | Easy — clear directory | Hard — mixed with custom code |
| Developer cognitive load | Low — clear separation | Low — clear nesting | Moderate — must know which are stock |
Recommendation
Section titled “Recommendation”Option A: canary/primitives/ as a peer of atoms/, with one adjustment.
Rationale
Section titled “Rationale”-
Clear vendor boundary. The
primitives/directory is an explicit declaration: “these are stock shadcn, regenerable, no custom logic.” This is the most important property for maintainability. -
No stories needed. Primitives don’t appear in the Storybook sidebar at all. They’re internal building blocks consumed by atoms. This avoids sidebar clutter and the question of “where do I document Sheet vs ArdaDrawer?”
-
Natural dependency direction. Atoms import from primitives (never the reverse). Molecules import from atoms and primitives. This is clean and auditable.
-
shadcn CLI compatibility. A dedicated directory makes it trivial to point the shadcn CLI at
canary/primitives/for regeneration or updates, without risking overwrites of custom code. -
Atomic design alignment. Brad Frost explicitly acknowledges sub-atomic particles (design tokens). Primitives occupy a similar conceptual space — they’re the styled foundation that atoms are composed from. The analogy holds: tokens are to CSS what primitives are to React components.
-
Pragmatism over dogma. The 2025 community consensus (Mykola Aleksandrov, Qt blog) explicitly warns against forcing every component into a rigid “chemistry box.” The model is a guide, not a religion. A
primitives/tier that serves a clear purpose (vendor isolation) is justified.
Why not Option B?
Section titled “Why not Option B?”Option B (subdirectory) is a close second. The main disadvantage: atoms/primitives/sheet implies Sheet is a kind of atom. It’s technically true, but muddies the distinction between “vendor foundation” and “Arda-designed atom.” When a developer sees atoms/button/ and atoms/primitives/sheet/ side by side, the relationship isn’t immediately clear. Option A makes the separation explicit at the directory level.
Why not Option C?
Section titled “Why not Option C?”Option C creates a maintenance burden. Without a directory boundary, developers must know which components are stock shadcn and which are custom. The shadcn CLI can’t safely regenerate components mixed with custom code. And the Storybook sidebar would show 14 undocumented primitives alongside documented Arda atoms.
Proposed Structure
Section titled “Proposed Structure”src/components/canary/ primitives/ ← Stock shadcn (no stories, no tests) — 13 files collapsible.tsx dropdown-menu.tsx input.tsx label.tsx separator.tsx sheet.tsx sidebar.tsx skeleton.tsx table.tsx tabs.tsx textarea.tsx toggle.tsx tooltip.tsx atoms/ ← Arda first-class atoms (stories + tests) avatar/ ← Promoted from ui/ (custom: size variants, badge, group) badge/ ← Promoted from ui/ (custom: ghost/link variants) button/ ← Promoted from ui/ (custom: icon sizes, OKLch) card/ ← Promoted from ui/ (custom: CardAction) dialog/ ← Promoted from ui/ (custom: showCloseButton) drawer/ ← Existing callil atom (wraps primitives/sheet) input-group/ ← Promoted from ui/ (entirely original) brand-logo/ ← Existing callil atom icon-button/ ← Existing callil atom icon-label/ ← Existing callil atom read-only-field/ ← Existing callil atom search-input/ ← Existing callil atom detail-field/ ← Existing jmpicnic atom grid/ ← Existing jmpicnic atom group molecules/ organisms/Import Convention
Section titled “Import Convention”// Primitives — internal use only, not exported from packageimport { Sheet, SheetContent } from '@/components/canary/primitives/sheet';
// Atoms — public API, exported from canary.ts barrelimport { ArdaDrawer } from '@/components/canary/atoms/drawer';Sources
Section titled “Sources”- Brad Frost — Atomic Design Methodology
- Brad Frost — Extending Atomic Design
- From Atomic to Subatomic — Brad Frost on Design Tokens
- Storybook — Structuring Your Storybook
- Atomic Design in Practice (2025) — Mykola Aleksandrov
- Atomic Design Systems: Why the Labels Don’t Matter — Qt
- shadcn/ui — The Foundation for Your Design System
- The Anatomy of shadcn/ui
- Design System in React with Tailwind, shadcn/ui and Storybook
Organism and Molecule Import Mapping
Section titled “Organism and Molecule Import Mapping”This section maps every @/components/ui/* import in the callil organisms and molecules to its
target location after consolidation (primitive or atom).
Legend
Section titled “Legend”| Marker | Meaning |
|---|---|
primitives/ | Stock shadcn — moves to src/components/canary/primitives/ |
atoms/ | Custom component — moves to src/components/canary/atoms/ |
Organisms
Section titled “Organisms”organisms/sidebar/sidebar.tsx
Section titled “organisms/sidebar/sidebar.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/sidebar | SidebarProvider, Sidebar, SidebarRail | primitives/sidebar |
organisms/app-header/app-header.tsx
Section titled “organisms/app-header/app-header.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/button | Button | atoms/button |
@/components/ui/separator | Separator | primitives/separator |
(Also imports atoms/search-input and atoms/icon-button — already canary atoms, no change.)
organisms/item-details/item-details.tsx
Section titled “organisms/item-details/item-details.tsx”No direct @/components/ui/* imports. Composes via canary atoms and molecules exclusively:
atoms/drawer, atoms/button, molecules/item-details/*, molecules/field-list/*, molecules/action-toolbar.
organisms/item-grid/item-grid.tsx
Section titled “organisms/item-grid/item-grid.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/input | Input | primitives/input |
Molecules
Section titled “Molecules”molecules/sidebar/sidebar-nav-item.tsx
Section titled “molecules/sidebar/sidebar-nav-item.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/sidebar | SidebarMenuItem, SidebarMenuButton | primitives/sidebar |
(Also imports atoms/badge — already a canary atom.)
molecules/sidebar/sidebar-user-menu.tsx
Section titled “molecules/sidebar/sidebar-user-menu.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/avatar | Avatar, AvatarFallback, AvatarImage | atoms/avatar |
@/components/ui/dropdown-menu | DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger | primitives/dropdown-menu |
@/components/ui/sidebar | SidebarFooter, SidebarMenu, SidebarMenuItem, SidebarMenuButton | primitives/sidebar |
molecules/sidebar/sidebar-nav.tsx
Section titled “molecules/sidebar/sidebar-nav.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/sidebar | SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu | primitives/sidebar |
molecules/sidebar/sidebar-nav-group.tsx
Section titled “molecules/sidebar/sidebar-nav-group.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/collapsible | Collapsible, CollapsibleContent, CollapsibleTrigger | primitives/collapsible |
@/components/ui/sidebar | SidebarMenuItem, SidebarMenuButton, SidebarMenuSub | primitives/sidebar |
molecules/sidebar/sidebar-header.tsx
Section titled “molecules/sidebar/sidebar-header.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/sidebar | SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton | primitives/sidebar |
@/components/ui/dropdown-menu | DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger | primitives/dropdown-menu |
(Also imports atoms/brand-logo — already a canary atom.)
molecules/item-details/item-details-header.tsx
Section titled “molecules/item-details/item-details-header.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/tabs | Tabs, TabsList, TabsTrigger | primitives/tabs |
@/components/ui/dropdown-menu | DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger | primitives/dropdown-menu |
molecules/action-toolbar/action-toolbar.tsx
Section titled “molecules/action-toolbar/action-toolbar.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/button | Button | atoms/button |
@/components/ui/dropdown-menu | DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger | primitives/dropdown-menu |
molecules/overflow-toolbar/overflow-toolbar.tsx
Section titled “molecules/overflow-toolbar/overflow-toolbar.tsx”| Current import | Exported names | Target |
|---|---|---|
@/components/ui/button | Button | atoms/button |
@/components/ui/dropdown-menu | DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger | primitives/dropdown-menu |
Summary — unique @/components/ui/* modules referenced across all organisms and molecules
Section titled “Summary — unique @/components/ui/* modules referenced across all organisms and molecules”ui/ module | Target tier | Files that import it |
|---|---|---|
sidebar | primitives/sidebar | organism/sidebar, mol/sidebar-nav-item, mol/sidebar-user-menu, mol/sidebar-nav, mol/sidebar-nav-group, mol/sidebar-header |
dropdown-menu | primitives/dropdown-menu | mol/sidebar-user-menu, mol/sidebar-header, mol/item-details-header, mol/action-toolbar, mol/overflow-toolbar |
button | atoms/button | organism/app-header, mol/action-toolbar, mol/overflow-toolbar |
separator | primitives/separator | organism/app-header |
input | primitives/input | organism/item-grid |
tabs | primitives/tabs | mol/item-details-header |
collapsible | primitives/collapsible | mol/sidebar-nav-group |
avatar | atoms/avatar | mol/sidebar-user-menu |
Remaining Callil Organisms Integration
Section titled “Remaining Callil Organisms Integration”The four organisms below exist in the callil branch (callil-consolidation-worktree) but have not
yet been moved into the main clone’s src/components/canary/organisms/ directory.
| Organism | Callil source path |
|---|---|
ArdaSidebar | organisms/sidebar/sidebar.tsx |
ArdaAppHeader | organisms/app-header/app-header.tsx |
ArdaItemDetails | organisms/item-details/item-details.tsx |
ItemGrid | organisms/item-grid/item-grid.tsx |
Each organism must be moved (not copied) as part of the consolidation work. The migration steps for each file are:
- Copy the file into
src/components/canary/organisms/<name>/in the main clone. - Rewrite all
@/components/ui/*imports to their target locations per the mapping table above:@/components/ui/sidebar→@/components/canary/primitives/sidebar@/components/ui/dropdown-menu→@/components/canary/primitives/dropdown-menu@/components/ui/button→@/components/canary/atoms/button@/components/ui/separator→@/components/canary/primitives/separator@/components/ui/input→@/components/canary/primitives/input@/components/ui/tabs→@/components/canary/primitives/tabs@/components/ui/collapsible→@/components/canary/primitives/collapsible@/components/ui/avatar→@/components/canary/atoms/avatar
- Rewrite relative imports of sibling canary atoms/molecules to the correct relative paths in the main clone’s directory tree.
- Add/update the barrel export in
src/components/canary/organisms/index.ts. - Verify TypeScript compiles (
npx tsc --noEmit) before opening a PR.
The same import-rewrite obligation applies to all molecule files that accompany these organisms
(the full molecules/sidebar/ set plus molecules/item-details/item-details-header.tsx,
molecules/action-toolbar/action-toolbar.tsx, and molecules/overflow-toolbar/overflow-toolbar.tsx).
Copyright: © Arda Systems 2025-2026, All rights reserved