Skip to content

Front End Components

Use this page when a frontend change is about rendering structure, reusable UI behavior, page/domain adaptation, workflow sequencing, or component copy.

Use Front End State when the main question is where state should live, how it should serialize, or how URL, saved-view, query-cache, workflow draft, and media draft state relate.

Reusable components should be generic enough to survive outside their first Wavemap screen.

Good reusable component responsibilities:

  • Input wiring and controlled/uncontrolled value behavior.
  • Popover, sheet, dialog, carousel, table, upload, pagination, and selection mechanics.
  • Keyboard and accessibility behavior.
  • Stable sizing, empty/loading/error states, and option rendering hooks.
  • English defaults when the component is useful outside a localized Wavemap surface.
  • Grouped labels props when the component owns internal visible copy across several subcomponents.

Avoid giving reusable components responsibility for:

  • Fetching domain data.
  • Knowing API DTO shapes.
  • Importing Wavemap translation namespaces or calling t().
  • Owning page URL state.
  • Applying route permissions.
  • Choosing page-specific toast, form, or navigation side effects.

Examples of this split:

  • TypeaheadSearch owns the larger input-plus-results presentation.
  • CompactTypeaheadSearch owns combobox and popover mechanics for smaller search fields.
  • Table, filtering controls, SortAndFilterPanel, Pagination, FileUploadSurface, Carousel, MediaCarousel, Breadcrumbs, and date/time/numeric inputs receive labels and formatters instead of owning Wavemap translation keys.

A good reusable component should be testable with plain props and simple fixtures. For example, a compact typeahead can be tested with three static options, a selected value callback, and custom labels; it should not need an artist API mock just to prove keyboard selection. When a component needs many labels, keep the label-map pattern aligned with i18n Contributor Workflows.

Layout composition components sit between reusable primitives and full page/domain wrappers. They are useful when a page has enough structure to extract, but the extracted structure is still too specific to become a design-system component.

They own:

  • Page section arrangement, responsive grid or stack structure, and repeated page chrome.
  • Placement of prepared cards, panels, forms, sidebars, tabs, headers, toolbars, and empty/error regions.
  • Visual grouping of already-loaded or already-derived domain data.
  • Wiring between generic child components when the wiring is presentational rather than data-fetching.
  • Domain-adjacent names when they make the page easier to read, such as ArtistDetailsHeader or ArtistMediaSection.

Avoid giving layout composition components responsibility for:

  • Fetching data or calling API hooks.
  • Owning URL state, saved-view state, or query cache state.
  • Translating DTOs into view models.
  • Resolving permissions or choosing workflow side effects.
  • Importing page-specific translation namespaces unless the component is intentionally a page-local extraction.
  • Becoming a reusable component library candidate before it has more than one real consumer.

The goal is to keep page files from becoming long render trees without pretending every extracted region is a reusable UI primitive. A page can load data, derive display-ready props, and hand them to layout composition components that arrange the surface. Those layout components then hand generic behavior to reusable children such as tables, typeaheads, carousels, upload surfaces, switches, buttons, and form controls.

Useful examples:

  • ArtistDetailsHeader can arrange the artist title, metadata, status actions, and permission-aware action slots after the page wrapper has already loaded the artist and resolved the allowed actions.
  • ArtistMediaSection can arrange profile image, gallery, empty media state, and edit affordances while still delegating carousel, upload, and button mechanics to reusable components.
  • RelationshipAssignmentLayout can arrange selected candidates, search results, pagination, and footer actions while a page or workflow hook owns the assignment draft and persistence sequence.
  • SearchResultsFrame can arrange filter chrome, result count, view switcher, table/gallery regions, and empty states while the page frame owns the query state and result mapping.

Page/domain wrappers should adapt reusable components and layout composition components to Wavemap behavior.

They own:

  • API hook invocation.
  • Debounce, fetch, and loading state for domain data.
  • DTO mapping into render rows, options, cards, or table data.
  • Query-control capability selection.
  • Localized copy, grouped labels, and formatter functions.
  • Result selection behavior.
  • Route pushes, URL serialization, and saved-view persistence.
  • Permission-aware rendering and page-specific empty/error states.

