Skip to main content
US Army Corps of EngineersInstitute for Water Resources, Risk Management Center

Forms and Interaction

Patterns for form composition, input states, auto-save behavior, chart styling, and naming conventions.


Building Forms

The standard pattern for forms uses Section as the container and InputField as the input wrapper. Do not use raw Tier 1 primitives directly — always use InputField or CheckboxRow.

InputField Layouts

InputField supports two layouts and auto-detects the input type from props:

{/* Stacked layout (default) — label above input */}
<Section title="Dam Overview">
<div className="flex flex-col gap-4">
<InputField label="Year Completed" inputType="number" align="left" value={year} onValueChange={setYear} />
<InputField label="Dam Height (ft)" decimals={1} value={height} onValueChange={setHeight} />
<InputField label="Dam Type" options={damTypeOptions} value={type} onChange={setType} />
</div>
</Section>

{/* Grid layout — multiple fields in columns */}
<Section title="Embankment Overtopping Parameters">
<div className="grid grid-cols-2 gap-x-4 gap-y-4">
<InputField label="Embankment Height (ft)" inputType="number" inputVariant="compact" ... />
<InputField label="Downstream Slope (H:V)" inputType="number" inputVariant="compact" ... />
<InputField label="Crest Width (ft)" inputType="number" inputVariant="compact" ... />
<InputField label="Tailwater Depth (ft)" inputType="number" inputVariant="compact" ... />
</div>
</Section>

Use inputVariant="compact" for dense grid layouts. Use inputVariant="default" (or omit) for standard spacing.

CheckboxRow Patterns

CheckboxRow is used for toggle-with-optional-input patterns:

<Section title="Embankment Components" hint="(check all that apply)" contentClassName="space-y-2">
<CheckboxRow
label={
<>
Conduit through Embankment <InfoPopover title="Conduit">...</InfoPopover>
</>
}
checked={components.hasConduit}
onChange={(e) => onComponentChange('hasConduit', e.target.checked)}
/>
<CheckboxRow
label="Vertical or Inclined Drain/Filter"
checked={components.hasVerticalDrain}
onChange={(e) => onComponentChange('hasVerticalDrain', e.target.checked)}
{...(components.hasVerticalDrain && {
inputType: 'number',
decimals: 1,
align: 'left',
inputValue: components.verticalDrainElevation,
onInputValueChange: (v) => onComponentChange('verticalDrainElevation', v),
inputPlaceholder: 'Top Elevation (ft)',
inputVariant: 'xs',
inputClassName: 'ml-4 w-40',
})}
/>
</Section>

Key patterns:

  • Use contentClassName="space-y-2" on the Section for tight vertical spacing
  • Use conditional spread syntax {...(condition && props)} to show/hide the inline input
  • The disableInputWhenUnchecked prop auto-disables the input when the checkbox is unchecked

Form Input States

StateBorderRingUsage
Defaultborder-dst-bordernoneNormal state
Focusborder-dst-steelring-1 ring-dst-focus-ringActive editing
Invalidborder-rose-500ring-2 ring-rose-500Validation error
Warningborder-amber-500ring-1 ring-amber-500Out-of-bounds warning
Disabledbg-dst-disabled-bgnoneNot editable

These states are built into the Tier 1 input primitives (InputNumber, InputText, Select) and are inherited by InputField and CheckboxRow. You don't need to implement them manually.


Auto-Save Pattern

The application uses debounced auto-save for user inputs. No "Save" button for normal parameter entry.

  • Save on change with 500ms debounce
  • Use useDebounce hook from hooks/common/useDebounce.js
  • Track changes with userEditedRef to prevent duplicate saves
  • Use hash comparison to skip saves when data hasn't changed

Chart Styling

Charts use Chart.js with consistent styling. All chart colors are resolved from CSS custom properties at render time.

import { getChartColors, FONT_FAMILY } from '@components/common/chart/utils';

const C = getChartColors();

// Axis styling
ticks: { color: C.text, font: { family: FONT_FAMILY, size: 15 } }
grid: { color: C.gridMinor, drawBorder: false }
title: { color: C.text, font: { family: FONT_FAMILY, size: 16 } }

// Tooltip styling
tooltip: {
backgroundColor: C.tooltipBg,
borderColor: C.tooltipBorder,
borderWidth: 1,
titleColor: C.text,
bodyColor: C.text,
}

Font family: 'Aptos Narrow, system-ui, sans-serif' (from the FONT_FAMILY constant)

Line colors: Use C.primary (steel blue) for DST estimates, C.revised (amber) for user-modified values.


Naming Conventions

TypeConventionExample
ComponentsPascalCaseHydrologicHazardTable
Custom hooksuse* prefixuseHydrologicHazardData
Functions/VariablescamelCasecalculateRisk, userName
ConstantsSCREAMING_SNAKE_CASEMAX_RETRIES, DEBOUNCE_MS
Visual constantsSCREAMING_SNAKE_CASEACCENT_BAR, HEADING
Event handler propson* prefixonClick, onSubmit, onValueChange
CSS tokenskebab-case with dst- prefix--color-dst-steel, bg-dst-page-bg

For the complete naming conventions across all languages (C#, JavaScript, Python, R), see Case Conventions by Language.


Do's and Don'ts

  • Use Section for page sections
  • Use InputField for form inputs (not raw InputNumber/InputText)
  • Use CheckboxRow for checkbox + label + optional input patterns
  • Use Tailwind's slate scale for neutrals
  • Declare visual constants at the top of component files
  • Use dst-* tokens for brand colors
  • Test at all 5 reference device sizes
  • Keep page components thin — extract complex JSX to sub-components
  • Add min-w-0 to grid children to prevent overflow