Conditionally Style Turbo Frame Content

Rails Designer - Sep 19 - - Dev Community

This article was originally published on Rails Designer


I like to explore a little technique I have been using for some time to reuse HTML and change it styles based if it is shown in a turbo-frame or not.

For inspiration I am going to use Letterboxd log in flow.

Image description

I assume you have an up-to-date Rails app ready with Hotwire set up. This example is also using Tailwind CSS.

Let's create the navigation component first (partial at app/views/shared/_navigation.html.erb).

<div class="w-full max-w-2xl py-40 mx-auto">
  <nav class="flex items-center justify-between px-6 py-2.5 bg-white/70 backdrop-blur-md rounded-md shadow-xl ring-1 ring-offset-0 ring-gray-200">
    <%= link_to "Rails Designer", "#", class: "font-bold tracking-tight text-gray-900" %>

      <ul class="flex items-center gap-6">
        <li>
          <%= link_to "Log In", new_session_path, class: "text-sm font-medium text-gray-900 hover:text-sky-600" %>
        </li>

        <li>
          <%= link_to "Pricing", "/pricing/", class: "text-sm font-medium text-gray-900 hover:text-sky-600" %>
        </li>

        <li>
          <%= link_to "Docs", "/docs/", class: "text-sm font-medium text-gray-900 hover:text-sky-600" %>
        </li>
      </ul>
  </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

It should look like this:

Image description

Then the log in form. Let's create it at app/sessions/new.html.erb. Make sure you have added resources :sessions, only: %w[new] in config/routes.rb.

<div class="max-w-xl px-3 py-4 mx-auto border border-gray-100 rounded-lg">
  <%= turbo_frame_tag "login" do %>
    <%= form_with model: Session.new, url: sessions_path, class: "flex flex-col gap-y-4" do |form| %>
      <div>
        <%= form.label "email", class: "text-sm text-gray-700 font-medium" %>

        <%= form.email_field :email, placeholder: t("placeholders.email"), autocomplete: "email", class: "w-full px-3 py-1.5 text-sm text-gray-800 bg-gray-50 border-0 outline outline-1 outline-offset-0 outline-gray-100 rounded-md" %>
      </div>

      <div>
        <%= form.label "password", class: "text-sm text-gray-700 font-medium" %>

        <%= form.password_field :password, autocomplete: "current-password", placeholder: "•••••••••••••", class: "w-full px-3 py-1.5 text-sm text-gray-800 bg-gray-50 border-0 outline outline-1 outline-offset-0 outline-gray-100 rounded-md" %>
      </div>

      <div>
        <%= form.submit "Log In" %>
      </div>
    <% end %>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

So now when you click the Log In button in the navigation you are directed to the sessions#new view.

Image description

With these basics in place, it's time to add the good stuff.

First wrap the form in a turbo-frame tag.

<div class="w-full max-w-2xl py-40 mx-auto">
  <nav class="flex items-center justify-between px-6 py-2.5 bg-white/70 backdrop-blur-md rounded-md shadow-xl ring-1 ring-offset-0 ring-gray-200">
    <%= link_to "Rails Designer", "#", class: "font-bold tracking-tight text-gray-900" %>

    <%= turbo_frame_tag "login" do %>
      <ul class="flex items-center gap-6">
        <!-- … -->
      </ul>
    <% end %>
  </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

And also wrap the form in a turbo-frame.

<div class="max-w-xl px-3 py-4 mx-auto border border-gray-100 rounded-lg">
  <%= turbo_frame_tag "login" do %>
    <%= form_with model: Session.new, url: sessions_path, class: "" do |form| %>
      <%#  %>
    <% end %>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

Now when you click the Log In button it loads the form inline. Close, but it looks like pretty bad!

Image description

So let's make some tweaks to the form to fix that. But before that, a custom Tailwind CSS plugin is needed. No worries, it's really simple.

// tailwind.config.js
module.exports = {
  plugins: [
    // …
    function ({ addVariant }) {
      addVariant("turbo-frame", "turbo-frame[src] &")
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This adds a custom CSS variant named turbo-frame that targets elements within a Turbo Frame with a src attribute. This means HTML can now be styled based if its within a turbo-frame, like so shadow turbo-frame:shadow-none.

Let's tweak the sessions#new to use this new plugin.

<div class="max-w-xl px-3 py-4 mx-auto border border-gray-100 rounded-lg">
  <%= turbo_frame_tag "login" do %>
    <%= form_with model: Session.new, url: sessions_path, class: "flex flex-col gap-y-4 gap-x-2 turbo-frame:flex-row turbo-frame:items-end" do |form| %>
      <div>
        <%= form.label "email", class: "text-sm text-gray-700 font-medium turbo-frame:sr-only" %>

        <%= form.email_field :email, placeholder: t("placeholders.email"), autocomplete: "email", class: "w-full px-3 py-1.5 text-sm text-gray-800 bg-gray-50 border-0 outline outline-1 outline-offset-0 outline-gray-100 rounded-md" %>
      </div>

      <div>
        <%= form.label "password", class: "text-sm text-gray-700 font-medium turbo-frame:sr-only" %>

        <%= form.password_field :password, autocomplete: "current-password", placeholder: "•••••••••••••", class: "w-full px-3 py-1.5 text-sm text-gray-800 bg-gray-50 border-0 outline outline-1 outline-offset-0 outline-gray-100 rounded-md" %>
      </div>

      <div>
        <%= form.submit "Log In" %>
      </div>
    <% end %>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

And now when you click Log In the inline form looks quite different compared to the standalone screen. Neat right?

Image description

This example only shows some minimal tweaks made to the form, from putting the fields on one row to hiding the form labels (for non-screen readers), but I think you can imagine the amount of tweaks you could make.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player