diff --git a/.gitignore b/.gitignore index 28f2c1545..19a339b1e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,8 +41,6 @@ demo/public/assets # Ignore master key for decrypting credentials and more. demo/config/master.key -demo/public/packs -demo/public/packs-test demo/app/assets/builds/* !demo/app/assets/builds/.keep demo/node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index dbac97111..b4cf095dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ The tooling to support our old way of doing the change log doesn't work anymore, ### Breaking changes +* Target Bootstrap 6: generate the Bootstrap 6 form markup (`form-field` wrappers, `check`/`radio`/`switch` classes, `form-range` wrappers, floating labels and adornments) and add helpers for the new combobox, datepicker, chips, OTP and password strength components. Requires Bootstrap 6 CSS and JavaScript - [@pardeyke](https://github.com/pardeyke). * [#618](https://github.com/bootstrap-ruby/bootstrap_form/pull/618): Allow overriding the wrapper class - [@donv](https://github.com/donv). * Your contribution here! diff --git a/README.md b/README.md index 5d8680032..4dab835a7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Ruby](https://github.com/bootstrap-ruby/bootstrap_form/actions/workflows/ruby.yml/badge.svg)](https://github.com/bootstrap-ruby/bootstrap_form/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/bootstrap_form.svg)](https://rubygems.org/gems/bootstrap_form) -`bootstrap_form` is a Rails form builder that makes it super easy to integrate Bootstrap v5-style forms into your Rails application. It provides form helpers that augment the Rails form helpers. `bootstrap_forms`'s form helpers generate the form field and its label and all the Bootstrap mark-up required for proper Bootstrap display. `bootstrap_form` also provides: +`bootstrap_form` is a Rails form builder that makes it super easy to integrate Bootstrap v6-style forms into your Rails application. It provides form helpers that augment the Rails form helpers. `bootstrap_forms`'s form helpers generate the form field and its label and all the Bootstrap mark-up required for proper Bootstrap display. `bootstrap_form` also provides: * [Validation error messages](#validation-and-errors) below the field they correspond to, by default. You can also put the error messages after the label, or turn off `bootstrap_form`'s validation error handling and do it yourself. _Note that this applies to Rails-generated validation messages._ HTML 5 client-side validation and Rails validation out of the box don't really work well together. One discussion of the challenges and some solutions is [here](https://www.jorgemanrubia.com/2019/02/16/form-validations-with-html5-and-modern-rails/) * Automatic [mark-up for the `required` attribute](#required-fields) on required fields. @@ -27,7 +27,7 @@ Some other nice things that `bootstrap_form` does for you are: * Ruby 3.2+ (https://www.ruby-lang.org/en/downloads/branches/) * Rails 7.2+ (https://guides.rubyonrails.org/maintenance_policy.html) -* Bootstrap 5.0+ +* Bootstrap 6.0+ ## Installation @@ -39,20 +39,22 @@ If you're creating a new Rails app, create it with at least the following option rails new --javascript=esbuild --css=bootstrap ``` -`esbuild` can be any of the options, _except_ the default `importmaps`. - -If you have an existing project, and it's processing CSS and JavaScript with some sort of front-end preprocessor, then install Bootstrap for your front-end preprocessor. That's probably something like this: +`esbuild` can be any of the options, _except_ the default `importmaps`. The `--css=bootstrap` option installs Bootstrap 5, so switch the `bootstrap` package to the Bootstrap 6 pre-release and add its peer dependencies: ```bash -yarn add bootstrap popper.js +yarn add bootstrap@next @floating-ui/dom vanilla-calendar-pro ``` +If you have an existing project, and it's processing CSS and JavaScript with some sort of front-end preprocessor, then install Bootstrap for your front-end preprocessor with the same `yarn add` command. + +If you compile Bootstrap's Sass yourself, run the compiled CSS through PostCSS with [`postcss-prefix-custom-properties`](https://github.com/twbs/postcss-prefix-custom-properties) and `{ prefix: "bs-" }`. Bootstrap 6 writes its CSS variables without the `bs-` prefix in Sass and adds it at build time, and its JavaScript expects the prefixed names. Without this step, JavaScript-driven components such as the range slider's fill and value bubble silently stop working. The [demo app](demo/postcss.config.js) shows the setup. + You can use this gem with other ways of installing Bootstrap, but how to do so is outside the scope of this README. Once Bootstrap is installed, add the `bootstrap_form` gem to your `Gemfile`: ```ruby -gem "bootstrap_form", "~> 5.6" +gem "bootstrap_form", ">= 6.0.0.alpha1" ``` Then: @@ -75,13 +77,7 @@ If you're using Sprockets, add the styles to your CSS bundle like this: @import "rails_bootstrap_forms.css"; ``` -If you're using Sprockets, you can also consider using the `bootstrap` gem to your `Gemfile`, as a way of installing Bootstrap itself. However, we haven't used this approach for a few years. Please report an issue if it doesn't work: - -```ruby -gem "bootstrap", "~> 5.0" -``` - -And follow the remaining instructions in the [official bootstrap installation guide](https://github.com/twbs/bootstrap-rubygem#a-ruby-on-rails) for setting up `application.scss` and `application.js`. +The [`bootstrap` gem](https://github.com/twbs/bootstrap-rubygem) packages Bootstrap 5 and doesn't offer Bootstrap 6 yet, so it can't be used to install Bootstrap for this version of `bootstrap_form`. ## Usage @@ -103,20 +99,20 @@ This generates the following HTML: ```html
-
+
-
+
-
+
- - + +
- + ``` @@ -138,11 +134,11 @@ This generates: ```html
-
+
- + ``` @@ -164,21 +160,21 @@ This generates: ```html
-
+
-
+
- - A good password should be at least six characters long + + A good password should be at least six characters long
-
+
- - + +
- + ``` @@ -210,19 +206,19 @@ Generated HTML: ```html
-
+
-
+
-
+
- + ``` @@ -303,7 +299,7 @@ Use the `label` option if you want to specify the field's label text: This generates: ```html -
+
@@ -320,7 +316,7 @@ class, which keeps your labels accessible to those using screen readers. This generates: ```html -
+
@@ -337,7 +333,7 @@ To add custom classes to the field's label: This generates: ```html -
+
@@ -353,7 +349,7 @@ Or you can add the label as input placeholder instead (this automatically hides This generates: ```html -
+
@@ -371,7 +367,7 @@ To specify the class of the generated input tag, use the `control_class` option: This generates: ```html -
+
@@ -389,13 +385,15 @@ To add help text, use the `help` option: This generates: ```html -
+
- - Must be at least 6 characters long + + Must be at least 6 characters long
``` +The help text gets an id, and the control references it with `aria-describedby` (together with the id of any validation feedback), as the Bootstrap documentation recommends for assistive technology. + This gem is also aware of help messages in locale translation files (i18n): ```yml @@ -434,7 +432,7 @@ You can pass `prepend` and/or `append` options to input fields: This generates: ```html -
+
$ @@ -455,7 +453,7 @@ If you want to attach multiple items to the input, pass them as an array: This generates: ```html -
+
Net @@ -470,60 +468,112 @@ This generates:
``` -You can also prepend and append buttons. Note: The buttons must contain the -`btn` class to generate the correct markup. +You can also prepend and append buttons. Note: The buttons must contain a +`btn` class to generate the correct markup, and Bootstrap 6 requires the +`input-group-btn` class on buttons inside input groups. ![Example 12](demo/doc/screenshots/bootstrap/readme/12_example.png "Example 12") ```erb -<%= f.text_field :search, append: link_to("Go", "#", class: "btn btn-secondary") %> +<%= f.text_field :search, append: link_to("Go", "#", class: "btn-solid theme-secondary input-group-btn") %> ``` This generates: ```html -
+
- Go + Go
``` -To add a class to the input group wrapper, use the `:input_group_class` option. +### Adorned Fields + +For a lighter-weight decoration than an input group, Bootstrap 6 offers [form-adorn](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/form-adorn/): an icon or text rendered inside the field's border. Use the `adorn` option: ![Example 13](demo/doc/screenshots/bootstrap/readme/13_example.png "Example 13") ```erb +<%= f.text_field :price, adorn: "$" %> +``` + +This generates: + +```html +
+ + +
+``` + +As in the Bootstrap documentation the wrapper is a `label`, so clicking the adornment focuses the input, and the adornment is `aria-hidden` because the field already has a label. Validation states are applied to the wrapper as well as the input. Pass a hash for more control: `icon:` renders HTML (e.g. an SVG) in a `form-adorn-icon` container instead of text, `end: true` places the adornment on the trailing side, and `class:` adds classes (e.g. sizing) to the wrapper: + +![Example 14](demo/doc/screenshots/bootstrap/readme/14_example.png "Example 14") +```erb +<%= f.search_field :query, adorn: { icon: ''.html_safe } %> +<%= f.text_field :username, adorn: { text: "@example.com", end: true, class: "form-control-lg form-adorn-lg" } %> +``` + +Generated HTML: + +```html +
+ + +
+
+ + +
+``` + +To add a class to the input group wrapper, use the `:input_group_class` option. + +![Example 15](demo/doc/screenshots/bootstrap/readme/15_example.png "Example 15") +```erb <%= f.email_field :email, append: f.primary('Subscribe'), input_group_class: 'input-group-lg' %> ``` This generates: ```html -
+
- +
``` ### Additional Form Group Attributes -Bootstrap mark-up dictates that most input field types have the label and input wrapped in a `div.mb-3`. +Bootstrap mark-up dictates that most input field types have the label and input wrapped in a `div.form-field.mb-3`. The [`form-field` class](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/field/) provides the layout for the control and its label, help text, and validation feedback. -If you want to change the CSS class or any other attribute to the form group div, you can use the `wrapper: { class: 'mb-3 additional-class', data: { foo: 'bar' } }` option. +If you want to change the CSS class or any other attribute to the form group div, you can use the `wrapper: { class: 'form-field mb-3 additional-class', data: { foo: 'bar' } }` option. Note that a custom class _replaces_ the default, so include `form-field` yourself if you want to keep Bootstrap's field layout. -![Example 14](demo/doc/screenshots/bootstrap/readme/14_example.png "Example 14") +![Example 16](demo/doc/screenshots/bootstrap/readme/16_example.png "Example 16") ```erb -<%= f.text_field :name, wrapper: { class: 'mb-3 has-warning', data: { foo: 'bar' } } %> +<%= f.text_field :name, wrapper: { class: 'form-field mb-3 has-warning', data: { foo: 'bar' } } %> ``` This generates: ```html -
+
@@ -531,9 +581,9 @@ This generates: Which produces the following output: -![Example 15](demo/doc/screenshots/bootstrap/readme/15_example.png "Example 15") +![Example 17](demo/doc/screenshots/bootstrap/readme/17_example.png "Example 17") ```erb -
+
@@ -542,18 +592,18 @@ Which produces the following output: This generates: ```html -
+
``` -If you only want to set the class on the form group div, you can use the `wrapper_class` option: `wrapper_class: 'mb-3 additional-class'`. -It's just a short form of `wrapper: { class: 'mb-3 additional-class' }`. +If you only want to set the class on the form group div, you can use the `wrapper_class` option: `wrapper_class: 'form-field mb-3 additional-class'`. +It's just a short form of `wrapper: { class: 'form-field mb-3 additional-class' }`. If you don't want any class on the form group div, you can set it to `false`: `wrapper_class: false`. -![Example 16](demo/doc/screenshots/bootstrap/readme/16_example.png "Example 16") +![Example 18](demo/doc/screenshots/bootstrap/readme/18_example.png "Example 18") ```erb <%= f.text_field :name, wrapper: { class: false } %> ``` @@ -571,7 +621,7 @@ This generates: You may want to define your own form group div around a field. To do so, add the option `wrapper: false` to the input field. For example: -![Example 17](demo/doc/screenshots/bootstrap/readme/17_example.png "Example 17") +![Example 19](demo/doc/screenshots/bootstrap/readme/19_example.png "Example 19") ```erb <%= f.form_group :user do %> <%= f.email_field :email, wrapper: false %> @@ -581,7 +631,7 @@ You may want to define your own form group div around a field. To do so, add the Generated HTML: ```html -
+
``` @@ -592,7 +642,7 @@ Note that Bootstrap relies on the form group div to correctly format most fields Our select helper accepts the same arguments as the [default Rails helper](http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select). Here's an example of how you pass both options and html_options hashes: -![Example 18](demo/doc/screenshots/bootstrap/readme/18_example.png "Example 18") +![Example 20](demo/doc/screenshots/bootstrap/readme/20_example.png "Example 20") ```erb <%= f.select :product, [["Apple", 1], ["Grape", 2]], { label: "Choose your favorite fruit:", wrapper: { class: 'has-warning', data: { foo: 'bar' } } }, { class: "selectpicker" } %> ``` @@ -602,20 +652,187 @@ This generates: ```html
-
``` +## Comboboxes + +Bootstrap 6 introduces a native [combobox component](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/combobox/): a select-like control with optional search and multiple selection. It replaces third-party select plugins (such as `use-bootstrap-select` or `bootstrap-select`) with plain Bootstrap. Use the `combobox` helper to generate one: + +![Example 21](demo/doc/screenshots/bootstrap/readme/21_example.png "Example 21") +```erb +<%= f.combobox :status, [["activated", 1], ["blocked", 2]], placeholder: "Select a status…" %> +``` + +This generates: + +```html +
+ + + + +
+``` + +Bootstrap's JavaScript creates a hidden `` for form submission, so the value arrives in your controller params like a regular select. + +The current value of the attribute (or the `selected` option, which takes precedence) is marked with `aria-selected` and shown in the toggle. Pass `multiple: true` for multi-select and `search: true` for a search input that filters the options (pass a string instead of `true` to customize the search placeholder): + +![Example 22](demo/doc/screenshots/bootstrap/readme/22_example.png "Example 22") +```erb +<%= f.combobox :skills, Skill.all.map { |s| [s.name, s.id] }, + multiple: true, search: true, placeholder: "Select skills…" %> +``` + +Generated HTML: + +```html +
+ + + + +
+``` + +Choices can be an array of `[text, value]` pairs, a hash of `text => value`, or a flat array of strings (used as both text and value). A hash whose values are lists of choices renders each key as a `menu-header` above its group. A `[text, value]` pair can carry a trailing hash of item options: `icon:` (HTML rendered in a `menu-item-icon`), `description:` (secondary text in a `menu-item-description`) and `disabled: true`. Pass `check: true` to add Bootstrap's check icon to the items of a single-select combobox (multi-select items always have it), and the fourth, `html_options` argument sets attributes on the toggle button, e.g. `disabled: true` or `class: "form-control-lg"`: + +![Example 23](demo/doc/screenshots/bootstrap/readme/23_example.png "Example 23") +```erb +<%= f.combobox :priority, + { "Levels" => [["Low", 1, { description: "Can wait" }], ["High", 3, { icon: ''.html_safe, disabled: true }]] }, + { check: true, combobox: { placement: "top-start", offset: [0, 4], search_normalize: true } }, + { disabled: true } %> +``` + +Generated HTML: + +```html +
+ + + + +
+``` + +The `combobox` option takes Bootstrap's remaining JavaScript options (`placement`, `offset`, `boundary`, `search_normalize`) and renders them as `data-bs-*` attributes; arrays are converted to JSON. + +**Caveat:** with `multiple: true`, Bootstrap submits the selected values as a single comma-separated string (e.g. `"1,3"`), not as a Rails-style array parameter. Split it in your controller: `params[:user][:skills].split(",")`. + +## Chip Inputs + +Bootstrap 6 also ships a [chips component](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/chips/) for tag-style input. Use the `chip_input` helper to render one; the attribute's current value (an array, or a string split on the separator) is rendered as dismissible chips, followed by the `form-ghost` text input that adds more: + +![Example 24](demo/doc/screenshots/bootstrap/readme/24_example.png "Example 24") +```erb +<%= f.chip_input :skills, placeholder: "Add a skill…" %> +``` + +This generates: + +```html +
+ +
+ +
+
+``` + +Configuration goes in the `chips` option and is rendered as `data-bs-*` attributes on the container; `class:` adds container classes such as a theme, and `values:` overrides the attribute value: + +![Example 25](demo/doc/screenshots/bootstrap/readme/25_example.png "Example 25") +```erb +<%= f.chip_input :skills, values: %w[Ruby Rails], + chips: { class: "theme-primary", separator: ";", max_chips: 5, dismissible: false } %> +``` + +Generated HTML: + +```html +
+ +
+ Ruby + Rails + +
+
+``` + +**Caveat:** as of Bootstrap 6.0.0-alpha.1 chips have no form-submission integration. Read the values with the component's `getValues()` JavaScript API, or listen for its `change.bs.chips` event and copy the values into a hidden field. + ## Checkboxes and Radios Checkboxes and radios should be placed inside of a `form_group` to render properly. The following example ensures that the entire form group will display an error if an associated validations fails: -![Example 19](demo/doc/screenshots/bootstrap/readme/19_example.png "Example 19") +![Example 26](demo/doc/screenshots/bootstrap/readme/26_example.png "Example 26") ```erb <%= f.form_group :skill_level, label: { text: "Skill" }, help: "Optional Help Text" do %> <%= f.radio_button :skill_level, 0, label: "Novice", checked: true %> @@ -631,34 +848,34 @@ an error if an associated validations fails: This generates: ```html -
- -
- - +
+ +
+ +
-
- - +
+ +
-
- - +
+ +
- Optional Help Text + Optional Help Text
-
-
+
+
- - + +
``` You can also create a checkbox using a block: -![Example 20](demo/doc/screenshots/bootstrap/readme/20_example.png "Example 20") +![Example 27](demo/doc/screenshots/bootstrap/readme/27_example.png "Example 27") ```erb <%= f.form_group :terms, label: { text: "Optional Label" } do %> <%= f.check_box :terms do %> @@ -670,12 +887,12 @@ You can also create a checkbox using a block: This generates: ```html -
+
-
+
- -
@@ -684,7 +901,7 @@ This generates: To display checkboxes and radios inline, pass the `inline: true` option: -![Example 21](demo/doc/screenshots/bootstrap/readme/21_example.png "Example 21") +![Example 28](demo/doc/screenshots/bootstrap/readme/28_example.png "Example 28") ```erb <%= f.form_group :skill_level, label: { text: "Skill" } do %> <%= f.radio_button :skill_level, 0, label: "Novice", inline: true %> @@ -696,26 +913,62 @@ To display checkboxes and radios inline, pass the `inline: true` option: This generates: ```html -
- -
- - +
+ +
+ +
-
- - +
+ +
-
- - +
+ +
``` -Check boxes and radio buttons are wrapped in a `div.form-check`. You can add classes to this `div` with the `:wrapper_class` option: +Pass `card: true` to render Bootstrap's card-style field (`form-field-card`), which highlights the whole field when its control is checked. Combined with `help`, that gives the "group cards" from the Bootstrap documentation: -![Example 22](demo/doc/screenshots/bootstrap/readme/22_example.png "Example 22") +![Example 29](demo/doc/screenshots/bootstrap/readme/29_example.png "Example 29") +```erb +<%= f.form_group :plan, label: { text: "Select a plan" }, class: "form-group mb-3" do %> +
+ <%= f.radio_button :plan, "basic", label: "Basic", help: "$9/month — For individuals.", card: true, checked: true %> + <%= f.radio_button :plan, "pro", label: "Pro", help: "$29/month — For teams.", card: true %> +
+<% end %> +``` + +Generated HTML: + +```html +
+ +
+
+ +
+ + $9/month — For individuals. +
+
+
+ +
+ + $29/month — For teams. +
+
+
+
+``` + +Check boxes and radio buttons are wrapped in a `div.form-field`. You can add classes to this `div` with the `:wrapper_class` option: + +![Example 30](demo/doc/screenshots/bootstrap/readme/30_example.png "Example 30") ```erb <%= f.radio_button :skill_level, 0, label: "Novice", inline: true, wrapper_class: "w-auto" %> ``` @@ -723,15 +976,15 @@ Check boxes and radio buttons are wrapped in a `div.form-check`. You can add cla This generates: ```html -
- - +
+ +
``` You can also add a style to the tag using the `wrapper` option: -![Example 23](demo/doc/screenshots/bootstrap/readme/23_example.png "Example 23") +![Example 31](demo/doc/screenshots/bootstrap/readme/31_example.png "Example 31") ```erb <%= f.check_box :skilled, inline: true, wrapper: {style: "color: green"} %> <%= f.radio_button :skill_level, 0, label: "Novice", inline: true, wrapper: {class: 'w-auto', style: "color: red"} %> @@ -740,22 +993,22 @@ You can also add a style to the tag using the `wrapper` option: This generates: ```html -
+
- - + +
-
- - +
+ +
``` ### Switches -To render checkboxes as switches with Bootstrap 4.2+, use `switch: true`: +To render checkboxes as switches, use `switch: true`: -![Example 24](demo/doc/screenshots/bootstrap/readme/24_example.png "Example 24") +![Example 32](demo/doc/screenshots/bootstrap/readme/32_example.png "Example 32") ```erb <%= f.check_box :remember_me, switch: true %> ``` @@ -763,10 +1016,39 @@ To render checkboxes as switches with Bootstrap 4.2+, use `switch: true`: This generates: ```html -
- - - +
+
+ + +
+ +
+``` + +Theme colors, sizes and other switch styling belong on the `switch` wrapper rather than the input, so pass them in the `switch` option: a string adds classes, and a hash sets any attributes: + +![Example 33](demo/doc/screenshots/bootstrap/readme/33_example.png "Example 33") +```erb +<%= f.check_box :remember_me, switch: "theme-success switch-lg" %> +<%= f.check_box :remember_me, switch: { style: "--bs-switch-width: 2.75rem; --bs-switch-indicator-width: 1.5rem;" } %> +``` + +Generated HTML: + +```html +
+
+ + +
+ +
+
+
+ + +
+
``` @@ -775,7 +1057,7 @@ This generates: `bootstrap_form` also provides helpers that automatically create the `form_group` and the `radio_button`s or `check_box`es for you: -![Example 25](demo/doc/screenshots/bootstrap/readme/25_example.png "Example 25") +![Example 34](demo/doc/screenshots/bootstrap/readme/34_example.png "Example 34") ```erb <%= f.collection_radio_buttons :skill_level, Skill.all, :id, :name %> <%= f.collection_check_boxes :skills, Skill.all, :id, :name %> @@ -784,27 +1066,27 @@ This generates: This generates: ```html -
+
Skill level
-
- - +
+ +
-
- - +
+ +
- -
+ +
Skills
-
- - +
+ +
-
- - +
+ +
``` @@ -816,12 +1098,13 @@ Collection methods accept these options: * `:label`: Customize the `form_group`'s label * `:hide_label`: Pass true to hide the `form_group`'s label * `:help`: Add a help span to the `form_group` +* `:id`: Set the id of the group's label (and of the error feedback element), for example to keep ids unique when the same attribute appears twice on a page * Other options will be forwarded to the `radio_button`/`check_box` method To add `data-` attributes to a collection of radio buttons, map your models to an array and add a hash: -![Example 26](demo/doc/screenshots/bootstrap/readme/26_example.png "Example 26") +![Example 35](demo/doc/screenshots/bootstrap/readme/35_example.png "Example 35") ```erb <%# Use the :first and :second elements of the array to be the value and label respectively %> <%- choices = @collection.map { |addr| [ addr.id, addr.street, { 'data-zip-code': addr.zip_code } ] } -%> @@ -832,15 +1115,15 @@ To add `data-` attributes to a collection of radio buttons, map your models to a This generates: ```html -
+
Misc
-
- - +
+ +
-
- - +
+ +
``` @@ -849,7 +1132,7 @@ This generates: You can create a range control like this: -![Example 27](demo/doc/screenshots/bootstrap/readme/27_example.png "Example 27") +![Example 36](demo/doc/screenshots/bootstrap/readme/36_example.png "Example 36") ```erb <%= f.range_field :excellence %> ``` @@ -857,9 +1140,34 @@ You can create a range control like this: This generates: ```html -
+
- +
+ +
+
+``` + +Theme colors and the value bubble live on the `form-range` wrapper, so pass them in the `range` option: a string adds classes, and a hash can also enable Bootstrap's value bubble with `bubble: true`. Tick marks work as in plain HTML, through the input's `list` attribute and a ``: + +![Example 37](demo/doc/screenshots/bootstrap/readme/37_example.png "Example 37") +```erb +<%= f.range_field :excellence, range: { bubble: true, class: "theme-success" }, min: 0, max: 100, step: 25, list: "excellence_ticks" %> +``` + +This generates: + +```html +
+ +
+ + +
``` @@ -867,7 +1175,7 @@ This generates: You can create a static control like this: -![Example 28](demo/doc/screenshots/bootstrap/readme/28_example.png "Example 28") +![Example 38](demo/doc/screenshots/bootstrap/readme/38_example.png "Example 38") ```erb <%= f.static_control :email %> ``` @@ -875,7 +1183,7 @@ You can create a static control like this: This generates: ```html -
+
@@ -883,7 +1191,7 @@ This generates: Here's the output for a horizontal layout: -![Example 29](demo/doc/screenshots/bootstrap/readme/29_example.png "Example 29") +![Example 39](demo/doc/screenshots/bootstrap/readme/39_example.png "Example 39") ```erb <%= bootstrap_form_for(@user, layout: :horizontal) do |f| %> <%= f.static_control :email %> @@ -895,8 +1203,8 @@ This generates: ```html
- -
+ +
@@ -905,7 +1213,7 @@ This generates: You can also create a static control that isn't based on a model attribute: -![Example 30](demo/doc/screenshots/bootstrap/readme/30_example.png "Example 30") +![Example 40](demo/doc/screenshots/bootstrap/readme/40_example.png "Example 40") ```erb <%= f.static_control :field_name, label: "Custom Static Control", value: "Content Here" %> ``` @@ -913,7 +1221,7 @@ You can also create a static control that isn't based on a model attribute: This generates: ```html -
+
@@ -923,7 +1231,7 @@ This generates: You can also create the static control the following way, if you don't need to get the value of the static control as a parameter when the form is submitted: -![Example 31](demo/doc/screenshots/bootstrap/readme/31_example.png "Example 31") +![Example 41](demo/doc/screenshots/bootstrap/readme/41_example.png "Example 41") ```erb <%= f.static_control label: "Custom Static Control", value: "Content Here", name: nil %> ``` @@ -931,7 +1239,7 @@ You can also create the static control the following way, if you don't need to g This generates: ```html -
+
@@ -948,7 +1256,7 @@ Passing a block to `static_control` no longer has any effect. You can create a date picker, time picker, or date-time picker with `date_field`, `time_field`, or `datetime_field`, like this: -![Example 32](demo/doc/screenshots/bootstrap/readme/32_example.png "Example 32") +![Example 42](demo/doc/screenshots/bootstrap/readme/42_example.png "Example 42") ```erb <%= f.date_field :joined_at, class: "w-auto" %> ``` @@ -956,7 +1264,7 @@ You can create a date picker, time picker, or date-time picker with `date_field` This generates: ```html -
+
@@ -964,18 +1272,404 @@ This generates: For backwards compatibility, there are also helpers for `date_select`, `time_select`, and `datetime_select`. +### Datepickers + +Bootstrap 6 ships its own [datepicker component](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/datepicker/), based on a text field activated with `data-bs-toggle="datepicker"`. Use the `datepicker_field` helper to generate one: + +![Example 43](demo/doc/screenshots/bootstrap/readme/43_example.png "Example 43") +```erb +<%= f.datepicker_field :joined_at %> +``` + +This generates: + +```html +
+ + +
+``` + +Pass datepicker configuration in the `datepicker` option. Each key is rendered as a `data-bs-*` attribute, so all of Bootstrap's documented datepicker options are available. `Date` (and other `iso8601`-capable) values are converted to `YYYY-MM-DD`, and arrays are converted to JSON: + +![Example 44](demo/doc/screenshots/bootstrap/readme/44_example.png "Example 44") +```erb +<%= f.datepicker_field :vacation, + datepicker: { + selection_mode: "multiple-ranged", + date_min: Date.current, + date_max: Date.current + 1.year, + display_months_count: 2 + } %> +``` + +This generates: + +```html +
+ + +
+``` + +To pre-select dates, pass an array in the `selected_dates` option. It's rendered as a JSON array in the `data-bs-selected-dates` attribute, and `Date` values are converted to ISO 8601 strings: + +![Example 45](demo/doc/screenshots/bootstrap/readme/45_example.png "Example 45") +```erb +<%= f.datepicker_field :misc, datepicker: { selected_dates: [Date.new(2026, 7, 2), "2026-07-04"] } %> +``` + +This generates: + +```html +
+ + +
+``` + +Note that the datepicker requires Bootstrap's JavaScript. `autocomplete="off"` is set by default, as the Bootstrap documentation recommends; pass your own `autocomplete` option to override it. + +Pass `inline: true` for an always-visible calendar. It renders a `div` with a hidden input that Bootstrap keeps in sync, so the selected date(s) are submitted with the form; the attribute's current value is pre-selected. The calendar itself is mounted on a child element (via `data-bs-position-element`) so that rendering it leaves the hidden input in place: + +![Example 46](demo/doc/screenshots/bootstrap/readme/46_example.png "Example 46") +```erb +<%= f.datepicker_field :joined_at, inline: true, datepicker: { selection_mode: "multiple-ranged" } %> +``` + +This generates: + +```html +
+ +
+ +
+
+
+
+ +
+
+
+ Mon + Tue + Wed + Thu + Fri + Sat + Sun +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+``` + +Pass `button: "Select date"` to render a button trigger instead of a text field (a hash `button: { text:, icon: }` adds HTML before the text, and `class:` replaces the default `btn-outline theme-secondary`). Bootstrap updates the button's text with the selection but submits no value, so use its `change.bs.datepicker` event or the `display_element` option if you need the date elsewhere: + +![Example 47](demo/doc/screenshots/bootstrap/readme/47_example.png "Example 47") +```erb +<%= f.datepicker_field :joined_at, button: { text: "Select date", icon: ''.html_safe } %> +``` + +Generated HTML: + +```html +
+ + +
+``` + The multiple selects that the date and time helpers (`date_select`, `time_select`, `datetime_select`) generate are wrapped inside a `div.rails-bootstrap-forms-[date|time|datetime]-select` tag. This is because Bootstrap automatically styles our controls as `block`s. This wrapper fixes this by defining these selects as `inline-block` and a width of `auto`. +## OTP Inputs + +Bootstrap 6 ships an [OTP input](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/otp-input/) for one-time codes: a single real text input that Bootstrap's JavaScript renders as separate digit slots. Use the `otp_field` helper: + +![Example 48](demo/doc/screenshots/bootstrap/readme/48_example.png "Example 48") +```erb +<%= f.otp_field :verification_code %> +``` + +This generates: + +```html +
+ +
+ + +
+
+``` + +The number of slots comes from `maxlength` (default 6). Configuration goes in the `otp` option and is rendered as `data-bs-*` attributes on the container — arrays are converted to JSON, and `class:` adds container classes such as `otp-connected`, `otp-sm`, or `otp-lg`: + +![Example 49](demo/doc/screenshots/bootstrap/readme/49_example.png "Example 49") +```erb +<%= f.otp_field :verification_code, maxlength: 8, + otp: { type: :numeric, groups: [4, 4], separator: "-", class: "otp-connected" } %> +``` + +Generated HTML: + +```html +
+ +
+ + +
+
+``` + +When the attribute has a validation error, `is-invalid` is applied to the `.otp` container (as Bootstrap expects) as well as the input, and the usual `invalid-feedback` follows. + +## Password Strength Meters + +Bootstrap 6's [password strength](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/password-strength/) component evaluates a password as the user types. Pass the `strength` option to `password_field` to render a meter after the input: + +![Example 50](demo/doc/screenshots/bootstrap/readme/50_example.png "Example 50") +```erb +<%= f.password_field :password, strength: true %> +``` + +This generates: + +```html +
+ + +
+
+
+
+
+
+
+
+
+
+
+``` + +Pass a hash for the progress-bar variant, a minimum length, and a dynamic text message: + +![Example 51](demo/doc/screenshots/bootstrap/readme/51_example.png "Example 51") +```erb +<%= f.password_field :password, strength: { variant: :bar, min_length: 12, text: true } %> +``` + +Generated HTML: + +```html +
+ + +
+
+ + +
+``` + +which renders `
` followed by ``. + +To render a meter at a fixed level without JavaScript, for example from a server-side check, pass `level:` (`weak`, `fair`, `good` or `strong`). The level goes in `data-bs-strength`, the matching number of segments get the `active` class, and `text:` can be a string to show alongside it. The static meter opts out of Bootstrap's JavaScript (which would otherwise evaluate the password field next to it) by pointing `data-bs-input` at a selector that matches nothing: + +![Example 52](demo/doc/screenshots/bootstrap/readme/52_example.png "Example 52") +```erb +<%= f.password_field :password, strength: { level: :good, text: "Good password" } %> +``` + +Generated HTML: + +```html +
+ + +
+
+
+
+
+
+
+
+
+
+ Good password +
+``` + ## Submit Buttons -The `btn btn-secondary` CSS classes are automatically added to your submit +The `btn-solid theme-secondary` CSS classes are automatically added to your submit buttons. -![Example 33](demo/doc/screenshots/bootstrap/readme/33_example.png "Example 33") +![Example 53](demo/doc/screenshots/bootstrap/readme/53_example.png "Example 53") ```erb <%= f.submit %> ``` @@ -983,13 +1677,13 @@ buttons. This generates: ```html - + ``` -You can also use the `primary` helper, which adds `btn btn-primary` to your +You can also use the `primary` helper, which adds `btn-solid theme-primary` to your submit button: -![Example 34](demo/doc/screenshots/bootstrap/readme/34_example.png "Example 34") +![Example 54](demo/doc/screenshots/bootstrap/readme/54_example.png "Example 54") ```erb <%= f.primary "Optional Label" %> ``` @@ -997,20 +1691,20 @@ submit button: This generates: ```html - + ``` You can specify your own classes like this: -![Example 35](demo/doc/screenshots/bootstrap/readme/35_example.png "Example 35") +![Example 55](demo/doc/screenshots/bootstrap/readme/55_example.png "Example 55") ```erb -<%= f.submit "Log In", class: "btn btn-success" %> +<%= f.submit "Log In", class: "btn-solid theme-success" %> ``` This generates: ```html - + ``` If the `primary` helper receives a `render_as_button: true` option or a block, @@ -1018,7 +1712,7 @@ it will be rendered as an HTML button, instead of an input tag. This allows you to specify HTML content and styling for your buttons (such as adding illustrative icons to them). For example, the following statements -![Example 36](demo/doc/screenshots/bootstrap/readme/36_example.png "Example 36") +![Example 56](demo/doc/screenshots/bootstrap/readme/56_example.png "Example 56") ```erb <%= f.primary "Save changes ".html_safe, render_as_button: true %> @@ -1031,10 +1725,10 @@ illustrative icons to them). For example, the following statements This generates: ```html - - ``` @@ -1042,7 +1736,7 @@ This generates: are equivalent, and each of them both be rendered as: ```html - + ``` If you wish to add additional CSS classes to your button, while keeping the @@ -1052,7 +1746,7 @@ Bootstrap classes), or for element targeting via CSS classes. Be aware, however, that using the `class` option will discard any extra classes you add. As an example, the following button declarations -![Example 37](demo/doc/screenshots/bootstrap/readme/37_example.png "Example 37") +![Example 57](demo/doc/screenshots/bootstrap/readme/57_example.png "Example 57") ```erb <%= f.primary "My Nice Button", extra_class: 'my-button' %> @@ -1062,7 +1756,7 @@ you add. As an example, the following button declarations will be rendered as ```html - + ``` @@ -1070,7 +1764,7 @@ will be rendered as ## Rich Text Areas AKA Trix Editor -![Example 38](demo/doc/screenshots/bootstrap/readme/38_example.png "Example 38") +![Example 58](demo/doc/screenshots/bootstrap/readme/58_example.png "Example 58") ```erb <%= f.rich_text_area(:life_story) %> ``` @@ -1078,7 +1772,7 @@ will be rendered as will be rendered as: ```html -
+
@@ -1111,7 +1805,7 @@ will be rendered as:
``` @@ -1138,7 +1832,7 @@ The `hidden_field` helper in `bootstrap_form` calls the Rails helper directly, a If you want to use the original Rails form helpers for a particular field, append `_without_bootstrap` to the helper: -![Example 39](demo/doc/screenshots/bootstrap/readme/39_example.png "Example 39") +![Example 59](demo/doc/screenshots/bootstrap/readme/59_example.png "Example 59") ```erb <%= f.text_field_without_bootstrap :email %> ``` @@ -1160,7 +1854,7 @@ To use an inline-layout form, use the `layout: :inline` option. To hide labels, use the `hide_label: true` option, which keeps your labels accessible to those using screen readers. -![Example 40](demo/doc/screenshots/bootstrap/readme/40_example.png "Example 40") +![Example 60](demo/doc/screenshots/bootstrap/readme/60_example.png "Example 60") ```erb <%= bootstrap_form_for(@user, layout: :inline) do |f| %> <%= f.email_field :email, hide_label: true %> @@ -1183,21 +1877,21 @@ This generates:
-
+
- - + +
- +
``` To skip label rendering at all, use `skip_label: true` option. -![Example 41](demo/doc/screenshots/bootstrap/readme/41_example.png "Example 41") +![Example 61](demo/doc/screenshots/bootstrap/readme/61_example.png "Example 61") ```erb <%= f.password_field :password, skip_label: true %> ``` @@ -1205,7 +1899,7 @@ To skip label rendering at all, use `skip_label: true` option. This generates: ```html -
+
``` @@ -1214,14 +1908,14 @@ This generates: To use a horizontal-layout form with labels to the left of the control, use the `layout: :horizontal` option. You should specify both `label_col` and -`control_col` css classes as well (they default to `col-sm-2` and `col-sm-10`). +`control_col` css classes as well (they default to `sm:col-2` and `sm:col-10`). In the example below, the submit button has been wrapped in a `form_group` to keep it properly aligned. -![Example 42](demo/doc/screenshots/bootstrap/readme/42_example.png "Example 42") +![Example 62](demo/doc/screenshots/bootstrap/readme/62_example.png "Example 62") ```erb -<%= bootstrap_form_for(@user, layout: :horizontal, label_col: "col-sm-2", control_col: "col-sm-10") do |f| %> +<%= bootstrap_form_for(@user, layout: :horizontal, label_col: "sm:col-2", control_col: "sm:col-10") do |f| %> <%= f.email_field :email %> <%= f.password_field :password %> <%= f.check_box :remember_me %> @@ -1236,29 +1930,29 @@ This generates: ```html
- -
+ +
- -
+ +
-
-
+
+
- - + +
-
- +
+
@@ -1266,12 +1960,12 @@ This generates: The `label_col` and `control_col` css classes can also be changed per control: -![Example 43](demo/doc/screenshots/bootstrap/readme/43_example.png "Example 43") +![Example 63](demo/doc/screenshots/bootstrap/readme/63_example.png "Example 63") ```erb <%= bootstrap_form_for(@user, layout: :horizontal) do |f| %> <%= f.email_field :email %> - <%= f.text_field :age, label_col: %w[col-sm-3 text-bg-info], control_col: %w[col-sm-3 text-bg-success] %> - <%= f.check_box :terms, label_col: "", control_col: "col-sm-11" %> + <%= f.text_field :age, label_col: %w[sm:col-3 text-bg-info], control_col: %w[sm:col-3 text-bg-success] %> + <%= f.check_box :terms, label_col: "", control_col: "sm:col-11" %> <%= f.form_group do %> <%= f.submit %> <% end %> @@ -1283,29 +1977,29 @@ This generates: ```html
- -
+ +
- -
+ +
-
-
+
+
- - + +
-
- +
+
@@ -1318,10 +2012,10 @@ or default value can be changed in initializer: module BootstrapForm class FormBuilder def default_label_col - 'col-sm-4' + 'sm:col-4' end def default_control_col - 'col-sm-8' + 'sm:col-8' end def default_layout # :vertical, :horizontal or :inline @@ -1333,7 +2027,7 @@ end Control col wrapper class can be modified with `add_control_col_class`. This option will preserve column definition: -![Example 44](demo/doc/screenshots/bootstrap/readme/44_example.png "Example 44") +![Example 64](demo/doc/screenshots/bootstrap/readme/64_example.png "Example 64") ```erb <%= bootstrap_form_for(@user, layout: :horizontal) do |f| %> <%= f.email_field :email %> @@ -1349,20 +2043,20 @@ This generates: ```html
- -
+ +
- -
+ +
-
- +
+
@@ -1372,7 +2066,7 @@ This generates: The form-level `layout` can be overridden per field, unless the form-level layout was `inline`: -![Example 45](demo/doc/screenshots/bootstrap/readme/45_example.png "Example 45") +![Example 65](demo/doc/screenshots/bootstrap/readme/65_example.png "Example 65") ```erb <%= bootstrap_form_for(@user, layout: :horizontal) do |f| %> <%= f.email_field :email %> @@ -1390,33 +2084,33 @@ This generates: ```html
- -
+ +
-
+
-
+
-
-
+
+
- - + +
- + ``` @@ -1424,10 +2118,10 @@ A form-level `layout: :inline` can't be overridden because of the way Bootstrap ### Floating Labels -The `floating` option can be used to enable Bootstrap 5's floating labels. This option is supported on text fields +The `floating` option can be used to enable Bootstrap's floating labels. This option is supported on text fields and dropdowns. Here's an example: -![Example 46](demo/doc/screenshots/bootstrap/readme/46_example.png "Example 46") +![Example 66](demo/doc/screenshots/bootstrap/readme/66_example.png "Example 66") ```erb <%= bootstrap_form_for(@user) do |f| %> <%= f.email_field :email, floating: true %> @@ -1442,40 +2136,114 @@ This generates: ```html
-
- - +
+
+ + +
-
- - +
+
+ + +
-
- - +
+
+ + +
-
- - +
+
+ + +
- + ``` +Floating labels also work inside input groups. Bootstrap nests the `form-floating` wrapper inside the `input-group`, with any validation feedback after the group, and `bootstrap_form` does the same when you combine `floating: true` with `prepend` or `append`: + +![Example 67](demo/doc/screenshots/bootstrap/readme/67_example.png "Example 67") +```erb +<%= f.text_field :username, floating: true, prepend: "@" %> +``` + +This generates: + +```html +
+
+ @ +
+ + +
+
+
+``` + ## Validation and Errors Rails normally wraps fields with validation errors in a `div.field_with_errors`, but this behaviour isn't consistent with Bootstrap 4 styling. By default, `bootstrap_form` generations in-line errors which appear below the field. But it can also generate errors on the label, or not display any errors, leaving it up to you. +### Client-Side Validation + +Bootstrap 6 styles browser-validated fields via the `data-bs-validate` attribute on the form. Pass `validate: true` to the form helper to enable it (this also sets `novalidate`, as the Bootstrap documentation recommends, to suppress the browser's default messages): + +```erb +<%= bootstrap_form_for(@user, validate: true) do |f| %> + ... +<% end %> +``` + +Pass `validate: "valid"` to also show success styling on valid fields (`data-bs-validate="valid"`). + +Browser validation needs messages to show, so every helper accepts `invalid_feedback:` and `valid_feedback:` for static `invalid-feedback` and `valid-feedback` elements after the control (a model error replaces the static invalid message). `tooltip: true` renders them as Bootstrap's tooltip-style feedback instead, and `valid: true` applies the `is-valid` success state, for example after a server-side check: + +![Example 68](demo/doc/screenshots/bootstrap/readme/68_example.png "Example 68") +```erb +<%= f.email_field :email, required: true, invalid_feedback: "Please enter a valid email address.", valid_feedback: "Looks good!" %> +<%= f.check_box :terms, required: true, invalid_feedback: "You must agree before submitting." %> +<%= f.text_field :username, valid: true, valid_feedback: "Username is available.", tooltip: true %> +``` + +This generates: + +```html +
+ + +
Please enter a valid email address.
+
Looks good!
+
+
+ + +
+ +
You must agree before submitting.
+
+
+
+ + +
Username is available.
+
+``` + ### Inline Errors By default, fields that have validation errors will be outlined in red and the error will be displayed below the field. Here's an example: -![Example 47](demo/doc/screenshots/bootstrap/readme/47_example.png "Example 47") +![Example 69](demo/doc/screenshots/bootstrap/readme/69_example.png "Example 69") ```erb <%= bootstrap_form_for(@user_with_error) do |f| %> <%= f.email_field :email %> @@ -1491,37 +2259,41 @@ Generated HTML: ```html
-
+
is invalid
-
+
Misc
-
- - +
+ +
-
- - -
is invalid
+
+ +
+ +
is invalid
+
- -
+ +
Preferences
-
- - +
+ +
-
- - -
is invalid
+
+ +
+ +
is invalid
+
-
+
is invalid
@@ -1542,7 +2314,7 @@ You can turn off inline errors for the entire form like this: You can also display validation errors in the field's label; just turn on the `:label_errors` option. Here's an example: -![Example 48](demo/doc/screenshots/bootstrap/readme/48_example.png "Example 48") +![Example 70](demo/doc/screenshots/bootstrap/readme/70_example.png "Example 70") ```erb <%= bootstrap_form_for(@user_with_error, label_errors: true) do |f| %> <%= f.email_field :email %> @@ -1553,7 +2325,7 @@ Generated HTML: ```html -
+
@@ -1575,7 +2347,7 @@ To display an error message with an error summary, you can use the `alert_message` helper. This won't output anything unless a model validation has failed, and additional options will be passed on as attributes to the root `div`: -![Example 49](demo/doc/screenshots/bootstrap/readme/49_example.png "Example 49") +![Example 71](demo/doc/screenshots/bootstrap/readme/71_example.png "Example 71") ```erb <%= bootstrap_form_for @user_with_error do |f| %> <%= f.alert_message "Please fix the errors below.", tabindex: -1, role: "note" %> @@ -1586,7 +2358,7 @@ Which outputs: ```html -
+

Please fix the errors below.

  • Email is invalid
  • @@ -1599,7 +2371,7 @@ Which outputs: You can turn off the error summary like this: -![Example 50](demo/doc/screenshots/bootstrap/readme/50_example.png "Example 50") +![Example 72](demo/doc/screenshots/bootstrap/readme/72_example.png "Example 72") ```erb <%= bootstrap_form_for @user_with_error do |f| %> <%= f.alert_message "Please fix the errors below.", error_summary: false %> @@ -1610,13 +2382,13 @@ This generates: ```html -
    Please fix the errors below.
    +
    Please fix the errors below.
    ``` To output a simple unordered list of errors, use the `error_summary` helper. -![Example 51](demo/doc/screenshots/bootstrap/readme/51_example.png "Example 51") +![Example 73](demo/doc/screenshots/bootstrap/readme/73_example.png "Example 73") ```erb <%= bootstrap_form_for @user_with_error do |f| %> <%= f.error_summary %> @@ -1639,7 +2411,7 @@ Which outputs: If you want to display a custom inline error for a specific attribute not represented by a form field, use the `errors_on` helper. -![Example 52](demo/doc/screenshots/bootstrap/readme/52_example.png "Example 52") +![Example 74](demo/doc/screenshots/bootstrap/readme/74_example.png "Example 74") ```erb <%= bootstrap_form_for @user_with_error do |f| %> @@ -1660,7 +2432,7 @@ Note that the `invalid-feedback` `div` is hidden unless there is a preceding ele You can hide the attribute name like this: -![Example 53](demo/doc/screenshots/bootstrap/readme/53_example.png "Example 53") +![Example 75](demo/doc/screenshots/bootstrap/readme/75_example.png "Example 75") ```erb <%= bootstrap_form_for @user_with_error do |f| %> @@ -1679,7 +2451,7 @@ Which outputs: You can also use a custom class for the wrapping div, like this: -![Example 54](demo/doc/screenshots/bootstrap/readme/54_example.png "Example 54") +![Example 76](demo/doc/screenshots/bootstrap/readme/76_example.png "Example 76") ```erb <%= bootstrap_form_for @user_with_error do |f| %> @@ -1718,7 +2490,7 @@ ActiveModel::Validations::PresenceValidator. In cases where this behaviour is undesirable, use the `required` option to force the class to be present or absent: -![Example 55](demo/doc/screenshots/bootstrap/readme/55_example.png "Example 55") +![Example 77](demo/doc/screenshots/bootstrap/readme/77_example.png "Example 77") ```erb <%= f.password_field :login, label: "New Username", required: true %> <%= f.password_field :password, label: "New Password", required: false %> @@ -1727,11 +2499,11 @@ In cases where this behaviour is undesirable, use the `required` option to force This generates: ```html -
    +
    -
    +
    @@ -1741,7 +2513,7 @@ This generates: Adding a form control for a `belongs_to` field will automatically pick up the associated presence validator. -![Example 56](demo/doc/screenshots/bootstrap/readme/56_example.png "Example 56") +![Example 78](demo/doc/screenshots/bootstrap/readme/78_example.png "Example 78") ```erb <%= bootstrap_form_for(@address, url: '/address') do |f| %> <%= f.collection_select :user_id, @users, :id, :email, include_blank: "Select a value" %> @@ -1757,30 +2529,30 @@ Generated HTML: ```html
    -
    +
    -
    -
    +
    -
    +
    -
    +
    -
    +
    - + ``` @@ -1788,7 +2560,7 @@ Generated HTML: Fields can be disabled using the standard Rails form helper option. -![Example 57](demo/doc/screenshots/bootstrap/readme/57_example.png "Example 57") +![Example 79](demo/doc/screenshots/bootstrap/readme/79_example.png "Example 79") ```erb <%= bootstrap_form_for @user do |f| %>
    @@ -1812,53 +2584,53 @@ Generated HTML:
    -
    +
    -
    +
    -
    +
    -
    +
    -
    +
    -
    - - +
    + +
    -
    +
    - - + +
    -
    +
    - @@ -1866,13 +2638,13 @@ Generated HTML:
    -
    +
    - + ``` diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md new file mode 100644 index 000000000..e4bf42a92 --- /dev/null +++ b/UPGRADE-6.0.md @@ -0,0 +1,131 @@ +# Upgrading to `bootstrap_form` 6.0 + +`bootstrap_form` 6.0 generates mark-up for Bootstrap 6. Bootstrap 6 renames and restructures many of the form-related classes, so the mark-up generated by this gem changes accordingly. If your application styles or scripts against the generated mark-up, or if you pass Bootstrap utility classes to the helpers, you will need to make changes. + +## Bootstrap 6 Changes + +Upgrading `bootstrap_form` to version 6 means you must upgrade your whole application to Bootstrap 6. Read the [Bootstrap 6 migration guide](https://github.com/twbs/bootstrap/blob/v6-dev/skills/bootstrap-v5-v6-migration/SKILL.md) and the [Bootstrap 6 forms documentation](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/overview/) to see what changes you have to make to your views. + +## Changes to Generated Mark-up + +### Form Group Wrappers + +Bootstrap 6 introduces the [`form-field` wrapper](https://v6-dev--twbs-bootstrap.netlify.app/docs/6.0/forms/field/), a CSS-grid container that lays out a control together with its label, help text, and validation feedback. `bootstrap_form` now uses it as the default form group wrapper: + +- Vertical form groups are wrapped in `class="form-field mb-3"` (was `class="mb-3"`). +- Collections rendered with `group_around_collections = true` are wrapped in `class="form-group mb-3"`, Bootstrap 6's wrapper for multiple `form-field`s under a shared label. +- Horizontal groups (`mb-3 row`), inline groups, and floating label groups (`mb-3 form-floating`) are unchanged, because they use Bootstrap's grid or the `form-floating` component for layout instead. +- A custom `wrapper_class`/`wrapper: { class: ... }` still *replaces* the default classes. Include `form-field` in your custom class if you want to keep Bootstrap 6's field layout. + +### Help Text + +Help text is now rendered as `` without the `text-muted` class. Bootstrap 6 removes `text-muted` (its replacement is `fg-secondary`), and `form-text` styles itself. + +### Selects + +Bootstrap 6 styles ` + +
    + +
    +``` + +instead of the v5 `form-check form-switch` wrapper with a `form-check-input` class. + +### Range Fields + +Bootstrap 6 wraps range inputs in a component element. `range_field` now generates: + +```html +
    + +
    +``` + +instead of a bare ``. + +### Floating Labels + +Bootstrap 6 requires the `
    - - - + + + + +