Available Now JavaScript for Rails Developers

Introducing Turbo Transition: create smoother Turbo Streams

Ever wondered how to add more joy to components and partials injected or removed from the DOM? Something like this:

Previously I used a solution that relied on event callbacks from turbo. It did its job, but I was never really happy with the solution nor with its usage.

So I am introducing: Turbo Transition: A “minion” for Turbo-Frames and Streams that transitions elements as they enter or leave the DOM. ✨ A way simpler, but equally powerful solution than what I had before.

Since I’ve been working actively on Rails Designers I have been exploring all kinds of interesting techniques. Turbo Transition, just like turbo-frame and turbo-stream is nothing more than a custom element that adds and removes defined CSS classes.

Check it out for your nextcurrent Turbo-powered app.

Interested how Turbo Transition and custom elements work? Continue reading. 👇

How Turbo Transitions works

Turbo Transition is nothing more than a custom element. They let you create your own HTML tags with built-in behavior (like extending HTML’s vocabulary). Here’s the minimal setup:

class MyElement extends HTMLElement {
  // Called when element is added to the page
  connectedCallback() {
    // Element is now in the DOM
  }

  // Called when element is removed
  disconnectedCallback() {
    // Clean up time
  }
}

// Register so browsers know about the new element
customElements.define("my-element", MyElement);

Turbo Transition builds on this foundation to handle animations when elements enter or leave the page:

class TurboTransition extends HTMLElement {
  connectedCallback() {
    // Handle enter animations
  }

  remove() {
    // Handle leave animations
  }
}

customElements.define("turbo-transition", TurboTransition);

From here, it manages transition classes, timing, and cleanup.

class TurboTransition extends HTMLElement {
  connectedCallback() {
    if (this.#config.hasEnterTransition()) {
      this.#enter();
    }
  }

  remove() {
    if (this.#config.hasLeaveTransition()) {
      this.#leave();

      return this;
    }

    return super.remove();
  }
}

The transition process is straightforward:

async #transition({ to, element = null }) {
  const target = element || this.firstElementChild;

  if (!target) return;

  const classes = this.#config.getClasses({ for: to });

  await this.#utilities.run(target, classes);
}
  1. Get the target element (Turbo Transition requires one child element inside it);
  2. Fetch the relevant classes from attributes (eg. enter-from-class="fade-enter-from");
  3. Run the transition sequence.

The remove() method uses cloning to keep the animation visible while the original element is removed:

#leave() {
  const clone = this.cloneNode(true);
  const parent = this.parentNode;

  parent.replaceChild(clone, this);

  // Run transition on the clone's content
  this.#transition({ to: "leave", element: clone.firstElementChild })
    .then(() => clone.parentNode?.removeChild(clone));
}

Classes are applied in a specific sequence to create smooth transitions:

// utilities.js
async run(element, classes) {
  this.#applyInitialState(element, classes); // Add 'from' + 'active'

  await this.#nextFrame(); // Wait for browser

  this.#applyFinalState(element, classes); // Switch to 'to' state

  await this.#waitForCompletion(element); // Wait for animation

  this.#cleanup(element, classes); // Remove classes
}

This creates a reliable way to add smooth transitions to your components and elements that works seamlessly with Turbo Frames and Turbo Streams.

Check it out and give it that star! ⭐

Published at . Have suggestions or improvements on this content? Do reach out.

More articles like this on modern Rails & frontend? Get them first in your inbox.
JavaScript for Rails Developers
Out now

UI components Library for Ruby on Rails apps

$ 99 one-time
payment

View components