Guide
Diagnosing Elm Html.keyed List Rendering Issues: Symptoms, Causes, and Fixes
Elm's Html.keyed lists can misbehave if keys are missing, non‑unique, or unstable. This guide walks through symptoms, diagnoses, and fixes, plus how to verify correct behaviour.
Published by Tasadduq Burney
12 Feb 2026, 00:26 UTC
3 min94.9K views0

Recognizable Condition
When a list of items in your Elm UI does not update correctly after a model change, you’ll notice stale DOM nodes, items appearing out of order, or new items never showing. The UI may render with duplicate elements or leave gaps where items were removed.
Cause & Diagnostic Table
| Cause | Typical Symptom |
|---|---|
| Missing key function | Elm reuses DOM nodes based on position; inserting or removing items breaks the list. |
| Non‑unique key | Duplicate keys cause the diff algorithm to pick the wrong node, leading to unexpected updates. |
| Key changes on each render | Using a non‑stable value (e.g., a list index) makes Elm think every item is new, forcing a full list recreation. |
Ordered Checks
- Inspect the key function passed to
Html.keyed. It should be a function that maps an item to a stable key.- Example of a good key:
item -> String.fromInt item.id. - Example of a bad key:
item -> String.fromInt (List.length items)or the list index.
- Example of a good key:
- Verify key stability. Use
Debug.log "key" keyValueinside the view to log the key on each render. The same logical item must produce the same string. - Check for uniqueness. Log all keys and look for duplicates. If two items share a key, Elm will issue a warning in the console.
- Confirm the key type implements
Eq. Keys must be comparable; strings, ints, and tuples of comparable elements work.
Fixes Tied to Findings
- Missing key: Add a key function that returns a unique identifier.
ul [] (List.map ( item -> Html.keyed (String.fromInt item.id) (li [] [ text item.name ]) ) items) - Non‑unique key: Ensure the identifier is truly unique. If you only have an index, combine it with a stable tie‑breaker, e.g.,
String.join ":" [String.fromInt item.id, String.fromInt item.version]. - Key changes each render: Replace the key with a domain‑specific id. Avoid using list length, index, or a mutable counter.
Escalation Criteria
- After correcting keys, the list still misbehaves. Check that no custom
Html.Attributes(e.g.,idattributes) clash with the key values. - Reduce the view to a minimal reproducible example. If the problem disappears, the issue likely lies elsewhere in the surrounding code.
- If the minimal example still fails, file a bug in the Elm compiler repository. Include the Elm version (e.g., 0.19.1), the failing view code, and a description of the observed DOM state.
Verification Checklist
- Run
elm-liveorelm-reactorand open the browser console. Look for warnings about duplicate keys. - Use
Debug.logto print key values on each render and confirm consistency. - Write a unit test that serializes the view before and after a known model change using
Html.Render.toStringand asserts the expected element order.
Practical Example
Suppose you have a list of todo items:
type alias Todo = { id : Int, text : String }
viewTodos : List Todo -> Html msg
viewTodos todos =
ul []
(List.map (
todo ->
Html.keyed (String.fromInt todo.id)
(li [] [ text todo.text ])
) todos)
Adding or removing a todo will now update the DOM correctly because each li is tied to a stable, unique key.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.