Skip to content

Interaction patterns

Reusable interaction contracts so modules (task, chat, email, …) share one UX language. Prefer existing @freeanima/ui-kit/composite implementations over parallel menus, rows, or confirms.

Dimensions → dimensions.md. Components → components.md. Visuals → foundations.md.

Every pattern documents:

  1. Intent — when to use
  2. Slots — required/optional UI parts
  3. States — default, hover, selected, active, dragging, selectionMode, disabled, …
  4. Dimension adaptation — invariant / layout / interaction / shell (N/A if none)
  5. Implementation — code entry (or “pending extraction”)
  6. Forbidden — anti-patterns
  7. Compliance — reference / pending extraction / pending alignment

Intent: Primary row in a data list (tasks, mail threads, …): title, actions, selection, optional drag, overflow menu.

Slots:

SlotRole
Leading controlComplete checkbox, or selection glyph in selectionMode
TitlePrimary truncated text
Secondary line / tags / metaOptional muted line, tags, due date, entity id
Persistent actionsAlways-visible controls needed without hover (e.g. touch ⋯)
Hover actionsPointer-only revealed controls (group-hover) when used
Overflow menu sourceShared ActionSheetItem[] for ContextMenu and ActionSheet
Drag handlePrefer whole-row drag listeners; no separate handle unless required

States:

StateVisual / behavior
defaultRow chrome; min-h-11
hoverhover:bg-muted (pointer); must not be the only way to discover actions on touch
activeCurrent detail target (subtle ring/bg) when not selected
selectedStronger primary tint + ring; used in multi-select
selectionModeCheckbox → selection glyph; click toggles; disable drag/menus
draggingReduced opacity; grab cursor when draggable
disabledControls disabled; no drag

Dimension adaptation:

LensAdaptation
InvariantSame slot contract across modules; do not ship a second row component per breakpoint
LayoutOuter List-Detail / columns change; row slots stay the same in compact and expanded
Interactionpointer: hover bg; right-click ContextMenu when enabled; optional hover-revealed actions. touch: no hover-only actions; persistent ⋯ (or equivalent); long-press → ActionSheet; hit targets ≥44px
ShellN/A
ForbiddengetShellKind() to choose menu type; viewport width to choose ContextMenu vs ActionSheet; custom fixed coordinate menus

Implementation: Reference — TaskItemRowView.tsx (+ list wrapper TaskItemListView). Capability flags: useActionSheet, contextMenuEnabled, drag attrs/listeners from parent.

Compliance: Reference implementation (task-shaped). Pending extraction: domain-agnostic ListRow chassis. Pending alignment: other modules’ custom rows that diverge on menus/selection/drag.


Intent: Secondary actions on an object without cluttering the row.

Slots: Item list (ActionSheetItem[]: label, action, destructive flag, …); optional trigger (⋯).

States: Closed / open; destructive items styled distinctly; disabled when selectionMode or row disabled.

Dimension adaptation:

LensAdaptation
InvariantOne item builder; both surfaces consume the same array
LayoutActionSheet may present as bottom sheet; ContextMenu is anchored — presentation details follow primitives
Interactionpointer: ContextMenu. touch: ActionSheet + long-press and/or ⋯. Separate state machines; shared session data (target + items)
ShellN/A
ForbiddenParallel hand-rolled menus; window.confirm inside menu actions for irreversible deletes (use ConfirmDestructive)

Implementation: @freeanima/ui-kit/compositeContextMenu, ActionSheet, useLongPress.

Compliance: Reference.


Intent: Confirm irreversible or high-risk actions (delete, purge).

Slots: Title, description, cancel, confirm (destructive/error variant).

States: Open/closed; confirm button must not be default-focused or “easy Enter” default.

Dimension adaptation:

LensAdaptation
InvariantSecond confirmation for irreversible delete; showConfirm / ConfirmDialog
LayoutDialog vs Sheet presentation may follow ModalSheetPresent
InteractionSame confirm semantics on pointer and touch
ShellN/A
Forbiddenwindow.confirm; autoFocus on confirm

Implementation: ConfirmDialog, showConfirm from @freeanima/ui-kit/composite.

Compliance: Reference.


Intent: Modal content presentation that tracks layout, not shell.

Slots: Title, body, footer actions; optional close.

Dimension adaptation:

LensAdaptation
InvariantSame content model
Layoutexpanded: centered Dialog. compact: bottom Sheet / sheet-like surface
InteractionOpening gesture is separate (button vs long-press); do not conflate with presentation
ShellN/A
ForbiddengetShellKind() === "tauri" ⇒ Sheet

Implementation: shadcn Dialog / Sheet; pickers such as MoveToListPicker follow the same idea.

Compliance: Reference (with pending alignment on ad-hoc z-index values).


Document when stabilizing a second consumer:

  • QuickAddBar
  • PullToRefresh (see also page refresh — pointer header button vs touch pull)
  • ListDetail / ThreeColumn page chassis
  • EmptyState / StatusAlert surfaces as full pattern cards
  • ModuleScopeBar / DetailPanelShell
  • Chose a pattern (or extended the catalog) instead of inventing a parallel gesture
  • Documented dimension adaptation (layout + interaction at minimum)
  • Menus share one item list; no fixed-position DIY menus
  • Irreversible actions use ConfirmDestructive
  • Touch paths do not depend on hover-only affordances
  • Shell kind does not lock layout or menu type