Skip to content

FRD-028: Form Layout Components

FieldValue
OwnerDavid Holmes
StatusShipped
Last Updated2026-05-26
Target Releasev2.0.0
TypeWidget
SizeM
PriorityP1 — Component Improvement

Document Summary

Introduce FormSection, FieldErrorSummary, and InlineEdit components to address form layout and editing patterns missing from the design system. FormSection provides semantic grouping with optional collapsibility. FieldErrorSummary aggregates validation errors with scroll-to-first-error behavior. InlineEdit enables click-to-edit patterns for read-heavy UIs. All integrate with React Hook Form and Zod.


Introduction

Overview

The design system provides field-group.tsx (FieldGroup) for grouping label + input + message, but lacks higher-level form layout primitives. Teams building multi-section forms (settings pages, profile editors, onboarding wizards) duplicate section heading/description/divider patterns. Error summaries that scroll to the first invalid field are hand-built per project. Inline edit patterns (click a value to edit it in place) have no standard implementation.

Goals

  • Provide FormSection for visually and semantically grouping related form fields with heading, description, and optional collapse.
  • Provide FieldErrorSummary that renders a list of validation errors with click-to-scroll-to-field behavior.
  • Provide InlineEdit for toggling between a read-only display and an editable input, with save/cancel actions.
  • Demonstrate RHF + Zod integration patterns for all three in Storybook docs.

Non-Goals

  • Full form wizard (multi-step with routing) — PanelWizard exists for that.
  • Form state management library — RHF is the recommended solution.
  • Server-side form actions or progressive enhancement.

Scope

In Scope

ItemDescription
FormSectionSemantic <fieldset>-based section with heading, description, optional collapse, divider control.
FieldErrorSummaryError list component that accepts RHF errors object; each error is a clickable link scrolling to the field.
InlineEditRead-view / edit-view toggle with save/cancel buttons; supports TextField, Select, and TextArea as edit controls.
RHF + Zod examplesStorybook recipes showing form validation with these components.
Stories and testsFull Storybook coverage; unit tests for scroll behavior, collapse, and edit toggling.

Out of Scope

ItemReason
Multi-step wizardCovered by PanelWizard.
Form auto-saveConsumer-level concern; InlineEdit supports onSave callback.
Drag-to-reorder form fieldsNiche requirement; not a layout primitive.

Users and Pain Points

UserPain Point
Settings page developersRecreate section heading + description + divider layout for every settings group.
Form-heavy application teamsNo standard error summary; users must scroll to find invalid fields.
Admin dashboard developersInline editing of table cells or profile fields requires custom toggle logic per field.
Accessibility-focused teamsError summaries without programmatic focus management fail screen reader users.

Definitions

TermDefinition
FormSectionA visually distinct group of related form fields, rendered as a <fieldset> with a <legend>.
FieldErrorSummaryA banner-like component listing all current form errors with links to the corresponding fields.
InlineEditA pattern where a value is displayed as read-only text; clicking it transitions to an editable input.
Scroll-to-errorProgrammatically scrolling the viewport and focusing the first invalid field in a form.

Current State

  • FieldGroup (src/components/ui/field-group.tsx): Groups a label, input, hint, and error message for a single field. Does not handle multi-field sections.
  • FieldMessage: Renders error/success/hint messages with icons. Used within FieldGroup.
  • No FormSection, FieldErrorSummary, or InlineEdit exists.
  • RHF is used in consumer applications but the design system has no integration layer or error aggregation utilities.

Proposed Solution

FormSection

src/components/ui/form-section.tsx — Renders a <fieldset> with a styled <legend> (heading), optional description paragraph, and optional collapsible behavior (using the existing Collapsible primitive or a simple details/summary pattern). A divider prop controls whether a top border separates it from the previous section.

FieldErrorSummary

src/components/ui/field-error-summary.tsx — Accepts an errors object (compatible with RHF formState.errors) and an optional fieldLabels map (Record<string, string>) for human-readable field names. Renders an alert-styled card with a list of errors. Each error is a button that calls document.getElementById(fieldId)?.scrollIntoView({ behavior: "smooth" }) and then focuses the element. The component uses role="alert" and aria-live="assertive" for screen reader announcement.

