← Software design practices guides

How to keep UI components focused

How-To Software design practices Intermediate 1101004HOWTO-1101004

HOWTO-1101004Software design practicesIntermediate

This guide shows you how to keep UI components focused with SpecDD in a spec-driven development workflow.

UI components often become the easiest place to put everything: formatting, validation, data fetching, permission checks, persistence, analytics, and business decisions. The component still renders, but it also becomes a hidden domain service and a hidden adapter. That makes the component harder to test and harder to reuse.

A component spec keeps the boundary visible. It says what the component renders, what interaction it owns, what state it manages locally, and which decisions belong somewhere else.

Short answer

Write a component spec for one UI component or a small cohesive component group. Use Purpose for the visible responsibility, Owns for component and test files, Must for rendering and interaction behavior, Must not for domain, persistence, and cross-layer responsibilities, and Scenario for important user flows. Use Depends on or References for services, models, or specs the component relies on without owning.

When to use this guide

Use this guide when:

The design idea

A focused component owns presentation and interaction. It may collect input, show state, call a handler or service, display returned errors, and preserve accessibility behavior. It should not own durable domain rules unless the component is truly the product surface for that rule.

This distinction matters because UI changes are frequent. If every UI change risks changing validation, persistence, or authorization, the system becomes brittle. A component spec lets reviewers ask whether a diff changed UI behavior or quietly moved business behavior into the UI layer.

Steps

1. Choose the component boundary

Choose one component or a tightly related group:

Spec: Itinerary Form

Avoid vague subjects:

Spec: Trip UI

If a screen contains several independent concerns, write smaller specs for the important component boundaries.

2. Write a UI-focused purpose

Good:

Purpose:
  Capture itinerary item input and show validation feedback.

Weak:

Purpose:
  Manage itinerary creation, validation, saving, and search.

The weak purpose turns the component into a workflow owner. The stronger purpose keeps the component focused on UI interaction.

3. Specify visible behavior

Use Must for behavior the user can observe or interact with:

Must:
  Render place name and trip day inputs.
  Submit place name and trip day to itinerary behavior.
  Show validation feedback returned by itinerary behavior.
  Keep keyboard focus on the first invalid field after validation fails.

These rules describe component behavior without making the component own validation rules.

4. Separate domain decisions

Use Must not for domain behavior that should stay elsewhere:

Must not:
  Decide whether an itinerary item is valid.
  Decide whether the user may edit the trip.
  Reorder itinerary items after save.

If the component needs validation feedback, it can depend on the service or behavior that returns it:

Depends on:
  ItineraryBehavior

The component may display the result. It should not duplicate the rule.

5. Block persistence and external access

Use Forbids when the component must not directly access lower layers:

Forbids:
  ../adapters/*
  direct browser storage access
  direct booking API access

This protects the component from becoming a persistence boundary.

6. Use scenarios for user interaction

Scenarios make component behavior concrete:

Scenario: validation feedback
  Given the place name is empty
  When the person submits the itinerary form
  Then the form shows the validation message returned by itinerary behavior
  And focus moves to the place name input

This scenario is UI-specific. The validation rule itself should still belong to the domain or service spec that owns it.

7. Keep tasks UI-local

Good:

Tasks:
  [ ] Show returned validation feedback beside the place name input.

Wrong for a component spec:

Tasks:
  [ ] Add missing-place validation to the itinerary domain.

That task belongs in the validation or domain spec.

Full example

Spec: Itinerary Form

Purpose:
  Capture itinerary item input and show validation feedback.

Owns:
  ./itinerary-form.jsx
  ./itinerary-form.test.jsx

Must:
  Render place name and trip day inputs.
  Submit place name and trip day to itinerary behavior.
  Show validation feedback returned by itinerary behavior.
  Keep keyboard focus on the first invalid field after validation fails.

Must not:
  Decide whether an itinerary item is valid.
  Persist itinerary items directly.
  Fetch destination search results directly.

Depends on:
  ItineraryBehavior

Forbids:
  ../adapters/*
  direct browser storage access

Scenario: validation feedback
  Given the place name is empty
  When the person submits the itinerary form
  Then the form shows the validation message returned by itinerary behavior
  And focus moves to the place name input

Done when:
  Validation feedback rendering is covered by a component check.
  The component does not import storage adapters.

This spec lets the component be rich and useful without becoming the owner of every trip rule.

Common mistakes

How to verify the result

The component is focused when:

← Software design practices guides