跳转到内容

UI components

Taxonomy and placement rules for Portal UI. Implementation lives under src/ui-kit/ and src/features/*/ui/. Agent import bans → .agent/rules/frontend-ui.md and .agent/rules/frontend-features.md.

Visual tokens → foundations.md. Dimension axes → dimensions.md.

Spoken (product/design)Repository termTypical home
Non-business / design-system UIPrimitive, structure, composite@freeanima/ui-kit
Business / product UIDomain UIsrc/features/<slug>/ui
UI componentA React building block at one of the layers above
UI element / slot piecePresentational part inside a pattern (title text, badge, icon) — not its own packageInside a component or pattern
App chromeapp frame (not Shell)src/client/app-frame
HostShellsrc/portal/app/*
LayerRoleLocation
Primitiveshadcn/Radix controls + variants (Button, Dialog, Input, …)ui-kit/components/ui
StructureForm and layout shells (FormFieldset, ListDetailLayout, viewport hooks)ui-kit/form, ui-kit/layout
CompositeCross-feature interaction patterns (ConfirmDialog, ActionSheet, ContextMenu, EmptyState, …)ui-kit/composite
DomainProduct-specific screens and fieldsfeatures/<slug>/ui
app frameModule rail / tabs / settings hostclient/app-frame
ShellPortal host, IPC, bridgeportal/app

Dependency direction: app-framefeatures/*/ui{ui-kit, portal-sdk} → shared. ui-kit must not import Habitat RPC / rpc-contract.

  1. If two features need the same interaction chassis, put the chassis in composite (or structure/layout); keep field copy and domain types in the feature.
  2. Primitives stay dumb: variants and sizes, no feature imports.
  3. Composites take capability props (e.g. useActionSheet: boolean) or call portal-sdk interaction hooks — they must not call getShellKind() to choose ContextMenu vs ActionSheet.
  4. Layout structure components may use layout APIs (useCompactLayout, useDrawerNav, …).
  5. Shell-only concerns stay in portal-sdk / shell host; ui-kit does not export shell detectors.

Pending alignment: Task-oriented views under ui-kit/composite (TaskItemRowView, …) are shared reference implementations. Prefer aligning new list modules to their pattern contract (patterns.md); extracting a generic ListRow is a follow-up task, not a license to fork row behavior.

LayerShell APIsLayout APIsInteraction APIs
PrimitiveNoNoNo (presentation only)
Structure / layoutNoYesRare (e.g. resize handles)
CompositeOnly via injected capability / portal-sdk helpers that hide shellIf the composite is a layout patternYes (menus, long-press, Enter-to-send consumers)
DomainVia portal-sdk when needed for capability UIYes for page chromeYes
app frameCapability visibilityYes (primary)Yes where chrome needs it
Shell hostYesMust not lock app layout from shell kind aloneMust not invent a parallel interaction model
NeedPrefer
Button<Button variant size>
Fields<Input> / <Textarea> / <Select>; no auto-focus by default (focusOnMount when needed)
Dialog / SheetDefault onOpenAutoFocus blocked; override only when intentional
Loading<Spinner>
Empty<EmptyState>
Status<StatusAlert>
Confirm<ConfirmDialog> / showConfirm — never window.confirm

Full agent checklist → .agent/rules/frontend-ui.md.

LensRule
InvariantLayer placement and import boundaries
LayoutStructure components switch List-Detail / Modal-Sheet presentation; domain fills slots
InteractionComposites branch on capability flags; shared menu item data (ActionSheetItem[])
ShellCapability availability only; never “Tauri ⇒ bottom tabs”
ForbiddenDeep-importing feature sources from app-frame; ui-kit importing RPC