This is why typeahead and search surfaces should usually be parent-driven. The parent or domain wrapper owns the query, fetching, filtering/ranking if any, result mapping, and selection side effect. Layout composition components can arrange the search region and results region. The reusable search component owns how that already-curated state is displayed and operated.

Useful examples:

  • Artist search wrappers can call the relevant query hook, map artist DTOs into option labels and thumbnails, and pass result actions into a generic typeahead.
  • Relationship sheets can keep assignment, pagination, selected candidates, and permission-aware actions near the domain workflow while using generic sheet, search, list, and pagination pieces.
  • Admin or edit pages can combine route guards from Authentication And Authorization with page-local empty and unauthorized states instead of making reusable components know about roles.

Endpoint hooks should stay close to individual API routes. Feature-level workflow hooks should own sequencing.

Workflows live on this page because they keep components and page wrappers focused on rendering prepared state. They also coordinate workflow draft state, pending state, partial-success state, retry posture, and recovery hints, so keep their state outputs aligned with the ownership rules in Front End State.

Use endpoint hooks for:

  • One API route.
  • Request args and response typing.
  • Mutation/query options close to the transport boundary.

Use workflow hooks for:

  • Creating a parent entity and then attaching related media.
  • Editing details separately from media collection sync.
  • Diffing retained, reordered, deleted, or promoted media.
  • Add-mode draft relationship fan-out after entity creation.
  • Retry posture, warnings, and recovery handoff.
  • UI-facing pending, success, partial-success, and failure state.

Avoid placing request choreography directly in large page components. Page components should read workflow state, render the form or surface, and call clear workflow commands.

For example, Add Artist can use endpoint hooks for creating the artist and uploading media, while a workflow hook owns the sequence: validate draft media, create the artist, attach selected images, choose the profile image, surface partial success, and hand recovery hints back to the page. Edit Artist can reuse the same media primitives but needs a different diff: retained persisted media, new browser files, deleted items, reordered gallery images, and profile-image changes. The media proof and deployment lanes for those flows live in Media Workflow And Validation.

Reusable components receive display-ready labels and formatter functions.

Use grouped label maps when a component has internal visible copy across several subcomponents and areas, such as:

  • Primary controls.
  • Status text.
  • Empty/loading/error text.
  • Row or item actions.
  • ARIA labels.
  • Dynamic formatter functions for counts, positions, pages, slide labels, and toast bodies.

The grouping should mirror the component’s internal label supply chain. Resolve labels once near the top-level component or page/domain adapter, then pass complete groups to subcomponents. Keep page or domain namespaces for workflow-specific copy, and use common--* namespaces for product-neutral component chrome.

Do not pass Wavemap translation keys, namespace names, or t() into generic reusable components.

Good shape:

const paginationLabels = {
controls: {
nextPage: t("pagination.next"),
previousPage: t("pagination.previous"),
},
status: {
pageSummary: ({ page, totalPages }) => t("pagination.summary", { page, totalPages }),
},
}

The page or domain wrapper resolves t() once, then the reusable component receives display-ready strings and formatter functions. Use Translation Resources for key and sheet conventions, and i18n Guardrails for verification boundaries.

Choose the layer that owns the risk:

  • Reusable component tests should cover props, default labels, custom label maps, formatter behavior, and meaningful interactions.
  • Page integration tests should cover loading, empty, error, search, filter, sort, view switching, saved views, and workflow state preservation.
  • Hook tests should cover API serialization, query keys, diffing, sequencing, partial-success behavior, and retry posture.
  • URL-state tests should cover parsing, sanitization, serialization, unsupported criteria, and invalid limits.
  • Browser smoke should stay narrow and prove behavior that only exists in a browser, such as routing, focus restoration, media rendering, or deployed runtime behavior.

Avoid testing calibration helpers or implementation-only plumbing when behavior is already covered through the public component or workflow surface.

Examples:

  • Test a reusable table filter popover with static column definitions, custom labels, and keyboard interactions.
  • Test a page wrapper by mocking the endpoint hook or API layer and asserting that URL state, empty states, and view switching survive user actions.
  • Test a workflow hook around the sequence and resulting states, especially partial success and retry posture.
  • Save browser smoke for behavior that needs the browser runtime, such as focus, routing, media rendering, or deployed edge/runtime integration.

Use Testing Overview for the full layer-selection rules and Local Development Runtime for the local browser runtime setup.