Digging into View Transitions with Turbo 8 and Rails
Tags: ruby, rails, turbo
Published: 2024-03-05 19:35:30

---
title: Digging into View Transitions with Turbo 8 and Rails
published: true
date: 2024-02-12 17:03:25 UTC
tags: ruby, rails, turbo
canonical_url: https://webcrunch.com/posts/view-transitions-rails
---

{% youtube gdePV0Beihg %}

This is a super quick tutorial to show you the new ViewTransition API that ships with Turbo 8, 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.

```bash
rails new turbo_view_transitions -j esbuild
```

To save time, I’ll add the [tailwind-rails gem](https://github.com/rails/tailwindcss-rails) to get scaffold UI out of the box.

```bash
bundle add tailwindcss-rails
rails tailwindcss:install
```

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

```bash
rails g scaffold Post title:string body:text
```

Update your root route to `posts#index`

```ruby
# 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" />`

```html
<!-- 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.

```html
<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`

```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.

```css
@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.

```css
@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

```css
/* 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.”

```css
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.

Depending on the request, if you view the source when clicking a link or button, the HTML element will quickly update with the new data attributes. 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, and I’m excited and here for it!

#### Links

- [Hotwired Dev - Turbo View Transitions](https://turbo.hotwired.dev/handbook/drive#view-transitions)
- [MDN - View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
- [Chrome Developer Article on View Transitions](https://developer.chrome.com/docs/web-platform/view-transitions)
- [Turbo on Github](https://github.com/hotwired/turbo)
- [Learn Ruby on Rails](https://hellorails.io)