Declarative animations for LiveView

Your LiveView,
animated.

One component. Real spring physics. Animate enter, exit, position and size in plain HEEx — no phx-hook, no JS commands to wire up.

enter exit position size stagger spring hook-free server-driven enter exit position size stagger spring hook-free server-driven

See it move.

Scroll to explore
— 01

Enter & exit, in a grid.

Render a list; add or remove items in your LiveView. Shift animates each card in when it arrives and out when it leaves — no per-element wiring.

Add and remove items in a grid.

1
2
3
cards.heex .heex
<.animated
  :for={card <- @cards}
  id={card.id}
  initial={%{y: 16, scale: 0.95}}
  exit={%{y: -16, scale: 0.95}}
>
  {card.n}
</.animated>
— 02

Reordering with real FLIP.

Shuffle a list and Shift measures the layout delta, then animates every item from its old slot to its new one. Position is just another thing your server can change.

Add, remove, sort, or reorder — surviving items slide to their new positions via FLIP, added items fade in, removed items fade out.

Draft the hero
Style the cards
Wire animations
Polish edges
Ship landing
Write docs
reorder.heex .heex
<.animated :for={row <- @rows} id={row.id}>
  {row.label}
</.animated>
— 03

Filtering, staggered.

Type into the field — matching items animate in, non-matches drop out. Staggered enters, simultaneous exits, all driven by the filtered list on the server.

Type to filter — items animate out as they stop matching, and back in as they match.

Shift
Motion One
AutoAnimate
Framer Motion
GSAP
React Spring
Anime.js
list.heex .heex
<.animated
  :for={name <- @results}
  id={name}
  initial={%{y: 8}}
  exit={%{scale: 0.92}}
>
  {name}
</.animated>
— 04

Expand & collapse, by height.

Animate from height: 0 to height: auto without measuring or freezing the box.

With a handle. The header is the toggle. The content animates its height — the content area is part of the same visual element as the handle.

accordion.heex .heex
<div class="overflow-hidden">
  <button phx-click="toggle">Section title</button>
  <.animated
    :if={@open}
    initial={%{height: 0}}
    exit={%{height: 0}}
  >
    ...content of any height...
  </.animated>
</div>

Plain toggle. The panel doesn't exist in the DOM until you click Show details — Shift animates it in the moment LiveView renders it, no hooks or mount lifecycle to wire up. Animates back out the moment LiveView removes it.

toggle.heex .heex
<button phx-click="toggle">
  Show / Hide
</button>
<.animated
  :if={@open}
  initial={%{height: 0}}
  exit={%{height: 0}}
>
  A separate panel that slides open by animating its height.
</.animated>
— 05

Modal with a real spring.

An overlay that pops in with a spring and fades out on dismiss. The transition map is the only thing different from a regular div.

A dialog that pops in with a spring and fades out on dismiss. The transition is the only thing different from a regular div.

modal.heex .heex
<.animated
  :if={@open}
  initial={%{scale: 0.9, y: 8}}
  exit={%{scale: 0.95}}
  transition={%{type: :spring, stiffness: 300, damping: 26}}
>
  Pops in with a spring, fades out on dismiss.
</.animated>
— 06

Tune the spring.

Stiffness, damping, mass. Drag the sliders and watch the same animation change character — overshoot, settle, bounce. The runtime simulates and bakes keyframes per render.

A real spring simulation — tune its feel, then move the box. The same transition drives the FLIP slide here, and enter/exit elsewhere.

Presets
spring.heex .heex
<.animated
  transition={%{type: :spring, stiffness: 260, damping: 20, mass: 1.0}}
>
  Any animation on this element runs as a real spring simulation —
  enter, exit, position and size all share the same physics.
</.animated>
— 07

Notifications, dismissed by the server.

The LiveView schedules an auto-dismiss with Process.send_after — Shift handles the in/out animation on the client. Server stays in charge.

Toasts slide in, then auto-dismiss after a few seconds.

No notifications.