InlineEdit

src/components/ui/inline-edit.tsx — A compound component with two states: read mode (renders the value as styled text with a pencil icon) and edit mode (renders the appropriate form control with save/cancel buttons). Props include value, onSave, onCancel, editControl (a render prop receiving { value, onChange }), and readView (optional custom read display). Pressing Escape cancels; Enter in a single-line input saves.


Requirements

Requirement Priorities

  • Must Have: FormSection, FieldErrorSummary with scroll-to-error, InlineEdit with save/cancel.
  • Should Have: FormSection collapsible mode, FieldErrorSummary field label mapping, InlineEdit keyboard shortcuts.
  • Could Have: FormSection animated collapse, FieldErrorSummary auto-scroll-on-submit helper, InlineEdit optimistic save.

Functional Requirements

IDRequirementPriority
FR-01FormSection renders a <fieldset> with <legend> heading and optional description.Must
FR-02FormSection collapsible prop enables expand/collapse with arrow indicator.Should
FR-03FormSection divider prop renders a top border when true (default true for non-first sections).Must
FR-04FieldErrorSummary accepts errors (RHF-compatible) and renders a clickable error list.Must
FR-05FieldErrorSummary clicking an error scrolls to and focuses the corresponding field.Must
FR-06FieldErrorSummary uses fieldLabels map to display human-readable field names.Should
FR-07FieldErrorSummary auto-focuses itself on mount (for submit-triggered rendering).Should
FR-08InlineEdit toggles between read mode and edit mode on click or Enter key.Must
FR-09InlineEdit save button triggers onSave callback; cancel restores original value.Must
FR-10InlineEdit Escape key cancels; Enter key saves (in single-line inputs).Should
FR-11InlineEdit shows loading state during async onSave.Could

Non-Functional Requirements

IDRequirementTarget
NFR-01FormSection render time< 5ms for a section with 10 fields.
NFR-02FieldErrorSummary scroll-to-field latency< 100ms from click to field focus.
NFR-03InlineEdit mode toggle< 16ms perceived transition.
NFR-04Bundle size< 3 KB gzipped per component.
NFR-05Dark modeFull token-based dark mode support.

API/Interface Requirements

FormSection

interface FormSectionProps {
heading: string;
description?: string;
collapsible?: boolean;
defaultCollapsed?: boolean;
divider?: boolean; // default true
children: ReactNode;
className?: string;
}

FieldErrorSummary

interface FieldErrorSummaryProps {
errors: Record<string, { message?: string }>;
fieldLabels?: Record<string, string>;
title?: string; // default "Please fix the following errors"
autoFocus?: boolean; // default true
className?: string;
}

InlineEdit

interface InlineEditProps<T = string> {
value: T;
onSave: (value: T) => void | Promise<void>;
onCancel?: () => void;
editControl: (props: { value: T; onChange: (value: T) => void }) => ReactNode;
readView?: (value: T) => ReactNode;
saveLabel?: string; // default "Save"
cancelLabel?: string; // default "Cancel"
editLabel?: string; // default "Edit"
disabled?: boolean;
className?: string;
}

Accessibility Requirements

IDRequirement
A11Y-01FormSection uses &lt;fieldset&gt; and &lt;legend&gt; for semantic grouping; collapse toggle has aria-expanded.
A11Y-02FieldErrorSummary uses role="alert" and receives focus on mount for screen reader announcement.
A11Y-03FieldErrorSummary error links use role="link" or native &lt;a&gt; with href="#fieldId" for navigation.
A11Y-04InlineEdit read mode has role="button" or uses a &lt;button&gt; element; edit label is announced.
A11Y-05InlineEdit edit mode traps focus within the edit region; cancel/save are keyboard accessible.
A11Y-06All components pass axe-core automated checks with zero violations.

Content and Documentation Requirements

IDRequirement
DOC-01Storybook docs page for each component with usage guidelines, do/don’t, and prop tables.
DOC-02RHF + Zod integration recipe showing FormSection grouping, FieldErrorSummary wired to formState.errors, and InlineEdit within a form.
DOC-03Composition guide showing FormSection + FieldGroup + FieldErrorSummary working together in a settings page.

