ensolabs/designsystemet-elm/1.0.0

Designsystemet.ErrorSummary

A list of a form's errors after a failed submit, with a link to each field.

Mirrors ErrorSummary from @digdir/designsystemet-react.

Each error shows up twice: under its field, and as a link to that field in the summary. Keep one list of errors and read both from it:

emailField : ErrorSummary.FieldId
emailField =
    ErrorSummary.fieldId "email"


validate : Model -> List ErrorSummary.Error
validate model =
    if String.contains "@" model.email then
        []


    else
        [ { field = emailField, message = "Skriv inn en gyldig e-postadresse" } ]


view : Model -> Html Msg
view model =
    Html.form [ Events.onSubmit Submitted ]
        [ ErrorSummary.view
            { heading = "Du må rette disse feilene før du kan sende inn"
            , level = ErrorSummary.H2
            , errors = model.errors
            }
            []
        , Textfield.new (Ds.label "E-post")
            |> Textfield.withError (ErrorSummary.messageFor emailField model.errors)
            |> Textfield.view
                [ ErrorSummary.linkTarget emailField
                , Attr.value model.email
                , Events.onInput ChangedEmail
                ]
        ]

The FieldId ties the link to its field, and messageFor gives the field the same message. Set model.errors on submit, not while the user types: <ds-error-summary> takes focus when it appears, so screen readers announce it at once.

React → Elm

  • <ErrorSummary> → ErrorSummary.view { … } []
  • <ErrorSummary.Heading> → heading and level
  • <ErrorSummary.List> + .Item + .Link → errors
  • <ErrorSummary.Link href="#email"> → { field = ErrorSummary.fieldId "email", … }

Managed by designsystemet-web

  • role="group", tabindex="-1" and aria-labelledby on <ds-error-summary>
  • id on the heading

Links in Browser.application

The links are plain <a href="#email"> anchors. In a Browser.application every link click goes to your onUrlRequest, so the browser won't jump to the field. Move focus there in update:

LinkClicked (Browser.Internal url) ->
    case url.fragment of
        Just id ->
            ( model, Task.attempt (\_ -> NoOp) (Browser.Dom.focus id) )


        Nothing ->
            ( model, Nav.pushUrl model.key (Url.toString url) )
{ heading : String
, level : Level
, errors : List Error
}
-> List (Attribute msg)
-> Html msg

A <ds-error-summary class="ds-error-summary"> with a heading and a list of links. The attributes go on <ds-error-summary>.

Renders nothing when there are no errors, so you can always call it. The summary appears, and takes focus, once there is something to fix.

React defaults to h2. Here the level is required, as for Heading.

type Level
= H2
| H3
| H4
| H5
| H6

The summary's heading level. No H1: the page already has one, and <ds-error-summary> only takes its name from an h2–h6.

Errors

type alias Error =
{ field : FieldId
, message : String
}

A field and what's wrong with it.

messageFor : FieldId -> List Error -> Maybe String

The message for one field, ready for withError on a Textfield, Checkbox, Radio or Fieldset. If a field has several errors, the first one wins.

Textfield.withError (ErrorSummary.messageFor emailField model.errors)

Field ids

type FieldId

The id of a field that can have an error. A separate type, so the summary's link and its field are built from the same value.

fieldId : String -> FieldId

Make a FieldId from an id that is unique on the page.

The id as a string, for Browser.Dom.focus and the like.

linkTarget : FieldId -> Attribute msg

Sets the id on the input, select or textarea the error is about, so the summary's link jumps there.

Checkbox.view [ ErrorSummary.linkTarget termsField, Attr.checked model.terms ]