How do you deal with forms?

Indeed one of the hardest things to get right in Elm. I’ve also played with composable-form and others, without luck.

A solution that is working for my particular use case (SPAs and configuration-heavy UIs) is to separate the form state definition from the html representation, something like this:

type alias Field state input error a =
    { key : String
    , parser : input -> Result error a
    , value : state -> input
    , update : input -> state -> state
    }

That allows me to create form elements by explicitly stating how they consume user input:

type FormError 
    = MustHaveAName

type alias Inputs =
    { name : String
    , address : String
    }

name : Field Inputs String FormError String
name =
    { key = "name"
    , parser = Ok
    , value = .name
    , update = \val inputs -> { inputs | name = val }
    }
    |> Field.notEmpty MustHaveAName

(Note: The “key” here is the actual value the server expects for that value)
(Note: Field.notEmpty is a just a Result.andThen over the parser, that fails if the string is empty)

Finally, I can create the UI layer, that is usually very different from app to app (I use Tailwind):

textField : Field state String error a -> state -> List (Attribute state) -> Html state
textField field state attrs =
    customInput "text"
        (\val -> field.update val state)
        (field.value state)
        (Html.Attributes.name field.key :: attrs)

(Note: I like to use the state as message in form elements, I find it easier to manage)

These fields can be consumed in any way using the parsers, I’ve played with several methods and the most interesting is to do something similar to composable-form, with Form.fromField, and then compose to create a form result. The problem with this idea is that it expects a specific kind of backend :frowning_face: , but anyway, I hope this helps.