Update: Disregard this post. The solution is Html.Keyed. Thanks to Ilias Van Peer for pointing this out.

Elm is an interesting functional language for the web. It compiles to very fast JavaScript and through its strong static type system leads to virtually no runtime errors: If the program compiles, it is most certain it does not crash.

Elm Architecture

In Elm the only mutable variable is the model variable. Everything else is forced to be immutable. In the model you store the internal state of the application, but not the DOM; you only store the data it depends on.
The desired DOM is specified by defining functions which map that model to the DOM. Functions are to be understand in a mathematical way: They cannot access anything else than their input arguments and cannot do anything else than choosing their output.

Architecture Example

Say we want to show online users on a web page. Then the model would include the list of users:

type alias Model = {  
    online_users: List String,  
    -- optionally other state information for the website  

One of its transformation to DOM could look something like this:

div [id usercounter] [text ("users online: "++toString (List.length model.online_users))]

This generates a div element with id “usercounter” and the content “users online: " and then the number of online users.
At another position in the DOM you might put the online user names in a ul element etc.

CSS Transitions

The CSS transition style property allows to easily animate things efficiently (i.e. without calculating the animation path in JavaScript). You just have to specify the property transition: width 2s and whenever you change the width style property, it will change continuously over the course of two seconds instead of abruptly.

Elm vs Transitions

Elm does not have a notion of the additional state attached to a DOM element by CSS transitions. It just sees the complete DOM before the latest model update and the complete DOM after that update. It then calculates some steps that will transform the old DOM into the new DOM. There is no guarantee which former element will end up where in the DOM. The only guarantee is that the resulting DOM will reflect the new state. There is no correspondance between elements in the old and new DOM. But CSS animations build upon that correspondance.

Failing Example

Assume you want to create a slide show with nice slide transition effects. At every point in time there would be three slides in the DOM: the old slide in its faded out state, the current slide in a visible state and the next slide in the state before fading in. If the animation just uses the opacity CSS property for fade-in and fade-out, the DOM would look like this.

<div style="transition: opacity 2s; opacity: 0;">last slide content</div>
<div style="transition: opacity 2s; opacity: 1;">current slide content</div>
<div style="transition: opacity 2s; opacity: 0;">upcoming slide content</div>

Now to transition smoothly to the next slide you have to remove the last slide from the DOM, set the opacity of the current slide to 0, set the opacity of the upcoming slide to 1, and add a new div for the slide after that with opacity 0. Then the DOM looks like this:

<div style="transition: opacity 2s; opacity: 0;">current slide content</div>
<div style="transition: opacity 2s; opacity: 1;">upcoming slide content</div>
<div style="transition: opacity 2s; opacity: 0;">content of slide after that</div>

There is by design no way to tell Elm to do these steps. Instead Elm will look at the old and the new DOM and calculate an efficient series of operations turning the old into the new DOM. This results in these changes: change content of the first div to “current slide content”, the content of the second div to “upcoming slide content” and change the content of the third div to “content of slide after that”. This process does not change any div’s opacity property, so no opacity transition is triggered.

How to fix this

The DOM diff algorithm needs an option to force specific nodes in the old and new DOM to be identified with each other. This could be the HTML id attribute or a new, special attribute that is not rendered to the DOM.