One of Superform’s strengths is giving developers complete control over how form errors are displayed. Rather than being locked into a specific CSS framework’s conventions, you can customize error styling to work with DaisyUI, Tailwind, Bootstrap, or any other framework you prefer.
Custom Field class with error helpers
The key is extending Superform::Rails::Form::Field with your own error helpers. Here’s an example that works with DaisyUI:
# ./app/components/form.rb
class Components::Form < Superform::Rails::Form
class Field < Superform::Rails::Form::Field
# A single sentence for inclusion below an input.
def error_message
"#{key.to_s.titleize} #{errors.to_sentence}" if errors.any?
end
def input(*, **attributes)
super(*, **mix(
{
class: [
"input input-outline w-full",
("input-error" if field.invalid?)
]
},
attributes)
)
end
def textarea(*, **attributes)
super(*, **mix(
{
class: [
"textarea textarea-outline w-full",
("input-error" if field.invalid?)
]
},
attributes)
)
end
end
end
How it works
The Field class provides two key features for error handling:
1. The error_message method
This generates a human-readable error message by combining the field name with its validation errors:
def error_message
"#{key.to_s.titleize} #{errors.to_sentence}" if errors.any?
end
For a :email field with the error “is invalid”, this produces “Email is invalid”.
2. Conditional error classes
The field.invalid? method checks if the field has validation errors. When combined with Ruby’s conditional expression, you can add error-specific CSS classes:
class: [
"input input-outline w-full",
("input-error" if field.invalid?)
]
When the field is valid, the class array is ["input input-outline w-full", nil]. Superform’s mix method filters out nil values, so you get clean HTML output.
Using it in your form
Render the error message below your input using the error_message helper:
class Views::Users::Form < Components::Form
def view_template
Field(:email).input(type: :email)
p(class: "text-error text-sm mt-1") { Field(:email).error_message }
Field(:password).input(type: :password)
p(class: "text-error text-sm mt-1") { Field(:password).error_message }
submit
end
end
Adapting for other CSS frameworks
The pattern works identically for any CSS framework. Just swap out the class names:
Bootstrap
def input(*, **attributes)
super(*, **mix(
{
class: [
"form-control",
("is-invalid" if field.invalid?)
]
},
attributes)
)
end
Plain Tailwind
def input(*, **attributes)
super(*, **mix(
{
class: [
"border rounded px-3 py-2 w-full",
("border-red-500" if field.invalid?)
]
},
attributes)
)
end
The beauty of Superform is that you define these patterns once in your base Components::Form class, and they apply consistently across every form in your application.
Going deeper
The Phlex on Rails video course covers form validation patterns in detail, including how to build reusable field components with labels, hints, and error messages.