Andy from Webcrunch

Subscribe for email updates:

Portrait of Andy Leverenz
Andy Leverenz

February 12, 2024

Last updated February 12, 2024

Digging into View Transitions with Turbo 8 and Rails

This is a super quick tutorial to show you the new ViewTransition API that ships with Turbo 8, which was released recently.

View Transitions are a neat way to add more fluidity between views in your app. With Rails and Turbo 8, adding this effect is pretty straightforward. The most challenging obstacle is understanding the CSS side of the equation.

Let’s dive in and start experimenting.

Create a new Rails app

I’ll use a basic Rails app with esbuild for JavaScript. We won’t use JavaScript in this tutorial, so feel free to use the defaults.

rails new turbo_view_transitions -j esbuild

To save time, I’ll add the tailwind-rails gem to get scaffold UI out of the box.

bundle add tailwindcss-rails
rails tailwindcss:install

With this out of the way we can generate a new Post scaffold with some demo columns.

rails g scaffold Post title:string body:text

Update your root route to posts#index

# config/routes.rb
Rails.application.routes.draw do
  resources :posts  
  get "up" => "rails/health#show", as: :rails_health_check
  root "posts#index"
end

Modifying the Rails app main layout

To use view transitions with Turbo 8, add a new meta tag to your layout’s <head></head> tags. <meta name="view-transition" content="same-origin" />

<!-- app/views/layouts/application.html.erb-->
  <!DOCTYPE html>
<html>
  <head>
    <title>TurboViewTransitions</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="view-transition" content="same-origin" />

    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
  </head>

  <body>
    <main class="container mx-auto mt-10 px-5 flex">
      <%= yield %>
    </main>
  </body>
</html>

Here’s my modified layout. The main change is the new meta tag <meta name="view-transition" content="same-origin" />. I also changed the main tag to use a smaller margin at the top of the page.

Modifying the posts UI

In the app/views/posts/index.html.erb I added some new markup to help make this demo more visually appealing.

<div class="w-full">
  <% if notice.present? %>
    <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
  <% end %>

  <div class="grid grid-cols-12 gap-16">
    <div class="col-span-8">
      <div class="flex justify-between items-center">
        <h1 class="font-bold text-4xl">Posts</h1>
        <%= link_to "New post", new_post_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
      </div>

      <div id="posts">
        <%= render @posts %>
      </div>
    </div>

    <aside class="col-span-4 bg-blue-600 rounded-lg text-white p-10">
      <h3 class="font-semibold text-3xl sidebar-title">Sidebar title</h3>
      <p>This is my sidebar</p>
    </aside>
  </div>
</div>

Consider this a basic blog with a sidebar. We’ll leverage view transitions to make it snazzy.

Add custom view transition CSS

Now for the exciting part. Let's add some view transition effects starting from simple to more complex.

Simple global transitions (easy)

You may be interested in a slight animation on every view transition. To do that, you could add a couple of lines of CSS to app/assets/stylesheets/application.tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

/* Global slight fade */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.5s;
}

Target the root of the page directly and add a global transition.

How about different types of animations?

Want to get a little more sophisticated? Try these styles that offer some slide and fade effects all in one.

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}

@keyframes slide-from-right {
  from {
    transform: translateX(60px);
  }
}

@keyframes slide-up {
  from {
    transform: translateY(130px);
  }
}

@keyframes slide-to-left {
  to {
    transform: translateX(-30px);
  }
}

/* Global but more fancy */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

Singling out elements and combining transitions

Say you want to target independent elements directly but also have global transition effects. That’s possible. Here’s my code thus far.

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}

@keyframes slide-from-right {
  from {
    transform: translateX(60px);
  }
}

@keyframes slide-up {
  from {
    transform: translateY(130px);
  }
}

@keyframes slide-to-left {
  to {
    transform: translateX(-30px);
  }
}

/* Global but more fancy */
::view-transition-old(root) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}

::view-transition-new(root) {
  animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
    300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}

/* Sidebar */
aside {
  view-transition-name: sidebar;
}

html[data-turbo-visit-direction="forward"]::view-transition-new(sidebar) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-in,
    210ms cubic-bezier(0.4, 0, 0.2, 1) both slide-up;
}

With turbo, we’re able to target directions. And in doing so, you can even tie into either the ::view-transition-new or ::view-transition-old states of the transition. With some gnarly CSS, you can target elements independently of one another and perform some wacky animations.

Notice how, much like CSS Grid, you can name “areas” to define layouts. In this specific case, you can use view-transition-name to name an element in the dom for use in other targeted CSS.

So I used

/* Sidebar */
aside {
  view-transition-name: sidebar;
}

This targets the aside element in app/views/posts/index.html.erb and gives us a way to define a new transitional style based on the turbo directions. Here's the example from before with that in action. Notice how our named view transition is "sidebar.”

html[data-turbo-visit-direction="forward"]::view-transition-new(sidebar) {
  animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-in,
    210ms cubic-bezier(0.4, 0, 0.2, 1) both slide-up;
}

Hooking into Turbo

Turbo adds a data-turbo-visit-direction attribute to the <html> element to indicate the direction of the transition. The attribute can have one of the following values:

  • forward in advance visits.
  • back in restoration visits.
  • none in replace visits.

If you view the source when clicking a link or button, the HTML element will quickly update with the new data attributes depending on the request. As a result, your CSS can capture the forward, backward, and none directions to manipulate the elements on the page.

Browser Compatibility

Only Chrome and Microsoft Edge are currently supported, but I anticipate Safari and Firefox to follow soon. Keep this in mind as you add this to your apps.

What's the real-world use case for view transitions?

Having toyed with View Transitions, these will be extremely popular for folks leveraging Rails to build mobile apps. It allows you to do less with more native versus non-native apps. We see Turbo in use on the Basecamp team's apps already today, and it's about to get more familiar with other apps.

This is all not to say web apps or progressive web apps shouldn't leverage this feature. It will be overused to some degree. I remember when the trend of animating everything on a marketing website was popular. You couldn't scroll without something moving in or out of view. I worry we're in for that again...

We’ve only scratched the surface

This tutorial is super basic, but I hope it sheds some light on what’s possible with a little dash of CSS and tools you’re already using with Ruby on Rails. Among many new features, Turbo 8 brings a dash of life with View Transitions. Rails apps are about to get more pleasing to use, and I’m excited and here for it!

Links to all things View Transitions

Link this article
Est. reading time: 7 minutes
Stats: 2,597 views

Collection

Part of the Hotwire and Rails collection

Products and courses