Survey how to approach converting from HTML templates to Rails views structures

Hi! Taking the template examples from Tailwind UI - Official Tailwind CSS Components, what are the strategies people are using to avoid repetition of HTML and also keep sanity when the templates are updated?

I’ve been trying to play around with them, but either I end up with very verbose templates that will have tons of repeated HTML (both classes and attributes) or if I decide to extract to helpers or partials, I fear when the template gets updated I will have a hard time to update, lowering the benefit of having these templates.

What have been the techniques you have found useful to minimize duplication and avoid future template upgrade headaches?

Note: I’ve been doing backend work only for several years now.

2 Likes

I am not really sure what you are asking for…

  • If you are asking how to reuse code in Rails, there are actually 2 main strategies Using Partials Using Helpers This will limit the duplication of code.

  • However, since your question seems related to Tailwind… Tailwind is extremely verbose (and heavy) on purpose. If you want something more concise you should probably go in the direction of Boostrap. This being said, Tailwind comes with tools to simplify the classes @apply directive but if you go in this direction you probably aren’t made for Tailwind and should probably go in the direction of another framework like Boostrap or Mantine

Finally, depending on the template upgrade (in Tailwind or anything), its complexity will depend a lot on the content of the upgrade… and only a little on your code itself.

Thank you @Cedric_Lefebvre , to clarify my question, is not only about re-use, as I’m well aware of the views, partials and layouts. It’s exactly reducing the amount of duplication or verboseness of the templates.

There is no absolute answer to your question. In Rails, you have some tools like the use of partials to factorize code in the erb files. But if each of your view contains 10 partials, the code becomes unreadable and hard to maintain.

And as I wrote above, Taillwind is a good example of why you would want verbose code.

Not trying to derail this conversation, and not trying to dig on Tailwind per se, but I have a basic question about using “verbose” CSS. If you have designed your page by fiddling with the combination of CSS classes added to individual elements in that page, how do you change that design later without touching every single element on every single layout, template, and partial?

If you have manually added mb-3 to every element that needs space below it, and the client asks you to change that for most, but not all cases, how do you make that change? Assume, for the exercise, that there are other completely orthogonal uses of mb-3 elsewhere in the site.

Now expand that to include font color, size, etc. Classnames can legally appear in any order, so you can’t just grep for a particular combination of them to make a sweeping change.

Contrast this with a stylesheet where you have created named classes that contain a mixture of styling rules, which are thoughtfully applied to page elements throughout your application. Your changes are much more surgical, by their very nature, since you are acting on the only layer that needs to change – the CSS.

In contrast, using Tailwind or any other system that atomizes CSS down to a very thin wrapper over using the style attribute directly, is really akin to fiddling with the page in your browser, directly manipulating styles to make it look right on your screen. You can make it look like anything you want – no limits – but you have to re-do that everywhere you want to see the change.

I just don’t see the point.

Walter

@walterdavis I agree with you, which is why I usually prefer Bootstrap. However, what you wrote is not entirely accurate. If you use the base Tailwind, you indeed end up with something very hard to modify, however this is usually not how it is being used. The principle of Tailwind is to design components and reuse components. In conjunction with React or Rails you end up not duplicating much code and have for example only one implementation of a password field.

If you use Bootstrap default theme where you just play with the colors and other customization options… there is indeed no comparison in terms of simplicity. But your website will look similar to any other website made in Boostrap. If you want to make it look very different, you will end up writing a lot of custom CSS and your final code will look as complex as Tailwind and more ugly.

It therefore really depends on what you want to do.

Tailwind can appear extremely cluttered after coming from bootstrap or a css zen garden approach.

If you’re doing things the full rails way with stimulus and turbo, then yeah, essentially everything is in your view files, and it starts to look like this;

As everyone else has said, partials and helpers are a great start. But it can still seem overwhelming having lines of classes everywhere.

IMO you get used to it. Ultimately you need to decide if you like working directly 'in medium` like this.

I love styling my screens in the same place I write the HTML for them - it’s like painting directly onto a canvas. Good luck :slight_smile:

Thank you all, it seems that I need to just accept the pain and move forward.

I use simple helpers for smaller stuff like like links and buttons