toasts.heex .heex
<.animated
  :for={toast <- @toasts}
  id={toast.id}
  initial={%{x: 48}}
  exit={%{x: 48}}
>
  {toast.text}
</.animated>
— 08

Tables, sorted in place.

Click a column header — rows FLIP to their new positions. Same <.animated> component, rendered as a <tr> via the as attribute.

Click any column header to toggle sort. Add a new row to watch it slot into place, or remove one with .

Caveat — a removed row collapses its cell padding but can't fully reach height: 0: <tr> elements derive their height from the tallest cell. Tables are unique in this regard — in any other container, the row would shrink all the way.

Brass hinge $12.50 38
Cedar plank $18.00 76
Cinder block $4.20 142
Drift magnet $31.90 9
Eclipse vial $88.00 4
table.heex .heex
<tbody>
  <.animated
    :for={row <- @rows}
    as="tr"
    id={row.id}
    exit={%{height: 0, opacity: 0}}
  >
    <td>{row.name}</td>
    <td>{row.price}</td>
    <td>{row.stock}</td>
  </.animated>
</tbody>
— 09

Smooth on long lists.

A searchable list of hundreds of rows, backed by a LiveView stream. Filtering drops or adds a lot of rows at once, and Shift keeps it smooth by only animating the rows on screen. The list stays responsive no matter how long it gets.

Search or flip starred only and the server re-streams the matches. Rows that drop out collapse away, rows that return slide back in, and the rest hold their place.

