For shadcn on rails, 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.
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
.
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
.
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:
<%= 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.
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:
<%= render_form_for(@user) do |f| %>
<%= f.text_field :username %>
<% end %>
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.