We can render and parse forms via a record. This can be a simple record:
data ContactForm = ContactForm
  { name :: Text
  , age :: Int
  , isFavorite :: Bool
  , planet :: Planet
  , moon :: Moon
  }
  deriving (Generic, FromForm)
Using a simple record requires you to match the field names manually:
nameForm :: View AddContact ()
nameForm = do
  form Submit $ do
    -- Make sure these names match the field names used by FormParse / formData
    field "name" $ do
      label $ do
        text "Contact Name"
        input Username @ placeholder "contact name"
Alternatively, use a Higher-Kinded record, and use fieldNames to have the compiler help:
data ContactForm' f = ContactForm'
  { name :: Field f Text
  , age :: Field f Int
  , isFavorite :: Field f Bool
  , planet :: Field f Planet
  , moon :: Field f Moon
  }
  deriving (Generic, FromFormF, GenFields FieldName)

nameForm' :: View AddContact ()
nameForm' = do
  let f = fieldNames @ContactForm'
  form Submit $ do
    field f.name $ do
      label $ do
        text "Contact Name"
        input Username @ placeholder "contact name"
We can use a Higher-Kinded form not only for field names and values, but to validate form fields
data UserForm f = UserForm
  { user :: Field f User
  , age :: Field f Int
  , pass1 :: Field f Text
  , pass2 :: Field f Text
  }
  deriving (Generic, FromFormF, GenFields Validated, GenFields FieldName)
Write a validator for each field:
validateAge :: Int -> Validated Int
validateAge a =
  validate (a < 20) "User must be at least 20 years old"
Then combine them into a record of Validated fields
validateForm :: UserForm Identity -> UserForm Validated
validateForm u =
  UserForm
    { user = validateUser u.user
    , age = validateAge u.age
    , pass1 = validatePass u.pass1 u.pass2
    , pass2 = NotInvalid
    }