Track
Levels
Album
Rating
Plays
Time
Fever Machines
Marlow Quinn
Folk Shoegaze
Backroads
149
3:18
Signal Weather
The Hollow Coast
Post-Rock Electronic
Undertow
286
4:31
Ghost Tide
Ivy Sarto
Shoegaze Ambient
Nightshift
423
5:44
Ember Static
Halcyon Field
Ambient Dream-Pop
Long Exposure
560
2:57
Hollow Coast
Renny Vale
Folk Folk
Salt & Static
697
3:16
Golden Hours
Aster Kable
Post-Rock Jazz
Ghost Notes
834
4:29
Slow Notes
Low Ceiling
Shoegaze Post-Rock
Ember Room
971
5:42
Northern Lines
June Pallas
Ambient Indie
Low Country
1108
2:55
Quiet Glass
Wren & Vale
Folk Shoegaze
Backroads
1245
3:14
Lantern Year
Neon Atlas
Post-Rock Electronic
Undertow
1382
4:27
Cinder Field
Marlow Quinn
Shoegaze Ambient
Nightshift
1519
5:40
Bright Wire
The Hollow Coast
Ambient Dream-Pop
Long Exposure
1656
2:53
Pale Air
Ivy Sarto
Folk Folk
Salt & Static
1793
3:12
Cobalt Sky
Halcyon Field
Post-Rock Jazz
Ghost Notes
1930
4:25
Dust Gold
Renny Vale
Shoegaze Post-Rock
Ember Room
2067
5:38
Amber Room
Aster Kable
Ambient Indie
Low Country
2204
2:51
Salt Water
Low Ceiling
Folk Shoegaze
Backroads
2341
3:10
Velvet Bloom
June Pallas
Post-Rock Electronic
Undertow
2478
4:23
Wild Light
Wren & Vale
Shoegaze Ambient
Nightshift
2615
5:36
Paper Lanterns
Neon Atlas
Ambient Dream-Pop
Long Exposure
2752
2:49
Fever Machines
Marlow Quinn
Folk Folk
Salt & Static
2889
3:08
Signal Weather
The Hollow Coast
Post-Rock Jazz
Ghost Notes
3026
4:21
Ghost Tide
Ivy Sarto
Shoegaze Post-Rock
Ember Room
3163
5:34
Ember Static
Halcyon Field
Ambient Indie
Low Country
3300
2:47
Hollow Coast
Renny Vale
Folk Shoegaze
Backroads
3437
3:06
Golden Hours
Aster Kable
Post-Rock Electronic
Undertow
3574
4:19
Slow Notes
Low Ceiling
Shoegaze Ambient
Nightshift
3711
5:32
Northern Lines
June Pallas
Ambient Dream-Pop
Long Exposure
3848
2:45
Quiet Glass
Wren & Vale
Folk Folk
Salt & Static
3985
3:58
Lantern Year
Neon Atlas
Post-Rock Jazz
Ghost Notes
4122
4:17
Cinder Field
Marlow Quinn
Shoegaze Post-Rock
Ember Room
4259
5:30
Bright Wire
The Hollow Coast
Ambient Indie
Low Country
4396
2:43
Pale Air
Ivy Sarto
Folk Shoegaze
Backroads
4533
3:56
Cobalt Sky
Halcyon Field
Post-Rock Electronic
Undertow
4670
4:15
Dust Gold
Renny Vale
Shoegaze Ambient
Nightshift
4807
5:28
Amber Room
Aster Kable
Ambient Dream-Pop
Long Exposure
144
2:41
Salt Water
Low Ceiling
Folk Folk
Salt & Static
281
3:54
Velvet Bloom
June Pallas
Post-Rock Jazz
Ghost Notes
418
4:13
Wild Light
Wren & Vale
Shoegaze Post-Rock
Ember Room
555
5:26
Paper Lanterns
Neon Atlas
Ambient Indie
Low Country
692
2:39
Fever Machines
Marlow Quinn
Folk Shoegaze
Backroads
829
3:52
Signal Weather
The Hollow Coast
Post-Rock Electronic
Undertow
966
4:11
Ghost Tide
Ivy Sarto
Shoegaze Ambient
Nightshift
1103
5:24
Ember Static
Halcyon Field
Ambient Dream-Pop
Long Exposure
1240
2:37
Hollow Coast
Renny Vale
Folk Folk
Salt & Static
1377
3:50
Golden Hours
Aster Kable
Post-Rock Jazz
Ghost Notes
1514
4:09
Slow Notes
Low Ceiling
Shoegaze Post-Rock
Ember Room
1651
5:22
Northern Lines
June Pallas
Ambient Indie
Low Country
1788
2:35
Quiet Glass
Wren & Vale
Folk Shoegaze
Backroads
1925
3:48
Lantern Year
Neon Atlas
Post-Rock Electronic
Undertow
2062
4:07
Cinder Field
Marlow Quinn
Shoegaze Ambient
Nightshift
2199
5:20
Bright Wire
The Hollow Coast
Ambient Dream-Pop
Long Exposure
2336
2:33
Pale Air
Ivy Sarto
Folk Folk
Salt & Static
2473
3:46
Cobalt Sky
Halcyon Field
Post-Rock Jazz
Ghost Notes
2610
4:05
Dust Gold
Renny Vale
Shoegaze Post-Rock
Ember Room
2747
5:18
Amber Room
Aster Kable
Ambient Indie
Low Country
2884
2:31
Salt Water
Low Ceiling
Folk Shoegaze
Backroads
3021
3:44
Velvet Bloom
June Pallas
Post-Rock Electronic
Undertow
3158
4:57
Wild Light
Wren & Vale
Shoegaze Ambient
Nightshift
3295
5:16
Paper Lanterns
Neon Atlas
Ambient Dream-Pop
Long Exposure
3432
2:29
Fever Machines
Marlow Quinn
Folk Folk
Salt & Static
3569
3:42
Signal Weather
The Hollow Coast
Post-Rock Jazz
Ghost Notes
3706
4:55
Ghost Tide
Ivy Sarto
Shoegaze Post-Rock
Ember Room
3843
5:14
Ember Static
Halcyon Field
Ambient Indie
Low Country
3980
2:27
Hollow Coast
Renny Vale
Folk Shoegaze
Backroads
4117
3:40
Golden Hours
Aster Kable
Post-Rock Electronic
Undertow
4254
4:53
Slow Notes
Low Ceiling
Shoegaze Ambient
Nightshift
4391
5:12
Northern Lines
June Pallas
Ambient Dream-Pop
Long Exposure
4528
2:25
Quiet Glass
Wren & Vale
Folk Folk
Salt & Static
4665
3:38
Lantern Year
Neon Atlas
Post-Rock Jazz
Ghost Notes
4802
4:51
Cinder Field
Marlow Quinn
Shoegaze Post-Rock
Ember Room
139
5:10
Bright Wire
The Hollow Coast
Ambient Indie
Low Country
276
2:23
Pale Air
Ivy Sarto
Folk Shoegaze
Backroads
413
3:36
Cobalt Sky
Halcyon Field
Post-Rock Electronic
Undertow
550
4:49
Dust Gold
Renny Vale
Shoegaze Ambient
Nightshift
687
5:08
Amber Room
Aster Kable
Ambient Dream-Pop
Long Exposure
824
2:21
Salt Water
Low Ceiling
Folk Folk
Salt & Static
961
3:34
Velvet Bloom
June Pallas
Post-Rock Jazz
Ghost Notes
1098
4:47
Wild Light
Wren & Vale
Shoegaze Post-Rock
Ember Room
1235
5:06
Paper Lanterns
Neon Atlas
Ambient Indie
Low Country
1372
2:19
Fever Machines
Marlow Quinn
Folk Shoegaze
Backroads
1509
3:32
Signal Weather
The Hollow Coast
Post-Rock Electronic
Undertow
1646
4:45
Ghost Tide
Ivy Sarto
Shoegaze Ambient
Nightshift
1783
5:58
Ember Static
Halcyon Field
Ambient Dream-Pop
Long Exposure
1920
2:17
Hollow Coast
Renny Vale
Folk Folk
Salt & Static
2057
3:30
Golden Hours
Aster Kable
Post-Rock Jazz
Ghost Notes
2194
4:43
Slow Notes
Low Ceiling
Shoegaze Post-Rock
Ember Room
2331
5:56
Northern Lines
June Pallas
Ambient Indie
Low Country
2468
2:15
Quiet Glass
Wren & Vale
Folk Shoegaze
Backroads
2605
3:28
Lantern Year
Neon Atlas
Post-Rock Electronic
Undertow
2742
4:41
Cinder Field
Marlow Quinn
Shoegaze Ambient
Nightshift
2879
5:54
Bright Wire
The Hollow Coast
Ambient Dream-Pop
Long Exposure
3016
2:13
Pale Air
Ivy Sarto
Folk Folk
Salt & Static
3153
3:26
Cobalt Sky
Halcyon Field
Post-Rock Jazz
Ghost Notes
3290
4:39
Dust Gold
Renny Vale
Shoegaze Post-Rock
Ember Room
3427
5:52
Amber Room
Aster Kable
Ambient Indie
Low Country
3564
2:11
Salt Water
Low Ceiling
Folk Shoegaze
Backroads
3701
3:24
Velvet Bloom
June Pallas
Post-Rock Electronic
Undertow
3838
4:37
Wild Light
Wren & Vale
Shoegaze Ambient
Nightshift
3975
5:50
Paper Lanterns
Neon Atlas
Ambient Dream-Pop
Long Exposure
4112
2:09
Fever Machines
Marlow Quinn
Folk Folk
Salt & Static
4249
3:22
Signal Weather
The Hollow Coast
Post-Rock Jazz
Ghost Notes
4386
4:35
Ghost Tide
Ivy Sarto
Shoegaze Post-Rock
Ember Room
4523
5:48
Ember Static
Halcyon Field
Ambient Indie
Low Country
4660
2:07
Hollow Coast
Renny Vale
Folk Shoegaze
Backroads
4797
3:20
Golden Hours
Aster Kable
Post-Rock Electronic
Undertow
134
4:33
Slow Notes
Low Ceiling
Shoegaze Ambient
Nightshift
271
5:46
Northern Lines
June Pallas
Ambient Dream-Pop
Long Exposure
408
2:05
Quiet Glass
Wren & Vale
Folk Folk
Salt & Static
545
3:18
Lantern Year
Neon Atlas
Post-Rock Jazz
Ghost Notes
682
4:31
Cinder Field
Marlow Quinn
Shoegaze Post-Rock
Ember Room
819
5:44
Bright Wire
The Hollow Coast
Ambient Indie
Low Country
956
2:57
Pale Air
Ivy Sarto
Folk Shoegaze
Backroads
1093
3:16
Cobalt Sky
Halcyon Field
Post-Rock Electronic
Undertow
1230
4:29
Dust Gold
Renny Vale
Shoegaze Ambient
Nightshift
1367
5:42
Amber Room
Aster Kable
Ambient Dream-Pop
Long Exposure
1504
2:55
Salt Water
Low Ceiling
Folk Folk
Salt & Static
1641
3:14
Velvet Bloom
June Pallas
Post-Rock Jazz
Ghost Notes
1778
4:27
Wild Light
Wren & Vale
Shoegaze Post-Rock
Ember Room
1915
5:40
Paper Lanterns
Neon Atlas
Ambient Indie
Low Country
2052
2:53
Fever Machines
Marlow Quinn
Folk Shoegaze
Backroads
2189
3:12
Signal Weather
The Hollow Coast
Post-Rock Electronic
Undertow
2326
4:25
Ghost Tide
Ivy Sarto
Shoegaze Ambient
Nightshift
2463
5:38
Ember Static
Halcyon Field
Ambient Dream-Pop
Long Exposure
2600
2:51
Hollow Coast
Renny Vale
Folk Folk
Salt & Static
2737
3:10
Golden Hours
Aster Kable
Post-Rock Jazz
Ghost Notes
2874
4:23
Slow Notes
Low Ceiling
Shoegaze Post-Rock
Ember Room
3011
5:36
Northern Lines
June Pallas
Ambient Indie
Low Country
3148
2:49
Quiet Glass
Wren & Vale
Folk Shoegaze
Backroads
3285
3:08
Lantern Year
Neon Atlas
Post-Rock Electronic
Undertow
3422
4:21
Cinder Field
Marlow Quinn
Shoegaze Ambient
Nightshift
3559
5:34
Bright Wire
The Hollow Coast
Ambient Dream-Pop
Long Exposure
3696
2:47
Pale Air
Ivy Sarto
Folk Folk
Salt & Static
3833
3:06
Cobalt Sky
Halcyon Field
Post-Rock Jazz
Ghost Notes
3970
4:19
Dust Gold
Renny Vale
Shoegaze Post-Rock
Ember Room
4107
5:32
Amber Room
Aster Kable
Ambient Indie
Low Country
4244
2:45
Salt Water
Low Ceiling
Folk Shoegaze
Backroads
4381
3:58
Velvet Bloom
June Pallas
Post-Rock Electronic
Undertow
4518
4:17
Wild Light
Wren & Vale
Shoegaze Ambient
Nightshift
4655
5:30
Paper Lanterns
Neon Atlas
Ambient Dream-Pop
Long Exposure
4792
2:43
Fever Machines
Marlow Quinn
Folk Folk
Salt & Static
129
3:56
Signal Weather
The Hollow Coast
Post-Rock Jazz
Ghost Notes
266
4:15
Ghost Tide
Ivy Sarto
Shoegaze Post-Rock
Ember Room
403
5:28
Ember Static
Halcyon Field
Ambient Indie
Low Country
540
2:41
Hollow Coast
Renny Vale
Folk Shoegaze
Backroads
677
3:54
Golden Hours
Aster Kable
Post-Rock Electronic
Undertow
814
4:13
Slow Notes
Low Ceiling
Shoegaze Ambient
Nightshift
951
5:26
Northern Lines
June Pallas
Ambient Dream-Pop
Long Exposure
1088
2:39
Quiet Glass
Wren & Vale
Folk Folk
Salt & Static
1225
3:52
Lantern Year
Neon Atlas
Post-Rock Jazz
Ghost Notes
1362
4:11
Cinder Field
Marlow Quinn
Shoegaze Post-Rock
Ember Room
1499
5:24
Bright Wire
The Hollow Coast
Ambient Indie
Low Country
1636
2:37
Pale Air
Ivy Sarto
Folk Shoegaze
Backroads
1773
3:50
Cobalt Sky
Halcyon Field
Post-Rock Electronic
Undertow
1910
4:09
Dust Gold
Renny Vale
Shoegaze Ambient
Nightshift
2047
5:22
Amber Room
Aster Kable
Ambient Dream-Pop
Long Exposure
2184
2:35
Salt Water
Low Ceiling
Folk Folk
Salt & Static
2321
3:48
Velvet Bloom
June Pallas
Post-Rock Jazz
Ghost Notes
2458
4:07
Wild Light
Wren & Vale
Shoegaze Post-Rock
Ember Room
2595
5:20
Paper Lanterns
Neon Atlas
Ambient Indie
Low Country
2732
2:33
Fever Machines
Marlow Quinn
Folk Shoegaze
Backroads
2869
3:46
Signal Weather
The Hollow Coast
Post-Rock Electronic
Undertow
3006
4:05
Ghost Tide
Ivy Sarto
Shoegaze Ambient
Nightshift
3143
5:18
Ember Static
Halcyon Field
Ambient Dream-Pop
Long Exposure
3280
2:31
Hollow Coast
Renny Vale
Folk Folk
Salt & Static
3417
3:44
Golden Hours
Aster Kable
Post-Rock Jazz
Ghost Notes
3554
4:57
Slow Notes
Low Ceiling
Shoegaze Post-Rock
Ember Room
3691
5:16
Northern Lines
June Pallas
Ambient Indie
Low Country
3828
2:29
Quiet Glass
Wren & Vale
Folk Shoegaze
Backroads
3965
3:42
Lantern Year
Neon Atlas
Post-Rock Electronic
Undertow
4102
4:55
Cinder Field
Marlow Quinn
Shoegaze Ambient
Nightshift
4239
5:14
Bright Wire
The Hollow Coast
Ambient Dream-Pop
Long Exposure
4376
2:27
Pale Air
Ivy Sarto
Folk Folk
Salt & Static
4513
3:40
Cobalt Sky
Halcyon Field
Post-Rock Jazz
Ghost Notes
4650
4:53
Dust Gold
Renny Vale
Shoegaze Post-Rock
Ember Room
4787
5:12
Amber Room
Aster Kable
Ambient Indie
Low Country
124
2:25
Salt Water
Low Ceiling
Folk Shoegaze
Backroads
261
3:38
Velvet Bloom
June Pallas
Post-Rock Electronic
Undertow
398
4:51
Wild Light
Wren & Vale
Shoegaze Ambient
Nightshift
535
5:10
Paper Lanterns
Neon Atlas
Ambient Dream-Pop
Long Exposure
672
2:23
Fever Machines
Marlow Quinn
Folk Folk
Salt & Static
809
3:36
Signal Weather
The Hollow Coast
Post-Rock Jazz
Ghost Notes
946
4:49
Ghost Tide
Ivy Sarto
Shoegaze Post-Rock
Ember Room
1083
5:08
Ember Static
Halcyon Field
Ambient Indie
Low Country
1220
2:21
Hollow Coast
Renny Vale
Folk Shoegaze
Backroads
1357
3:34
Golden Hours
Aster Kable
Post-Rock Electronic
Undertow
1494
4:47
Slow Notes
Low Ceiling
Shoegaze Ambient
Nightshift
1631
5:06
Northern Lines
June Pallas
Ambient Dream-Pop
Long Exposure
1768
2:19
Quiet Glass
Wren & Vale
Folk Folk
Salt & Static
1905
3:32
Lantern Year
Neon Atlas
Post-Rock Jazz
Ghost Notes
2042
4:45
Cinder Field
Marlow Quinn
Shoegaze Post-Rock
Ember Room
2179
5:58
Bright Wire
The Hollow Coast
Ambient Indie
Low Country
2316
2:17
Pale Air
Ivy Sarto
Folk Shoegaze
Backroads
2453
3:30
Cobalt Sky
Halcyon Field
Post-Rock Electronic
Undertow
2590
4:43
Dust Gold
Renny Vale
Shoegaze Ambient
Nightshift
2727
5:56
Amber Room
Aster Kable
Ambient Dream-Pop
Long Exposure
2864
2:15
Salt Water
Low Ceiling
Folk Folk
Salt & Static
3001
3:28
Velvet Bloom
June Pallas
Post-Rock Jazz
Ghost Notes
3138
4:41
Wild Light
Wren & Vale
Shoegaze Post-Rock
Ember Room
3275
5:54
Paper Lanterns
Neon Atlas
Ambient Indie
Low Country
3412
2:13
200 tracks
library.ex .heex
def handle_event("toggle-starred", _, socket) do
  {:noreply,
   socket
   |> update(:starred_only, &(!&1))
   |> stream(:tracks, visible(socket.assigns), reset: true)}
