# Building shadcn Form Builders in Rails

For [shadcn on rails](https://shadcn.themesonrails.com/docs/components/forms), I wanted to create a custom form builder that would plug my shadcn form inputs and controls into ActiveModel so that the error validations and other features native to Rails forms would work.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690281697638/6e126f13-4e9f-4a18-8d31-fda38c127191.png align="center")

## Creating the Custom Form Builder

First, let's create a new form builder. We'll call it `ShadcnFormBuilder`, and it will inherit from `ActionView::Helpers::FormBuilder`.

```ruby
class ShadcnFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(method, options = {})
    options[:class] << " error" if @object.errors[method].any?
    @template.render_input(
      name: "#{object_name}[#{method}]",
      id: "#{object_name}_#{method}",
      value: @object.send(method),
      type: "text", **options
    )
  end
end
```

In the `text_field` method, we first check if there are any validation errors for the given field (`method`). If there are, we add the "error" CSS class to the field.

The important part here is maintaining the conventions of Rails, like naming the form fields correctly using `"#{object_name}[#{method}]"` and giving it the appropriate ID using `"#{object_name}_#{method}"`

Just to be clear, with the form builder methods, you get access to some important objects that help you build your custom form.

### Form Builder Objects

The form builder objects I'm using above are:

`@object` which represents the underlying object that was passed to `form_for` or `form_with`.

`@template` which represents the `ActionView` instance of the rendering engine. You need to call any of the `ActionView` methods you would normally, like `text_field_tag`, or in our case our custom method supplied by our `Components::InputHelper render_input`.

`object_name` is just a shortcut method that gives you the class of the `@object`, useful for generating HTML attributes.

Next, we call the `render_input` helper method on the `@template` object, passing the necessary parameters to it.

## Creating Helper Methods

The `render_input` helper method is where we'll define the HTML for our text field. It lives in a helper module, `Components::InputHelper`.

```ruby
module Components::InputHelper
  def render_input(name:, label: false, id: nil, type: :text, value: nil, **options)
    # define and apply CSS classes
    # ...

    render partial: "components/ui/input", locals: {
      type:,
      label:,
      name:,
      value:,
      id:,
      options: options
    }
  end
end
```

The `render_input` method is responsible for rendering the form field's HTML. It receives various options, formats the CSS classes and other attributes, and then renders a partial (`_input.html.erb`) that contains the actual HTML for the form field.

## The Form Field Partial

Our `_input.html.erb` file looks like this:

```erb
<%= text_field_tag name, value, type: type, id: id,
              class: options[:class],
              placeholder: options[:placeholder],
              autocomplete: options[:autocomplete],
              autocapitalize: options[:autocapitalize],
              autocorrect: options[:autocorrect],
              disabled: options[:disabled],
              required: options[:required],
              readonly: options[:readonly],
              data: options[:data] %>
```

Here, we use the Rails `text_field_tag` helper to generate the HTML for our form field. We pass all the necessary attributes to it.

## Using the Custom Form Builder

To use our custom form builder, we've created two additional helper methods: `render_form_with` and `render_form_for`. These methods simply call the corresponding Rails form helper methods (`form_with` and `form_for`), but they also pass the `builder: ShadcnFormBuilder` option, which tells Rails to use our custom form builder.

```ruby
module Components::FormsHelper
  def render_form_with(**opts)
    form_with(**opts.merge(builder: ShadcnFormBuilder)) do |form|
      yield form
    end
  end

  def render_form_for(obj, **opts)
    form_for(obj, **opts.merge(builder: ShadcnFormBuilder), html: opts) do |form|
      yield form
    end
  end
end
```

Now, when we want to create a form in our views, we can use the `render_form_for` helper:

```erb
<%= render_form_for(@user) do |f| %>
  <%= f.text_field :username %>
<% end %>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690281717811/3172a9a4-ffcd-4102-8b23-ae75260ab325.png align="center")

## Conclusion

Building a custom form builder in Rails gives you the flexibility to define your own form fields' HTML and to manipulate form fields based on certain conditions (like the presence of validation errors). This example demonstrated how you could create a simple custom form builder that gives you control over the form fields' CSS classes and HTML attributes. From here, you could extend your form builder to add more field types, apply more custom logic, and further customize your form fields.
