DEV Community

Adam Rogers
Adam Rogers

Posted on

Rails FormBuilders with TailwindCSS

TailwindCSS is all about adding lots of utility classes to your markup to style elements directly. The problem with that, of course, is that you end up adding lots of utility classes to your markup. Like, a lot of classes. Dozens, sometimes, by the time you've accounted for the various break points and perhaps dark mode and what have you.

This can become tiresome, especially in the case of forms, where each element of the form may have all those classes applied and these classes need to be repeated for a dozen of elements in the form. Nobody has got time for that, I'm sure.

You want all the fields in your form to be consistent with each other, but you also want all the forms on your site to be consistent with each other, too. If you want to change how a text_field tag looks you want to make that change in one place and see the change reflected everywhere. DRY, and all that.

Tailwind encourages us to extract components in our views as a way to reduce duplication. In Rails we also have the concept of partials that we can use to share view code between different contexts. If we have a form, we might extract it into a partial and use the same form in the new and edit views.

That's cool, but we still have to duplicate all the utility classes on every element in that form, and we can't share elements between forms for different things.

Not very DRY.


But Rails has been doing this stuff for years, and obviously there is a super easy and well thought out solution for solving this exact problem: FormBuilders.

We can create our own FormBuilder, supplement the default behaviour with our TailwindCSS utility classes, and then whenever we write f.text_field :whatever we'll get a nicely styled text field that looks the same as every other text field on our site.

Here's how to do that (presuming you already have TailwindCSS in your Rails application, which is beyond the scope of this article).

Install the Tailwind forms plugin

To get us off to a good start, install the Tailwind forms plugin. This'll give us some nice forms out of the box, and allow us to style things further.

Create a form builder

I like to create a folder in app to keep form builders in. Files in subfolders of app are automatically loaded, and if we decide to add more form builders later then there is a sensible place to put them.

The basic form builder looks like this:

  class TailwindFormBuilder < ActionView::Helpers::FormBuilder
    def text_field(method, opts={})
      default_opts = { class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 #{'border-2 border-red-500' if @object.errors.any?}" }
      merged_opts = default_opts.merge(opts)
      super(method, merged_opts)
Enter fullscreen mode Exit fullscreen mode

We create a new class that inherits from ActionView::Helpers::FormBuilder and then go ahead and override the text_field method it provides. In our version, we're going to slam a bunch of Tailwind class names into the options hash we're given by default. This allows us to set different classes on a case by case basis but it's a fairly crude implementation so if we want to override one class we have to supply all the classes we want to see applied to the final element. Finally, we super the original method we were passed and our freshly mangled opts hash up to the original text_field method we're overriding and let it do its thing.

A more sophisticated solution is left as an exercise to the reader.

Use the FormBuilder

We can then use the form builder very simply by passing it to form_with:

  <main class="p-12 mx-auto text-gray-800 max-w-7xl">
    <%= form_with model: @widget, builder: TailwindFormBuilder, class: "mt-6" do |f| %>
      <%= f.label :reference %>
      <%= f.text_field :reference, placeholder: "Your reference" %>
      <div class="pt-5">
        <%= f.submit "Begin" %>
    <% end %>
Enter fullscreen mode Exit fullscreen mode

When we render the form and take a look at our text field, we see all our wonderful TailwindCSS classes!

<input class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indiogo-300 focus:ring focus:ring-indiogo-200 focus:ring-opacity-50 " placeholder="Your reference" type="text" name="widget[reference]" id="widget_reference">
Enter fullscreen mode Exit fullscreen mode

We can rinse and repeat this process for any and every Rails form tag we want to apply sensible default TailwindCSS styles to.

Making TailwindFormBuilder the default

Making our form builder the default is easy, too. In our ApplicatioHelper, we can set it as the default like this:

ActionView::Base.default_form_builder = TailwindFormBuilder
Enter fullscreen mode Exit fullscreen mode

We can then remove builder: TailwindFormBuilder from our call to form_with in the view, and all forms will use our nicely styled text_fields by default.


This is obviously a super simple example of what you can do with FormBuilders in Rails and how they can help tuck those CSS utility classes out of the way. We can do some neat things like making the border of the text_area red if the object has errors. We can even start cracking out content_tags to create chunks of HTML that include labels and inline error messages for each element. So far I'm still trying to find the balance between "everything is in the form builder" and "nothing is in the form builder". You'll notice above that the form is still managing a bit of it's own spacing, which may or may not be worth moving into the TailwindFormBuilder, but I so far I've kept label seperate from text_field, for example, and I think I'm happy with that.

I'm currently experimenting with appending a ul of error messages to each element in the form as a way of displaying nicely styled inline error messages to the user. This seems to be a bit of a reach, at first, because the markup for the errors don't appear in the form itself. But because they are applied by the form builder, they are applied consistently and to every text_field that the form is used for. That's just how errors are dealt with in this kind of form. I quite like that (so far).

FormBuilders are a great way to tuck utility CSS classes away in Rails applications.


Top comments (0)