end

<div id="tracks" phx-update="stream">
  <.animated
    :for={{dom_id, track} <- @streams.tracks}
    as="div"
    id={dom_id}
    exit={%{height: 0, opacity: 0}}
  >
    {track.title}
  </.animated>
</div>
— 10

Cards across columns.

Moving a DOM node between parents is normally a destroy-and-rebuild — the kind of thing that breaks every in-flight animation. Shift preserves the node and carries the motion straight across the parent change.

Use and on any card to send it between columns. Add a new Todo, or drop one with .

Todo
Draft hero copy
Tune spring params
Wire kanban demo
Doing
Polish typography
Done
Set up Fly app
kanban.heex .heex
<section :for={col <- @columns}>
  <h3>{col.name}</h3>
  <.animated :for={card <- col.cards} id={card.id}>
    {card.title}
  </.animated>
</section>
— 11

Mini to hero.

A 48px album art in the bottom bar; the same DOM node becomes a 200px hero when the player expands. Shift carries it across the parent change, animating position, size, and corner radius on one spring. This is the shared-element transition you've felt on every iOS app.

Tap the mini-player. The album art is the same DOM node in both states — Shift carries it from the 48px thumbnail in the bottom bar to the 200px hero of the full view, animating position, size, and corner radius together. Title swaps scale on the same spring; the rest of the chrome enters and exits around them.

