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