Best Practice: Configurable vs Separate Components for Complex Forms

Author: Pablo Mayoral Published: 18/10/2026 Updated: 18/10/2026
Category: Design System →

Choose component boundaries by their interaction contract. Share stable cells and field groups, and separate APIs when tasks, semantics or state ownership change.

Best Practice: Configurable vs Separate Components for Complex Forms
Quick Answer

Choose component boundaries by their interaction contract. Share stable cells and field groups, and separate APIs when tasks, semantics or state ownership change.

Use one configurable component when its options preserve the same interaction, semantics and state ownership. Create separate public components when the user task or behavioral contract changes, and share the smaller controls underneath. A compact table and a comfortable table can often share an API; a report table and a spreadsheet editor usually need different contracts. The goal is a boundary that readers of the code can predict.

This guide gives a best practice for deciding between one highly configurable component and separate components for responsive tables, complex forms and many variants. The decision framework and fictional examples are design recommendations, supported by the linked platform documentation. They are not a universal component taxonomy or measured productivity claim.

Choose the boundary by behavior

Start with a sentence describing the component’s promise. For a customer list: “Compare customers, follow a profile link, and optionally select records for an action.” For an editable allocation grid: “Navigate cells, modify values and commit or cancel edits.” Similar rows and borders do not make those promises interchangeable.

Component API illustration: Choose the boundary by behavior
Recommended API boundary: configure appearance, separate changed behavior and share smaller parts.

Write down what stays invariant: accessible role, keyboard behavior, data shape, ownership of changes and recovery after failure. Then classify proposed options. Density, a visible caption and a cell renderer may customize one promise. A switch that replaces link navigation with an editing workflow can change the promise itself. That is a reason to consider separate entry points, even when their visual parts remain shared.

For example, prefer a named CustomerTable and AllocationGrid over an EverythingTable whose editable, selectable, spreadsheet and mobile booleans silently redefine interaction. This is a recommended public API choice, not a demand for duplicated internals. Both entry points can reuse formatters, status badges, empty-state copy and surface styles.

Keep the decision local to the consumer. If one screen needs a download button above the table, compose that button outside the table before adding exportPermissions, exportEndpoint and exportFormat to every instance. For the broader distinction between shared controls and application compositions, use the component library architecture guide.

Separate supported options from impossible states

Count meaningful combinations rather than props alone. Three independent booleans describe eight theoretical configurations, but your product may support only four. Record which combinations are valid and what the consumer should do when a requested combination is unsupported. A large API becomes especially difficult when callers must memorize invisible exclusions.

Suppose an invoice form exposes isSaving, hasError and isComplete. Saving while complete might be meaningless in that workflow. Replace the overlapping status flags with a named status such as editing, submitting, failed or saved, while keeping genuinely independent data separate. React’s state-structure guidance recommends avoiding contradictory and redundant state; it does not impose a numeric prop limit.

Use a short capability table in the component documentation: supported mode, required inputs, available actions, owner of state and verification example. A selection mode could require selectedIds and onSelectionChange. A display mode should not require callbacks that are never used. When the language supports it, model mutually exclusive modes explicitly rather than accepting an unstructured bag of optional fields.

Do not split automatically at the fifth prop or the tenth variant. A date field can legitimately need several independent options. Conversely, two props can hide incompatible behaviors. Ask a teammate to configure the two most different supported consumers without reading internal conditionals. If the teammate cannot predict which inputs matter, improve the contract before adding another flag.

Keep table and grid contracts explicit

A table with links or buttons does not automatically become an ARIA grid. The deciding factor is the intended interaction model. Treat semantic and keyboard changes as first-class architecture decisions, with explicit examples and acceptance checks.

Component API illustration: Keep table and grid contracts explicit
WAI distinguishes table controls in the tab sequence from a grid with author-managed directional focus.

The WAI table pattern describes a tabular structure whose contained controls retain their own tab stops, and recommends native HTML tables where possible. The WAI grid pattern describes a composite widget with managed focus and directional navigation. Adding a role alone does not implement that navigation. A native table can contain interactive controls without adopting grid behavior.

