Skip to content
ThorbisInterface Guidelines (opens in a new tab)

Composition

One focused chapter from the Thorbis interface research. 8 rules. Read the research index for every chapter and focused study.

Composition

Boolean props create exponential states. Compose the pieces a surface actually needs. Lift state so siblings outside the frame can still act. This is Vercel’s React composition bench, the primitives we ship are Base UI , shadcn is a copy-in starting point.
  • Don’t proliferate boolean props. isThread, isEditing, isInvoice each double the state space and hide impossible combinations. Build JobNoteComposer, DispatchThreadComposer, and EditInvoiceComposer instead. Share internals; never share one monolithic parent.
  • Compound components over render props. Export Composer.Provider, Frame, Input, Footer, Submit. Each piece reads shared state from context. Callers compose exactly the chrome they need, no showAttachments flags, no renderFooter callbacks for static structure.
  • Name the variant. <ThreadComposer jobId="…" /> documents itself. <Composer isThread channelId="…" showFormatting={false} /> does not. Each variant is explicit about its provider, its pieces, and its actions.
  • Prefer children for structure. Use children to compose layout. Reach for a render prop only when the parent must pass data back, a list’s renderItem, not a composer’s header.
  • Lift state into a provider. A Forward / Peek / Dialog action that lives outside the composer frame still needs submit and the draft. Put state in a provider. Visual nesting is not the seam, the provider boundary is.
  • Context is a contract: state, actions, meta. Any provider that implements the same interface can drive the same UI. Local useState for an ephemeral forward. Synced store for a live job thread. The input does not know which.
  • UI does not know how state is stored. The provider is the only module that imports Zustand, a server sync, or useState. Pieces consume the context interface. Swap the provider, keep the pieces.
  • React 19: ref is a prop, use() is context. Do not wrap with forwardRef. Read context with use(), which may be called conditionally. Skip this if a surface is still on React 18.

I built this for the dispatcher at 7 a.m., the tech in a crawlspace, and the owner closing the books. Quiet chrome. Precise work. I’m still learning. These are the notes.

Applied bookmark research

Reviewed 2026-09-06. These examples reinforce the existing owners; they do not introduce another visual style or claim the product behavior is implemented. Read the 26-source review and access limits.

Make invalid combinations difficult to express

Independent loading, success, and error flags can contradict each other; presentation and business logic become tangled as a component grows.

Choose an explicit state model and a semantic composition API. Reuse the approved primitives, keep domain operations outside them, and locate loading boundaries according to the work they protect. Documentation names which variants actually ship.

Thorbis example. A payment dialog composes an approved trigger, content, fields, and actions around a domain-owned operation. The button receives one coherent request state, not several competing booleans and a private set of colors.

Verify. Exercise each valid state and transition, keyboard and focus behavior, controlled and uncontrolled contracts where supported, server-rendered output, and slow or missing data. Check provenance and license before source adoption.

Do not import. No arbitrary color escape hatch, duplicate component kit, unregistered dependency, or migration from Astro merely because a source uses RSC.

Open the composition skill · Evidence: React handbook; components.build; Component Architecture for React Server Components; shadcncraft

Loading search…

Loading search. You can close this message and keep reading.