player.heex .heex
<%= if @expanded do %>
  <div class="full-player">
    <.animated id="player-art" class="size-[200px] rounded-2xl" />
    <.animated id="player-title" class="text-[28px]">
      {song.title}
    </.animated>
    <p>{song.artist}</p>
    <progress />
    <div class="controls">⏮ ▶ ⏭</div>
  </div>
<% else %>
  <button class="mini-bar" phx-click="expand">
    <.animated id="player-art" class="size-12 rounded-md" />
    <.animated id="player-title" class="text-[15px]">
      {song.title}
    </.animated>
    <span class="play-icon"></span>
  </button>
<% end %>

Three ideas, baked in.

Why Shift exists
— 01

Drop in.
It animates.

Wrap an element in <.animated> and Shift takes over. Sensible defaults handle the common cases — fade in on enter, fade out on exit, FLIP on position change. Pass props only when you want to deviate.

— 02

Springs over keyframes.

Shift uses a real spring solver — stiffness, damping, mass. Where keyframe animations restart on interruption and snap on redirection, springs blend velocities and flow continuously into whatever new state arrives.

— 03

Animation is a side effect.

Your LiveView re-renders for the reasons it always does — data changed. Shift turns that DOM diff into motion. You write Elixir; the animation falls out.

Three steps. That's it.

Add the dep, import the JS, render the component. Shift carries its own runtime — there's nothing else to wire.

i.
# mix.exs
{:shift, "~> 0.2.1"}
ii.
// assets/js/app.js
import { init as initShift } from "shift"
initShift()
iii.
# your_live.heex
<.animated> ... </.animated>