For a read-oriented customer table, specify the caption, column headers, sort announcement and row actions. For a budget editor, separately specify entering a cell, editing its value, leaving edit mode and reporting a failed save. Then decide which low-level renderers can be shared. Keep selection rules and focus movement attached to the contract that actually uses them.

In review, run the same small dataset through both contracts: a long customer name, a missing optional value and one unavailable action. Observe whether the intended user can reach and understand each action. Keep the results with the component examples. Follow the responsive table design walkthrough for the narrower decision about presenting columns on small screens.

Inspect reusable cells with EmviUI

Use a real visual specimen to identify reusable parts before generalizing an entire screen. The EmviUI cell collection shows text, avatars, badges, progress and row actions. Those are useful candidates for independent cell content rather than one giant table variant.

EmviUI table cell specimens showing avatars, status badges, text, row actions and progress.
Real EmviUI visual reference; the image does not establish runtime semantics, keyboard support or permissions.

The live EmviUI component catalogue provides a visual starting point for this exercise. In the pictured collection, compare the labeled Edit and Delete controls with the icon-only action group. Record the names your implementation must expose, the behavior behind each action and who authorizes it. The image demonstrates appearance; it cannot establish keyboard support, backend permissions or production behavior.

Build one fictional customer-list composition using a customer cell, a status cell and an action cell. Reuse the same status cell inside a detail panel without importing the customer table. If that requires table-only context, decide whether the context is necessary or whether the cell has absorbed responsibilities that belong to the parent. EmviUI helps you explore the visual composition; your application still needs data contracts and behavioral verification.

Avoid inferring prices from example amounts visible in a specimen. Treat all sample values as illustration content. The useful question here is whether each part communicates its role and can survive another context. Keep the original specimen next to the proposed API so designers and developers can discuss the same concrete elements.

Compose complex forms around real tasks

For complex forms, share stable field groups while allowing each task to own its submission workflow. A create-customer form and an edit-customer form may share contact fields but differ in defaults, permissions, cancellation and successful completion. A single form with isCreate, isEdit and isWizard switches can obscure those differences.

Component API illustration: Compose complex forms around real tasks
Recommended composition: shared contact fields inside a task form that owns submission and server errors.

Composition gives you another option: render ContactFields inside task-specific forms, and accept actions or supporting content as explicit children or named regions. React documents passing JSX through children so a wrapper can contain content without knowing its implementation. Use that flexibility where it keeps a meaningful boundary; arbitrary nesting is not automatically a good API.

Preserve semantic grouping while composing. WAI’s grouping tutorial explains fieldset and legend for related controls. Its validation tutorial also distinguishes client feedback from server-side validation. In the proposed design, a field group can render labels, help and associated messages, while the task form interprets the server response and decides what happens next.

For a concrete failure case, let the server reject a customer identifier while accepting the email format. Keep the entered email, expose the identifier problem beside its control, and give the caller a defined way to map the response. Do not make a shared text field understand customer-account business rules. Document whether changing a field clears its previous error, and verify that behavior with the product team.

Give shared state a clear owner

State ownership is part of the API. If a table and a details panel must agree on the selected customer, choose one owner for the selected identifier. Let both consumers receive the current value and request changes through an explicit event. Separate local presentation state from shared product state.

Component API illustration: Give shared state a clear owner
A shared selection owner and illustrative form transitions; failed submissions can retry after correction.

React’s guidance on sharing state describes moving coordinated state to a common parent. Apply that principle deliberately: a field’s temporary focus state may stay local, while the active customer belongs above both views. Document what happens when the selected customer disappears from the current results and whether filtering preserves selection.

For a submission workflow, draw the allowed transitions: editing to submitting; submitting to saved or failed; failed back to submitting after correction. This is an illustrative model, not a complete specification for every form. Add cancellation or partial success only when the task needs them. Keep server errors and entered values alongside the status, with clear ownership.

