Form
Building forms with built-in client-side validations.
Usage
Example
Form(class: "w-2/3 space-y-6") do FormField do FormFieldLabel { "Default error" } Input(placeholder: "Joel Drapper", required: true, minlength: "3") { "Joel Drapper" } FormFieldHint() FormFieldError() end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Custom error message
Form(class: "w-2/3 space-y-6") do FormField do FormFieldLabel { "Custom error message" } Input(placeholder: "joel@drapper.me", required: true, data_value_missing: "Custom error message") FormFieldError() end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Backend error
Form(class: "w-2/3 space-y-6") do FormField do FormFieldLabel { "Backend error" } Input(placeholder: "Joel Drapper", required: true) FormFieldError { "Error from backend" } end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Checkbox
Form(class: "w-2/3 space-y-6") do FormField do Checkbox(required: true) label( class: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ) { " Accept terms and conditions " } FormFieldError() end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Select
Form(class: "w-2/3 space-y-6") do FormField do FormFieldLabel { "Select" } Select do SelectInput(required: true) SelectTrigger do SelectValue(placeholder: "Select a fruit") end SelectContent() do SelectGroup do SelectLabel { "Fruits" } SelectItem(value: "apple") { "Apple" } SelectItem(value: "orange") { "Orange" } SelectItem(value: "banana") { "Banana" } SelectItem(value: "watermelon") { "Watermelon" } end end end FormFieldError() end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Combobox
Form(class: "w-2/3 space-y-6") do FormField do FormFieldLabel { "Combobox" } Combobox do ComboboxInput(required: true) ComboboxTrigger do ComboboxValue(placeholder: "Select event...") end ComboboxContent do ComboboxSearchInput(placeholder: "Search event...") ComboboxList do ComboboxEmpty { "No results found." } ComboboxGroup(heading: "Suggestions") do ComboboxItem(value: "railsworld") do span { "Rails World" } end ComboboxItem(value: "tropicalrb") do span { "Tropical.rb" } end ComboboxItem(value: "friendly.rb") do span { "Friendly.rb" } end end ComboboxSeparator() ComboboxGroup(heading: "Others") do ComboboxItem(value: "railsconf") do span { "RailsConf" } end ComboboxItem(value: "euruko") do span { "Euruko" } end ComboboxItem(value: "rubykaigi") do span { "RubyKaigi" } end end end end end FormFieldError() end Button(type: "submit") { "Save" } end
Copied!
Copy failed!
Installation
Using RubyUI CLI
Run the install command
rails g ruby_ui:component Form
Copied!
Copy failed!
Manual installation
1
Add RubyUI::Form
to app/components/ruby_ui/form.rb
# frozen_string_literal: true module RubyUI class Form < Base def view_template(&) form(**attrs, &) end private def default_attrs {} end end end
Copied!
Copy failed!
2
Add RubyUI::FormField
to app/components/ruby_ui/form/form_field.rb
# frozen_string_literal: true module RubyUI class FormField < Base def view_template(&) div(**attrs, &) end private def default_attrs { data: { controller: "ruby-ui--form-field" }, class: "space-y-2" } end end end
Copied!
Copy failed!
3
Add RubyUI::FormFieldError
to app/components/ruby_ui/form/form_field_error.rb
# frozen_string_literal: true module RubyUI class FormFieldError < Base def view_template(&) p(**attrs, &) end private def default_attrs { data: { ruby_ui__form_field_target: "error" }, class: "text-sm font-medium text-destructive" } end end end
Copied!
Copy failed!
4
Add RubyUI::FormFieldHint
to app/components/ruby_ui/form/form_field_hint.rb
# frozen_string_literal: true module RubyUI class FormFieldHint < Base def view_template(&) p(**attrs, &) end private def default_attrs {class: "text-sm text-muted-foreground"} end end end
Copied!
Copy failed!
5
Add RubyUI::FormFieldLabel
to app/components/ruby_ui/form/form_field_label.rb
# frozen_string_literal: true module RubyUI class FormFieldLabel < Base def view_template(&) label(**attrs, &) end private def default_attrs {class: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"} end end end
Copied!
Copy failed!
6
Add form_field_controller.js
to app/javascript/controllers/ruby_ui/form_field_controller.js
import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static targets = ["input", "error"]; static values = { shouldValidate: false }; connect() { if (this.errorTarget.textContent) { this.shouldValidateValue = true; } else { this.errorTarget.classList.add("hidden"); } } onInvalid(error) { error.preventDefault(); this.shouldValidateValue = true; this.#setErrorMessage(); } onInput() { this.#setErrorMessage(); } onChange() { this.#setErrorMessage(); } #setErrorMessage() { if (!this.shouldValidateValue) return; if (this.inputTarget.validity.valid) { this.errorTarget.textContent = ""; this.errorTarget.classList.add("hidden"); } else { this.errorTarget.textContent = this.#getValidationMessage(); this.errorTarget.classList.remove("hidden"); } } #getValidationMessage() { let errorMessage; const { validity, dataset, validationMessage } = this.inputTarget; if (validity.tooLong) errorMessage = dataset.tooLong; if (validity.tooShort) errorMessage = dataset.tooShort; if (validity.badInput) errorMessage = dataset.badInput; if (validity.typeMismatch) errorMessage = dataset.typeMismatch; if (validity.stepMismatch) errorMessage = dataset.stepMismatch; if (validity.valueMissing) errorMessage = dataset.valueMissing; if (validity.rangeOverflow) errorMessage = dataset.rangeOverflow; if (validity.rangeUnderflow) errorMessage = dataset.rangeUnderflow; if (validity.patternMismatch) errorMessage = dataset.patternMismatch; return errorMessage || validationMessage; } }
Copied!
Copy failed!
7
Update the Stimulus controllers manifest file
Importmap!
You don't need to run this command if you are using Importmap
rake stimulus:manifest:update
Copied!
Copy failed!
Components
Component | Built using | Source |
---|---|---|
Form | Phlex | |
FormField | Phlex | |
FormFieldError | Phlex | |
FormFieldHint | Phlex | |
FormFieldLabel | Phlex | |
FormFieldController | Stimulus JS |