Comic-style Component Architecture hero showing primitives, features, routes, app shell, and ownership boundaries.

Component Architecture

Medium•

Component architecture is boundary design: decide what each component owns, what it receives, what it exposes, and what changes should not leak across the tree.

Architecture Is Ownership

The core question is not whether a component is small. The core question is whether its responsibility is coherent. A well-designed component has a clear owner, a clear input contract, and a clear reason to re-render.

Good architecture keeps volatile decisions local. Product copy, feature flags, analytics, permissions, server data, and layout constraints should not leak into generic primitives. When those concerns mix, reuse becomes accidental coupling.

Component Boundary Model

Strong components separate visual behavior, feature workflow, route orchestration, and application policy. Each layer can depend inward on simpler layers, but reusable layers should not depend outward on product-specific context.

BoundaryOwnsShould avoid
UI primitiveVisual affordance, accessibility behavior, variants, states.Business rules, data fetching, product-specific branching.
Feature componentFeature flow, local interactions, composition of primitives.Global app policy and unrelated feature state.
Route or screenData requirements, layout, loading/error states, navigation context.Low-level button/input behavior and reusable domain primitives.
Application shellProviders, routing, auth/session context, global layout, cross-cutting concerns.Feature-specific UI decisions and domain workflows.

Patterns and Trade-offs

Patterns are tools for managing change. The wrong pattern adds indirection; the right one makes ownership obvious.

Container + Presentational

Use when

Data ownership and view rendering need to be separated for testing or reuse.

Trade-off

Can become ceremony if hooks already isolate the side effects cleanly.

Compound Components

Use when

A parent and children need coordinated state with a flexible authoring API.

Trade-off

Great for UI libraries, but harder to type and document than plain props.

Custom Hooks

Use when

Reusable stateful logic should be shared without forcing a visual wrapper.

Trade-off

Hooks share behavior, not UI; they still need stable dependencies and ownership boundaries.

Slots / Children Composition

Use when

The parent owns layout but consumers own specific regions of content.

Trade-off

Keeps APIs flexible, but too many slots can make component behavior hard to predict.

File Organization That Scales

Organize by feature when product domains are the primary source of change. Organize shared primitives by capability when consistency is the primary source of value.

Feature-owned code

features/
  checkout/
    components/
    hooks/
    api/
    types.ts
    index.ts

Best when the code changes together and should not become a shared abstraction yet.

Shared platform code

shared/
  ui/
  hooks/
  lib/
  telemetry/
  design-system/

Best when multiple features rely on the same stable contract.

Component API Design

Component APIs should make valid usage easy and invalid states hard to express. The common staff-level smell is boolean prop growth: each new flag seems harmless, but the combinations become impossible to reason about.

Weak API

<Modal
  isCheckout
  isDanger
  showFooter
  showCancel
  primaryLabel="Pay now"
/>

Many booleans create hidden states. Which combinations are supported?

Stronger API

<Dialog variant="danger">
  <Dialog.Header />
  <Dialog.Body />
  <Dialog.Footer />
</Dialog>

Composition exposes structure while keeping behavior owned by the component.

State Placement

State placement is architecture. Lift state only when shared coordination requires it. Moving state upward too early increases render scope, coupling, and accidental global behavior.

AskDecision
Who needs the state?Keep it local when only one component or small subtree uses it.
Is it derived?Compute it from existing data instead of storing a second source of truth.
Does it come from the server?Treat it as server state with caching, invalidation, loading, and error semantics.
Does it coordinate a workflow?Own it at the feature or route boundary so related steps change together.

Architecture Smells

  • A reusable component imports product-specific stores, routes, or analytics events.
  • A component has many boolean props that encode mutually exclusive layouts.
  • Server data is copied into local component state without a clear reason.
  • A shared component knows about one feature's permissions, copy, or workflow.
  • State is lifted globally only because passing props felt inconvenient.
  • Every feature creates its own primitives instead of using the shared design system.

Interview Framing

A strong answer starts from change pressure: what changes often, what must stay stable, and which boundary prevents those changes from spreading.

Senior answer pattern

I would separate reusable UI primitives from feature-owned components, keep server data orchestration near route or feature boundaries, expose narrow component contracts, and introduce shared abstractions only after repeated usage proves the contract is stable. Reuse is useful only when it reduces change cost; premature reuse creates coupling.