Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 26, 2022 01:00 pm GMT

Galaxy brain CSS tricks with Hotwire and Rails

This post is part of Hotwire Summer: a new season of content on Boring Rails!

In Hotwire applications, you need to lean more on the fundamentals of CSS and HTML. If youre like me, you probably learned just enough CSS to get by, but never reach for it first. But thats changed recently and I wanted to share patterns Ive picked up recently that improve my Rails apps.

Empty States and Turbo Streams

An extremely common pattern in Rails apps is rendering a collection of elements and if the collection is empty, render an empty state.

<div id="my_list" class="flex flex-col divide-y">  <% if @list.size > 0 %>    <%= render partial: "list_item", collection: @list %>  <% else %>    <p>      Whoops! you have no items!    </p>  <% end %></div>

This works fine when rendering a typical page, but if you use Turbo Streams to add or remove list items, youll find a problem.

If you had two items in the list, and you remove both via Turbo Streams, the container will be empty but you wont have rendered the empty state. And if the list is empty and you dynamically append an item, youll want to remove the empty state.

You could re-render the whole list instead of inserting items, but one technique that Ive found helpful is using the CSS only-child pseudo-selector.

Ill show examples with Tailwind (because Tailwind is really, really good), but the same concept applies in regular CSS.

The idea is to always render the empty state and then use CSS to only show it if there are no items.

<div id="my_list" class="flex flex-col divide-y">  <p class="only:block hidden">Whoops! you have no items!</p>  <%= render partial: "list_item", collection: @list %></div>

Using Tailwinds only modifier we set the empty state to have display block if it is the only child of the container, otherwise hide it.

Now you can stream back operations to append or remove items to the my_list container and let CSS handle hiding or showing the loading state.

Note: you may want to use last-child, first-of-type, or some other modifier depending on your specific markup. Give it a shot!

Tailwind Variants with Data Attributes

Hotwire, especially Stimulus, makes use of HTML data attributes heavily. One neat trick is using data attributes to reduce conditionals in views.

Lets say you have a list of comments and only admins can delete the comments.

<%= tag.div id: dom_id(@comment) do %>  <p><%= @comment.body %></p>  <% if Current.user.admin? %>    <%= button_to "Delete", @comment, method: :delete %>  <% end %><% end %>

You could instead use a data attribute on the <body> of your page to conditionally show admin-related things.

<body <%= 'data-admin' if Current.user.admin? %>>  ...</body>

And then write styles based on that attribute. Tailwind makes it easy to add custom variants for this via the plugins section of the Tailwind config file:

plugins: [  function({addVariant}) {    addVariant('admin', 'body[data-admin] &')  }],

With this config change you can now use admin: with any Tailwind classes.

<%= tag.div id: dom_id(@comment) do %>  <p><%= @comment.body %></p>  <div class="admin:block hidden">    <%= button_to "Delete", @comment, method: :delete %>  </div><% end %>

But wait? Isnt this super risky because someone could just fiddle with the HTML and delete a comment? Well, yes, they could but you need to be checking authorization on the server-side anyways. Its a trade-off but there are cases where this cleans up a ton of conditional view logic.

Shout-out to my friend Marc Kohlbrugge for sharing the idea on Twitter!

In our app we recently used this technique to change how much margin we needed on an element based on user roles. Certain roles have a fixed height header that we needed to account for. Instead of a bunch of conditionals, all we had to end up writing was viewer:top-0 editor:top-12.

Dynamic styles with erb

Dont forget that you can use generate <style> tags dynamically!

<style>  [data-user~="<%= Current.user.id %>"] {    background: yellow;  }</style><div>  <p><%= @comment.body %></p>  <%= tag.span data: { user: @comment.author.id } do %>    <%= @comment.author.name %>  <% end %></div>

You could use a little CSS to highlight comments that you made with a yellow background by targeting a data-user attribute.

This is actually an old Rails technique from Basecamp that was popular because it works really well with fragment caching. You can cache the same chunk of HTML and then use CSS to change the styles instead of needing multiple, slightly different cache entries.

Ive also used this concept for building custom theming features by taking advantage of CSS variables.

<style>  :root {    --color-brand: <%= @account.brand_color %>;    --color-brand-contrast: <%= ColorHelper.contrast(@account.brand_color)) %>;    --color-brand-tint: <%= ColorHelper.tint(@account.brand_color) %>;  }</style>

You can use this to define custom Tailwind colors:

module.exports = {  theme: {    extend: {      colors: {        brand: 'rgb(var(--color-brand) / <alpha-value>)',        'brand-contrast': 'rgb(var(--color-brand-contrast) / <alpha-value>)',        'brand-tint': 'var(--color-brand-tint)'      }    }  }}

And now you can use bg-brand or text-brand-contrast in your application.

Stop string interpolating class names!

You will be writing a lot more HTML markup in Hotwire apps: more views, more partials, more components. Make sure you are taking advantage of newer Rails features for generating HTML without doing a bunch of gross string interpolation.

If youre writing markup like this:

<li class="bg-gray-50 p-2 text-gray-700 <%= 'line-through' if @task.completed? %>">  <%= @task.name %></li>

Please stop! Its hard to read and tricky to match all of the closing punctuation. There are better ways!

<%= tag.li class: ["bg-gray-50 p-2 text-gray-700", "line-through": @task.completed?] do %>  <%= @task.name %><% end %>

Rails 6.1 added a class_names helper method and the tag builder will automatically use it for conditionally setting class values. Its awesome!

Its extra powerful when using a library like ViewComponent where you have a lot of conditional styles, or just want to group up utility classes in a more organized manner.

class MyWidget < ViewComponent::Base  ...  def container_classes    [      "flex items-center justify-center space-x-2 rounded-full",      "disabled:pointer-events-none disabled:select-none",      "font-medium tracking-wide",      {"text-white bg-black hover:bg-neutral-900": variant == :primary},      {"text-neutral-600 border hover:bg-neutral-50 hover:text-neutral-900": variant == :secondary},      {"text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50": variant == :tertiary},      {"w-full": full_width?}    ]  endend

Wrap it up

CSS is often one of the last tools Rails developers reach for when trying to solve a tricky problem. We are much more inclined to add conditionals to views or fall back to string interpolation to make it work. But there are a few techniques that can make working with CSS in your Rails app improve the readability and durability of your code.

Even though Hotwires servered rendered approach feels retro, remember that we dont have to use CSS like its 1998 anymore!


Original Link: https://dev.to/swanson/galaxy-brain-css-tricks-with-hotwire-and-rails-2eoa

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To