1. 09Feature modules are units of change
  2. 10The parts of a feature
  3. 11Making a feature ready to build
  4. 12Building a feature from idea to implementation
  5. 13Testing a feature without the host
  6. 14The application is the composer

Building product features

09

Feature modules are units of change

A feature module gives one product outcome a clear frontend home, separating feature decisions from shared UI infrastructure and production integrations.

By Eyal Ellenbogen August 17, 2026 2 min read

Up to this point, our boundaries have focused entirely on shared UI infrastructure: design tokens, visual primitives, headless interaction behaviors, and third-party configuration presets. These elements provide the shared vocabulary of the interface.

Products, however, are not delivered as isolated buttons or dropdowns. Users navigate invoice approvals, configuration wizards, and permission matrices. Our challenge shifts from building reusable building blocks to assembling them into units of product delivery that can change rapidly without breaking neighboring workflows.

A feature is a piece of product work that changes as one thing. The frontend needs an explicit boundary around that work. A feature module is that boundary: the home for the decisions, state, and UI behavior that deliver a single outcome.

Why feature boundaries exist

Most frontend codebases begin by organizing files by technical role. Every button lives in components/, every API call in services/, and every state slice in store/.

When a product request arrives—say, adding expiration dates to pending member invites—the change fractures across the repository tree:

// Fragmented by technical type:
src/
	components/InviteModal.tsx
	hooks/useInviteState.ts
	services/inviteService.ts
	types/invite.ts

Modifying this workflow means reconstructing relationships across distant directories from memory. In code review, we have to verify that touching inviteService.ts did not accidentally break an unrelated route.

Grouping code by technical type optimizes for the day files are created. Grouping code by feature module optimizes for every subsequent change:

// Cohesive unit of change:
src/features/member-invitations/
	InviteDialogView.tsx
	invitationWorkflow.ts
	invitationAdapter.ts
	invitation.types.ts

All the decisions that change together live together. The change boundary matches the product ticket: one directory to edit, test, and review.

The change rule

If a product requirement changes only the invitation workflow, the pull request should only touch files inside the invitation feature module. If an edit spills into shared folders, the boundary is leaking.

The module as a collaboration surface

A feature module rarely consists of a single component. It routinely contains multiple sub-views, dialogs, and local state machines. Each unit inside the module still honors clear single responsibilities: presentation components own visual hierarchy, while local controllers or state machines handle workflow rules.

The module is where those pieces collaborate. It gives one product outcome an unmistakable address in the codebase, turning a loosely coupled set of views and state machines into an isolated unit of delivery.