module ButtonsHelper
  SOLID_BUTTON_CLASS_NAMES = "bg-green-800 font-semibold hover:bg-green-700 p-3 text-sm text-white rounded-md"
  OUTLINED_BUTTON_CLASS_NAMES = "border font-semibold hover:text-green-700 p-3 text-sm text-green-800"

  def outlined_button_tag(content, options = {})
    options[:class] = html_class_names(OUTLINED_BUTTON_CLASS_NAMES, options[:class])
    button_tag(content, options)
  end

  def solid_button_tag(content, options = {})
    options[:class] = html_class_names(SOLID_BUTTON_CLASS_NAMES, options[:class])
    button_tag(content, options)
  end

  def solid_link_to(name, options = {}, html_options = {}, &block)
    html_options, options, name = options, name, block if block_given?
    html_options[:class] = html_class_names(SOLID_BUTTON_CLASS_NAMES, html_options[:class])
    link_to(name, options, html_options, &block)
  end
end

And for more complex stuff I use view components. This works great with Taiwlind cause CSS and HTML are easily encapsulated inside template file :slight_smile:

3 Likes

I didnt know this Gem and was doing more or less the same things with partials… ViewComponent is however much more elegant :v:

Partials are also perfectly fine. I usually start with partial and when/if things become complex it is easy to switch to view component. I like components cause it is easy to encapsulate everything in one place. It also makes testing easier usually.

# app/components/user_contact_componet.rb
class UserContactComponent < ViewComponent::Base
  def initialize(user)
    @user = user
  end

  def full_name
    "#{@user.first_name} #{@user.last_name}".presence || "No name provided"
  end

  def address
    "#{@user.address}, #{@user.postal_code} #{@user.city}"
  end

  def email
    @user.email
  end

  def phone
    @user.phone.presence || "-"
  end
end

# app/components/user_contact_component.html.erb
<div class="border text-lg shadow p-6">
  Name: <%= full_name %><br>
  Address: <%= address %><br>
  Phone: <%= phone %><br>
  <% if email.present? %>
    <div class="border font-semibold bg-green-800 px-5 py-2"><%= mail_to "Send email", email %></div>
  <% end %>
</div>

# app/views/users/show.html.erb
...
<%= render UserContactComponent.new(@user) %>
...

# app/views/admin/orders/show.html.erb
...
<%= render UserContactComponent.new(@order.user) %>
...

As you can see all Ruby, HTML and CSS (thanks to tailwind) needed is encapsulated in two files (one component) and reusable.

Yes its elegant. The main advantage is that with components you can have some logic in some kind of a controller. Which is not the case for a partial

I have been playing with a new tool that I called styler. It tries to help to compose css classes and give them names to styles, with code that looks like this…

styles = Styler.new do
  style :default, ["pa3", "white", "bg_blue"]
  style :danger, [default - "bg_blue", "bg_red"]
end

styles.default.to_s # => "pa3 white bg_blue"
styles.danger.to_s # => "pa3 white bg_red"

For now is more an idea, although it works, and you can play with it if you want :grinning:

In this article I show how it works: Styler, a tool to compose css classes with ruby

And in this article I show a little page that I am building with examples using tachyons (that is very similar to tailwindcss): Styler and tachyons examples

In the example of @Vlado_Cingel you could have done something like…

module StylesHelper
  def styles
    @styles ||= Styler.new do
      collection :button do
        style :solid, %w(bg-green-800 font-semibold hover:bg-green-700 p-3 text-sm text-white rounded-md)
        style :outlined, %w(bg-green-800 font-semibold hover:bg-green-700 p-3 text-sm text-white rounded-md)
      end
    end
  end
end

<%= button_tag "hola", class: styles.button.solid %>
<%= button_tag "hola", class: styles.button.outlined %>
<%= link_to "hola", class: styles.button.solid %>

In the examples site, show how you can build a buttons collection like this…

With this code…

