{"id":13878821,"url":"https://github.com/pantographe/view_component-form","last_synced_at":"2025-07-16T14:33:03.664Z","repository":{"id":41832456,"uuid":"322863971","full_name":"pantographe/view_component-form","owner":"pantographe","description":"Rails FormBuilder for ViewComponent","archived":false,"fork":false,"pushed_at":"2024-11-08T21:47:15.000Z","size":308,"stargazers_count":216,"open_issues_count":28,"forks_count":17,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-11-08T22:32:02.933Z","etag":null,"topics":["form-builder","forms","rails","viewcomponent","viewcomponents"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pantographe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-19T14:18:39.000Z","updated_at":"2024-11-08T21:47:16.000Z","dependencies_parsed_at":"2024-01-13T20:43:05.133Z","dependency_job_id":"3498f86d-5da6-418d-939f-7038f80b8f79","html_url":"https://github.com/pantographe/view_component-form","commit_stats":{"total_commits":137,"total_committers":14,"mean_commits":9.785714285714286,"dds":0.635036496350365,"last_synced_commit":"2ccb020c1277832121b43e3d182232e0c3a86c88"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pantographe%2Fview_component-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pantographe%2Fview_component-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pantographe%2Fview_component-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pantographe%2Fview_component-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pantographe","download_url":"https://codeload.github.com/pantographe/view_component-form/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226138849,"owners_count":17579496,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["form-builder","forms","rails","viewcomponent","viewcomponents"],"created_at":"2024-08-06T08:02:01.136Z","updated_at":"2025-07-16T14:33:03.646Z","avatar_url":"https://github.com/pantographe.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# ViewComponent::Form\n\n**`ViewComponent::Form`** is a customizable form builder using the same interface as [`ActionView::Helpers::FormBuilder`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html) but with extensible [ViewComponent](https://github.com/github/view_component) components.\n\nDevelopment of this gem is sponsored by:\n\n\u003ca href=\"https://etamin.studio/?ref=view_component-form\"\u003e\u003cimg src=\"https://etamin.studio/images/logo.svg\" alt=\"Sponsored by Etamin Studio\" width=\"184\" height=\"22\"\u003e\u003c/a\u003e      \u003ca href=\"https://pantographe.studio/?ref=view_component-form\"\u003e\u003cimg src=\"https://static.s3.office.pantographe.cloud/logofull.svg\" alt=\"Sponsored by Pantographe\" width=\"156\" height=\"25\"\u003e\u003c/a\u003e\n\n## Compatibility\n\n\u003e [!WARNING]\n\u003e **This is an early release, and the API is subject to change until `v1.0.0`.**\n\nThis gem is tested on:\n\n- Rails 7.0+ (with or without ActionText)\n- Ruby 3.1+\n\n## Installation\n\n```shell\nbundle add view_component-form\n```\n\n### Configuration\n\n```ruby\n# config/initializers/vcf.rb\n\nViewComponent::Form.configure do |config|\n  config.parent_component = 'ApplicationFormComponent'\nend\n```\n\n| Attribute                   | Purpose                                               | Default                 |\n| --------------------------- | ----------------------------------------------------- | ----------------------- |\n| `parent_component` (string) | Parent class for all `ViewComponent::Form` components | `\"ViewComponent::Base\"` |\n\n#### Configuring component lookup\n\n`ViewComponent::Form` will automatically infer the component class with a `Component` suffix. You can customize the lookup using the `lookup_chain`:\n\n```rb\n# config/initializers/vcf.rb\n\nViewComponent::Form.configure do |config|\n  without_component_suffix = lambda do |component_name, namespaces: []|\n    namespaces.lazy.map do |namespace|\n      \"#{namespace}::#{component_name.to_s.camelize}\".safe_constantize\n    end.find(\u0026:itself)\n  end\n\n  config.lookup_chain.prepend(without_component_suffix)\nend\n```\n\n`ViewComponent::Form` will iterate through the `lookup_chain` until a value is returned. By using `prepend` we can fallback on the default `ViewComponent::Form` lookup.\n\n## Usage\n\nAdd your own form builder.\n\n```shell\nbin/rails generate vcf:builder FormBuilder\n      create  app/helpers/form_builder.rb\n```\n\nTo use the form builder:\n\n- add a `builder` param to your `form_for`, `form_with`, `fields_for` or `fields`:\n\n```diff\n- \u003c%= form_for @user do |f| %\u003e\n+ \u003c%= form_for @user, builder: FormBuilder do |f| %\u003e\n```\n\n- or; set it as a default in your controller using [default_form_builder](https://api.rubyonrails.org/classes/ActionController/FormBuilder.html#method-i-default_form_builder).\n\n```ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  default_form_builder FormBuilder\nend\n```\n\nThen use ActionView form builder helpers as you would normally:\n\n```erb\n\u003c%# app/views/users/_form.html.erb %\u003e\n\u003c%= form_for @user, builder: ViewComponent::Form::Builder do |f| %\u003e\n  \u003c%= f.label :first_name %\u003e        \u003c%# renders a ViewComponent::Form::LabelComponent %\u003e\n  \u003c%= f.text_field :first_name %\u003e   \u003c%# renders a ViewComponent::Form::TextFieldComponent %\u003e\n\n  \u003c%= f.label :last_name %\u003e         \u003c%# renders a ViewComponent::Form::LabelComponent %\u003e\n  \u003c%= f.text_field :last_name %\u003e    \u003c%# renders a ViewComponent::Form::TextFieldComponent %\u003e\n\n  \u003c%= f.label :email %\u003e             \u003c%# renders a ViewComponent::Form::LabelComponent %\u003e\n  \u003c%= f.email_field :email %\u003e       \u003c%# renders a ViewComponent::Form::EmailFieldComponent %\u003e\n\n  \u003c%= f.label :password %\u003e          \u003c%# renders a ViewComponent::Form::LabelComponent %\u003e\n  \u003c%= f.password_field :password, aria: { describedby: f.field_id(:password, :description) } %\u003e\n                                    \u003c%# renders a ViewComponent::Form::PasswordFieldComponent %\u003e\n  \u003cdiv id=\"\u003c%= f.field_id(:password, :description) %\u003e\"\u003e\n    \u003c%= f.hint :password, 'The password should be at least 8 characters long' %\u003e\n                                      \u003c%# renders a ViewComponent::Form::HintComponent %\u003e\n    \u003c%= f.error_message :password %\u003e  \u003c%# renders a ViewComponent::Form::ErrorMessageComponent %\u003e\n  \u003c/div\u003e\n\u003c% end %\u003e\n```\n\n### Customizing built-in components\n\nThe `ViewComponent::Form::Builder` will use the provided `namespace` to find any components you've customized.\n\n```ruby\n# app/helpers/form_builder.rb\nclass FormBuilder \u003c ViewComponent::Form::Builder\n  namespace Form\nend\n```\n\nLet's customize the `text_field` helper by generating a new [ViewComponent](https://github.com/github/view_component) in the namespace defined within the builder.\n\n```shell\nbin/rails generate component Form::TextField --parent ViewComponent::Form::TextFieldComponent --inline\n```\n\n```ruby\n# app/components/form/text_field_component.rb\nclass Form::TextFieldComponent \u003c ViewComponent::Form::TextFieldComponent\n  def html_class\n    class_names(\"custom-text-field\", \"has-error\": method_errors?)\n  end\nend\n```\n\nIn this case we're leveraging the [`#class_names`](https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-class_names) helper to:\n\n- always add the `custom-text-field` class;\n- add the `has-error` class if there is an error on the attribute (using `ViewComponent::Form::FieldComponent#method_errors?`).\n\n### Adding your own custom helpers and components\n\nAdd the helper method to your `ViewComponent::Form::Builder`\n\n```rb\n# app/helpers/form_builder.rb\nclass FormBuilder \u003c ViewComponent::Form::Builder\n  def year_field(method, options = {})\n    render_component(:year_field, @object_name, method, objectify_options(options))\n  end\n\n  def money_field(method, currencies = [], options = {})\n    render_component(:money_field, @object_name, method, currencies, objectify_options(options))\n  end\nend\n```\n\nAdd your component which can optionally inherit from:\n\n- `ViewComponent::Form::FieldComponent` (suggested when adding a field because of helpers)\n- `ViewComponent::Form::BaseComponent`\n- or any of the `ViewComponent::Form::*Component` such as `ViewComponent::Form::TextFieldComponent`\n\n```rb\n# app/components/form/year_field_component.rb\nclass Form::YearFieldComponent \u003c ViewComponent::Form::FieldComponent # or ViewComponent::Form::BaseComponent\nend\n```\n\nWhen inheriting from `ViewComponent::Form::FieldComponent`, you get access to the following helpers:\n\n#### `#label_text`\n\nReturns the translated text for the label of the field (looking up for `helpers.label.OBJECT.METHOD_NAME`), or humanized version of the method name if not available.\n\n```rb\n# app/components/custom/form/group_component.rb\nclass Custom::Form::GroupComponent \u003c ViewComponent::Form::FieldComponent\nend\n```\n\n```erb\n\u003c%# app/components/custom/form/group_component.html.erb %\u003e\n\u003cdiv class=\"custom-form-group\"\u003e\n  \u003clabel\u003e\n    \u003c%= label_text %\u003e\u003cbr /\u003e\n    \u003c%= content %\u003e\n  \u003c/label\u003e\n\u003c/div\u003e\n```\n\n```erb\n\u003c%# app/views/users/_form.html.erb %\u003e\n\u003c%= form_for @user do |f| %\u003e\n  \u003c%= f.group :first_name do %\u003e\n    \u003c%= f.text_field :first_name %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\n```yml\n# config/locales/en.yml\nen:\n  helpers:\n    label:\n      user:\n        first_name: Your first name\n```\n\nRenders:\n\n```html\n\u003cform\n  class=\"edit_user\"\n  id=\"edit_user_1\"\n  action=\"/users/1\"\n  accept-charset=\"UTF-8\"\n  method=\"post\"\n\u003e\n  \u003c!-- ... --\u003e\n  \u003clabel\u003e\n    Your first name\u003cbr /\u003e\n    \u003cinput\n      type=\"text\"\n      value=\"John\"\n      name=\"user[first_name]\"\n      id=\"user_first_name\"\n    /\u003e\n  \u003c/label\u003e\n\u003c/form\u003e\n```\n\n### Validations\n\nLet's consider the following model for the examples below.\n\n```rb\n# app/models/user.rb\nclass User \u003c ActiveRecord::Base\n  validates :first_name, presence: true, length: { minimum: 2, maximum: 255 }\nend\n```\n\n##### Accessing validations with `#validators`\n\nReturns all validators for the method name.\n\n```rb\n# app/components/custom/form/group_component.rb\nclass Custom::Form::GroupComponent \u003c ViewComponent::Form::FieldComponent\n  private\n\n  def validation_hint\n    if length_validator\n      \"between #{length_validator.options[:minimum]} and #{length_validator.options[:maximum]} chars\"\n    end\n  end\n\n  def length_validator\n    validators.find { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }\n  end\nend\n```\n\n```erb\n\u003c%# app/components/custom/form/group_component.html.erb %\u003e\n\u003cdiv class=\"custom-form-group\"\u003e\n  \u003clabel\u003e\n    \u003c%= label_text %\u003e (\u003c%= validation_hint %\u003e)\u003cbr /\u003e\n    \u003c%= content %\u003e\n  \u003c/label\u003e\n\u003c/div\u003e\n```\n\n##### Using `#required?` and `#optional?`\n\n```erb\n\u003c%# app/components/custom/form/group_component.html.erb %\u003e\n\u003cdiv class=\"custom-form-group\"\u003e\n  \u003clabel\u003e\n    \u003c%= label_text %\u003e\u003c%= \" (required)\" if required? %\u003e\u003cbr /\u003e\n    \u003c%= content %\u003e\n  \u003c/label\u003e\n\u003c/div\u003e\n```\n\n##### Validation contexts\n\nWhen using [validation contexts](https://guides.rubyonrails.org/active_record_validations.html#on), you can specify a context to the helpers above.\n\n```rb\n# app/models/user.rb\nclass User \u003c ActiveRecord::Base\n  validates :first_name, presence: true, length: { minimum: 2, maximum: 255 }\n  validates :email, presence: true, on: :registration\nend\n```\n\n```erb\n\u003c%# app/views/users/_form_.html.erb %\u003e\n\u003c%= form_with model: @user,\n              builder: ViewComponent::Form::Builder,\n              validation_context: :registration do |f| %\u003e\n  \u003c%= f.group :email do %\u003e\n    \u003c%= f.email_field :email %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\nIn this case, `ViewComponent::Form::Builder` accepts a `validation_context` option and passes it as a default value to the `#validators`, `#required?` and `#optional?` helpers.\n\nAlternatively, you can pass the context to the helpers:\n\n```erb\n\u003c%= \"(required)\" if required?(context: :registration) %\u003e\n```\n\n```rb\ndef length_validator\n  validators(context: :registration).find { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }\nend\n```\n\n### Setting up your own base component class\n\n1. Setup some base component from which the form components will inherit from\n\n```rb\nclass ApplicationFormComponent \u003c ViewComponent::Base\nend\n```\n\n2. Configure the parent component class\n\n```rb\n# config/initializers/vcf.rb\n\nViewComponent::Form.configure do |config|\n  config.parent_component = 'ApplicationFormComponent'\nend\n```\n\n### Using your form components without a backing model\n\nIf you want to ensure that your fields display consistently across your app, you'll need to lean on Rails' own helpers. You may be used to using form tag helpers such as `text_field_tag` to generate tags, or even writing out plain HTML tags. These can't be integrated with a form builder, so they won't offer you the benefits of this gem.\n\nYou'll most likely want to use either:\n\n- [`form_with`](https://api.rubyonrails.org/v6.1.4/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) and supply a route as the endpoint, e.g. `form_with url: users_path do |f| ...`, or\n- [`fields`](https://api.rubyonrails.org/v6.1.4/classes/ActionView/Helpers/FormHelper.html#method-i-fields), supplying a namespace if necessary. `fields do |f| ...` ought to work in the most basic case.\n\n[`fields_for`](https://api.rubyonrails.org/v6.1.4/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for) may also be of interest. To make consistent use of `view_component-form`, you'll want to be using these three helpers to build your forms wherever possible.\n\n## Supported helpers\n\nThe following helpers are currently supported by `ViewComponent::Form`.\n\n### `ActionView::Helpers::FormBuilder`\n\n**Supported:** `button` `check_box` `collection_check_boxes` `collection_radio_buttons` `collection_select` `color_field` `date_field` `date_select` `datetime_field` `datetime_local_field` `datetime_select` `email_field` `fields` `fields_for` `file_field` `field_id` `grouped_collection_select` `hidden_field` `month_field` `number_field` `password_field` `phone_field` `radio_button` `range_field` `search_field` `select` `submit` `telephone_field` `textarea` (formerly `text_area` before Rails 8) `text_field` `time_field` `time_select` `time_zone_select` `to_model` `to_partial_path` `url_field` `week_field` `weekday_select`\n\n**Partially supported:** `label` (blocks not supported) `rich_textarea` (formerly`rich_text_area` before Rails 8) (untested)\n\n**Unsupported for now:** `field_name`\n\n### Specific to `ViewComponent::Form`\n\n**Supported:** `error_message` `hint`\n\n## Testing your components\n\n### RSpec\n\n#### Configuration\n\nThis assumes your already have read and configured [tests for `view_component`](https://viewcomponent.org/guide/testing.html#rspec-configuration).\n\n```rb\n# spec/rails_helper.rb\nrequire \"view_component/test_helpers\"\nrequire \"view_component/form/test_helpers\"\nrequire \"capybara/rspec\"\n\nRSpec.configure do |config|\n  config.include ViewComponent::TestHelpers, type: :component\n  config.include ViewComponent::Form::TestHelpers, type: :component\n  config.include Capybara::RSpecMatchers, type: :component\nend\n```\n\n#### Example\n\n```rb\n# spec/components/form/text_field_component_spec.rb\nRSpec.describe Form::TextFieldComponent, type: :component do\n  let(:object)  { User.new } # replace with a model of your choice\n  let(:form)    { form_with(object) }\n  let(:options) { {} }\n\n  let(:component) { render_inline(described_class.new(form, object_name, :first_name, options)) }\n\n  context \"with simple args\" do\n    it do\n      expect(component.to_html)\n        .to have_tag(\"input\", with: { name: \"user[first_name]\", id: \"user_first_name\", type: \"text\" })\n    end\n  end\nend\n```\n\nFor more complex components, we recommend the [`rspec-html-matchers` gem](https://github.com/kucaahbe/rspec-html-matchers).\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `bin/release x.x.x`, which will update the `version.rb` file, open the changelog for edition, create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/pantographe/view_component-form. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/pantographe/view_component-form/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the ViewComponent::Form project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/pantographe/view_component-form/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpantographe%2Fview_component-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpantographe%2Fview_component-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpantographe%2Fview_component-form/lists"}