Rails FormBuilders with TailwindCSS
Tags: ruby, rails, tailwindcss
Published: 2021-02-15 22:11:03

[TailwindCSS](https://tailwindcss.com/) 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](https://tailwindcss.com/docs/extracting-components) in our views as a way to reduce duplication. In Rails we also have the concept of [partials](https://guides.rubyonrails.org/layouts_and_rendering.html#using-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.

## FormBuilders
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](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html).

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](https://github.com/tailwindlabs/tailwindcss-forms). 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:

```ruby
  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)
    end
  end
```

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

```erb
  <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" %>
      </div>
    <% end %>
  </main>
```

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

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

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:

```ruby
ActionView::Base.default_form_builder = TailwindFormBuilder
```

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

## Conclusion
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_tag`s 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.

## Refernces
- [The Rails FormBuilder documentation](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html)
- [Samuel Mullen's excellent write up](https://samuelmullen.com/articles/reduce-form-boilerplate-with-rails-formbuilder/)
- [Tailwind Forms Examples](https://tailwindcss-forms.vercel.app/)