styles = Styler.new do
  colors = [
    :black, :near_black, :dark_gray, :mid_gray,
    :purple, :light_purple, :hot_pink, :dark_pink,
    :navy, :dark_blue, :dark_green
  ]

  color_class = -> color { color.to_s.gsub("_", "-") }
  bg_class = -> color { "bg-#{color_class.(color)}" }

  style :base, %w(f6 link dim ph3 pv2 mb2 dib)

  collection :button do
    colors.each do |color|
      style color, [base, "white", bg_class.(color)]
    end

    collection :thin_border do
      colors.each do |color|
        style color, [base, "ba", color_class.(color)]
      end
    end

    collection :border do
      colors.each do |color|
        style color, [base, "ba", "bw1", color_class.(color)]
      end
    end

    collection :thick_border do
      colors.each do |color|
        style color, [base, "ba", "bw1", color_class.(color)]
      end
    end
  end
end
<div class="ph3">
  <h1 class="f6 fw6 ttu tracked">Basic button</h1>
  <a class="<%= styles.button.black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.near_black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.dark_gray %>" href="#0">Button Text</a>
  <a class="<%= styles.button.mid_gray %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.light_purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.hot_pink %>" href="#0">Button Text</a>
  <a class="<%= styles.button.dark_pink %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.navy %>" href="#0">Button Text</a>
  <a class="<%= styles.button.dark_blue %>" href="#0">Button Text</a>
  <a class="<%= styles.button.dark_green %>" href="#0">Button Text</a>
</div>
<div class="ph3 mt4">
  <h1 class="f6 fw6 ttu tracked">Basic Button with Thin Border</h1>
  <a class="<%= styles.button.thin_border.black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.near_black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.dark_gray %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.mid_gray %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.thin_border.purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.light_purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.hot_pink %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.dark_pink %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.thin_border.navy %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.dark_blue %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thin_border.dark_green %>" href="#0">Button Text</a>
</div>
<div class="ph3 mt4">
  <h1 class="f6 fw6 ttu tracked">Basic Button with Border</h1>
  <a class="<%= styles.button.border.black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.near_black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.dark_gray %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.mid_gray %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.border.purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.light_purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.hot_pink %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.dark_pink %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.border.navy %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.dark_blue %>" href="#0">Button Text</a>
  <a class="<%= styles.button.border.dark_green %>" href="#0">Button Text</a>
</div>
<div class="ph3 mt4">
  <h1 class="f6 fw6 ttu tracked">Basic Button with Thick Border</h1>
  <a class="<%= styles.button.thick_border.black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.near_black %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.dark_gray %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.mid_gray %>" href="#0">Button Text</a>
</div>
<div class="ph3">
  <a class="<%= styles.button.thick_border.purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.light_purple %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.hot_pink %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.dark_pink %>" href="#0">Button Text</a>
</div>
<div class="ph3 mb4">
  <a class="<%= styles.button.thick_border.navy %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.dark_blue %>" href="#0">Button Text</a>
  <a class="<%= styles.button.thick_border.dark_green %>" href="#0">Button Text</a>
</div>

I don’t really see the interest of this tool. This is something that goes a bit against Tailwind philosophy is to be verbose in order to make style customization simple.

A tool like this one is a bit like going to standard CSS or Bootstrap CSS.

Maybe it is true what you say… Although I think that you still get the benefit of using the collection of classes from tailwindcss (or tachyons). I think that it is a little less overwhelming because you have less things from where to choose.

But thanks for you feedback :smile:

That’s just my feeling, it does not mean that everybody will think the same :slight_smile:

To echo what others have already said here, Tailwind definitely comes with an opinion about how to organise you CSS and if it really doesn’t feel like the right approach to you, Tailwind may not be the best framework for you. Having said that, I’ve recently been using Tailwind (and TailwindUI elements) in a new project and found the Nice Partials gem a really great companion to the “Tailwind Way”. I’ve been using it to create configurable components that I then compose in each view. You can call a partial with a block and do things like:

<%=  render "page" do |p|  %>
  <% p.content_for :body do  %>
    <div>Some body content</div>
  <% end %>
<% end %>

It’s also easy to pass in variables to your partial that you might use to selectively choose a set of tailwind classes.

<%= render "product", featured: true, name: "My Product" do |p|  %>
  <% p.content_for :image do  %>
    <img... />
  <% %>
<% end %>

Then in the product partial, if featured is true, use one set of classes, if not, use a different set.

It doesn’t solve all the concerns raised re Tailwind but I’m enjoying using them together.