Dependencies

DependencyTypeRisk
src/components/ui/field-group.tsxInternalLow — FormSection wraps multiple FieldGroups.
src/components/ui/text-field.tsxInternalLow — InlineEdit default edit control.
src/components/ui/button.tsxInternalLow — Save/cancel actions.
react-hook-formPeerLow — Error object shape is standard; no hard dependency.
src/styles/motion.cssInternalLow — Collapse animation tokens.

Risks and Tradeoffs

RiskLikelihoodImpactMitigation
FieldErrorSummary scroll-to-field fails if field IDs don’t matchMediumMediumDocument ID convention; provide a fieldIdPrefix prop for namespaced forms.
FormSection collapsible state hides required fields from validationLowHighExpand collapsed sections containing errors when FieldErrorSummary is rendered.
InlineEdit save/cancel buttons conflict with table row click handlersMediumMediumStop propagation on InlineEdit button clicks; document this behavior.

Open Questions

#QuestionOwnerStatus
OQ-01Should FormSection support a required indicator showing that the section contains required fields?David HolmesOpen
OQ-02Should FieldErrorSummary auto-expand collapsed FormSections containing errors?David HolmesOpen
OQ-03Should InlineEdit support multi-field editing (e.g., first name + last name as a single inline edit)?David HolmesOpen

Acceptance Criteria

#Criterion
AC-01FormSection renders a semantic &lt;fieldset&gt; with heading and optional description; collapsible mode works.
AC-02FieldErrorSummary renders validation errors as clickable links that scroll to and focus the corresponding field.
AC-03FieldErrorSummary receives focus on mount and announces errors to screen readers.
AC-04InlineEdit toggles between read and edit modes; save and cancel work via buttons and keyboard shortcuts.
AC-05All components render correctly in dark mode.
AC-06All components pass axe-core checks with zero violations.
AC-07Storybook stories exist with controls for all props.
AC-08pnpm typecheck and pnpm vitest run --project unit pass with zero errors.

LLM Handoff Instructions

When implementing this FRD:

  1. Start with FormSectionsrc/components/ui/form-section.tsx. Use &lt;fieldset&gt; and &lt;legend&gt;. For collapsible, use a simple useState with height animation via motion tokens. Follow the existing FieldGroup naming and file conventions.
  2. Then FieldErrorSummarysrc/components/ui/field-error-summary.tsx. Accept an errors object matching RHF’s formState.errors shape (Record&lt;string, { message?: string }&gt;). Use scrollIntoView + focus() for navigation. Render in an alert card using StatusCard or a styled div with role="alert".
  3. Then InlineEditsrc/components/ui/inline-edit.tsx. Use a generic type parameter &lt;T&gt; for the value. Default readView renders the value as text in a button-like container. The editControl render prop gives consumers full control over the edit UI.
  4. Stories in sibling .stories.tsx files. Include a “Full Settings Page” story composing all three.
  5. Tests in sibling .test.tsx files. Test scroll-to-error with mocked scrollIntoView, collapse toggle, and InlineEdit mode switching.

Key files to reference:

  • src/components/ui/field-group.tsx — existing field grouping pattern.
  • src/components/ui/text-field.tsx — default InlineEdit edit control.
  • src/components/ui/button.tsx — save/cancel buttons.
  • src/components/ui/status-card.tsx — potential base for error summary card.

Decision Log

DateDecisionRationale
2026-05-26FormSection uses native &lt;fieldset&gt;/&lt;legend&gt; rather than div + heading.Semantic HTML provides free accessibility; styled &lt;legend&gt; is achievable with CSS.
2026-05-26FieldErrorSummary accepts RHF error shape but has no hard dependency on RHF.Any object matching Record&lt;string, { message?: string }&gt; works; keeps the component framework-agnostic.
2026-05-26InlineEdit uses a render prop for the edit control rather than a fixed set of input types.Maximum flexibility; consumers can use TextField, Select, TextArea, or custom inputs.

Document History

VersionDateAuthorChanges
0.12026-05-26David HolmesInitial draft.