A designer should be able to map each state to a specimen, and a developer should know which event enters it. For planning visual alternatives in Figma, the guide to variants and UI states is a useful next step. A Figma variant documents a representation; it does not implement runtime transitions or persistence.

Change responsive layout without losing work

Responsive changes can expose a bad component boundary. Turning a wide report into a narrow list may alter layout alone, or it may remove essential comparison and editing tasks. Decide which case you have before creating mobile and desktop APIs that drift apart.

WAI’s Reflow explanation discusses content at a width equivalent to 320 CSS pixels and exceptions for content that requires two-dimensional layout. A data table exception is not permission for unrelated page controls to overflow. In your own acceptance criteria, identify which region needs horizontal movement and which surrounding controls must adapt.

In the fictional customer form, type a value and trigger an error, then change viewport width. The pending values should follow the product’s stated preservation rule. React’s preservation and reset documentation explains that state depends on component identity and tree position; replacing a subtree or changing its key can reset local state. A breakpoint should not accidentally discard work.

Prefer one owner for the draft and two presentations only when two presentations are needed. Retain stable field identifiers and verify how focus behaves if a focused control moves or disappears. For a table-to-card presentation, explicitly retain labels and required actions rather than assuming a visual rearrangement communicates the same relationships. Test the actual implementation, since a static canvas cannot prove these properties.

Migrate one consumer before expanding

Migrate the smallest consumer that exercises the disputed behavior. Keep its data, permission cases and expected actions fixed while changing the component boundary. This makes it possible to compare the old and proposed contracts without mixing in a new product workflow.

Component API illustration: Migrate one consumer before expanding
Recommended migration: keep task and data stable, then compare representative acceptance examples.

For example, extract CustomerActions from the existing customer table, expose selectedIds through one documented owner, and move the customer-creation request into CreateCustomerForm. Keep the same visible labels during that pass. Then review whether a second consumer can reuse the extracted parts without importing unrelated behavior. If it cannot, revisit the abstraction while the change is still small.

  1. Write the invariant behavior and the supported configurations.
  2. Separate semantic or keyboard contracts that differ.
  3. Compose shared fields, cells and actions around task-specific owners.
  4. Verify failure, empty data, permissions and narrow layouts.
  5. Record a migration example and remove the obsolete path deliberately.

The useful evidence is a working consumer, a small set of representative examples and an explicit state contract. If both public components need the same fix to a shared badge, update that badge once. If their save behavior diverges, keep those workflows separately understandable. Reuse should make changes easier to reason about; the number of exported component names is not the success metric.

Pablo Mayoral

Senior UX/UI & Web Designer with 10+ years of experience. Creator of EmviUI (10k+ components Figma design system), UImand, and Vitamin Bootstrap. Currently designing digital products at CQMRewards.

Frequently asked questions

How many props are too many for a component? ▼
There is no universal number. Review the supported combinations and behavioral contract. React’s state-structure guidance supports avoiding contradictory state; the prop count itself does not tell you whether one API is understandable.
Does an interactive table always need role grid? ▼
No. The WAI table pattern allows controls inside a table with their own tab stops. The grid pattern adds managed focus and directional navigation, which must be implemented deliberately.
Should create and edit forms share one component? ▼
They can share field groups and presentation while exposing separate task-level forms when defaults, submission or permissions differ. React’s children mechanism supports composing those shared pieces.
Can switching mobile layouts reset form values? ▼
Yes, depending on component identity and tree position. React documents that changing a key or removing a component can reset its state. Keep drafts in an appropriate stable owner when preservation is required.
Does client-side validation replace server validation? ▼
No. WAI’s validation tutorial explains that client checks can be bypassed and server-side validation is still needed. Define how server errors are returned and associated with the appropriate controls.
Can an EmviUI screenshot prove a component is accessible? ▼
No. It can demonstrate the visible composition and controls. Verify the implemented semantics, focus behavior and error handling against the relevant WAI pattern; inspect the actual component rather than inferring behavior from the image.

Related articles