pickerId/picker_id is a required prop to instantiate the Date Picker. The presence of pickerId/picker_id in your Date Picker also associates the label with the input, providing the ability to focus the Date Picker by clicking the label.
The defaultDate/default_date prop has a null or empty string value by default. You can pass an ISO date string (preferred rails method) or date object (preferred JS method) if you want a default value on page load. Use Ruby UTC DateTime objects and convert them to ISO date strings with DateTime.now.utc.iso8601.
If you use a Date object without UTC time standardization the Date Picker kit may misinterpret that date as yesterdays date (consequence of timezone differentials and the Javascript Date Object constructor). See this GitHub issue for more information and the anti-pattern examples below.
You can leverage the defaultDate/default_date prop with custom logic in your filter or controller files where the determination of the default value changes based on user interaction. The page can load with an initial default date picker value or placeholder text, then after filter submission save the submitted values as the "new default" (via state or params).
<%= pb_rails("date_picker", props: { default_date: "07/25/2020", label: "Default Date String", picker_id: "date-picker-default-date1" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, label: "Default Date Dynamic", picker_id: "date-picker-default-date2" }) %> <%= pb_rails("date_picker", props: { label: "Default Behavior", picker_id: "date-picker-default-date4" }) %>
Setting the allowInput prop to true permits users to key values directly into the input. This prop is set to false by default.
The date picker is built with the text input kit. Text input props you pass to the date picker kit will be forwarded to the input, with a few exceptions. The value attribute is automatically handled and bound to whatever date string is contained by the input field. You cannot pass a custom value prop. id props passed to the date picker kit will be assigned to it's parent/wrapper div. The pickerId prop is passed directly to the input and is required to instatiate the date picker.
You must use inputAria or input_aria and inputData or input_data props if you wish to pass data or aria attributes to the text input kit. If you use data or aria props they will be passed to the date picker kit itself instead. Also be aware the default behavior of text input aria and data props is to pass those props to attributes on the wrapping div not on the input itself.
The placeholder prop has a default string value: "Select Date". You can replace this with your own string or an empty string if you'd prefer it blank.
<%= pb_rails("date_picker", props: { input_aria: { label: "input-field" }, input_data: { key: "value", key2: "value2" }, label: "Aria, Name, and Data Attributes", name: "date-field", picker_id: "date-picker-input1", }) %> <%= pb_rails("date_picker", props: { label: "Custom Placeholder", picker_id: "date-picker-input2", placeholder: "custom-placeholder", }) %> <%= pb_rails("date_picker", props: { label: "Blank Placeholder", picker_id: "date-picker-input3", placeholder: "" }) %> <%= pb_rails("date_picker", props: { disable_input: true, label: "Disable Input", picker_id: "date-picker-input4", placeholder: "Disabled Input" }) %>
Default label prop is "Date Picker". To remove the label set the hideLabel prop in React or the hide_label prop in Rails to true.
Your change handler function has access to two arguments: dateStr and selectedDates.
The first, dateStr, is a string of the chosen date. The second, selectedDates, is an array of selected date objects. In many use cases selectedDates will have only one value but you'll still need to access it from index 0.
NOTE: On Change does not account for manual input by users, so if your date picker sets allowInput, you should use the onClose method instead.
<%= pb_rails("date_picker", props: { label: "Date Picker", picker_id: "date-picker-onchange", margin_bottom: "lg" }) %> <%= pb_rails("caption", props: { text: "Date Object" }) %> <%= pb_rails("body", props: { label: "Date Object", text: "Select Date", margin_bottom: "lg", id: "date-picker-onchange-object", }) %> <%= pb_rails("caption", props: { text: "Date String" }) %> <%= pb_rails("body", props: { label: "Date String", text: "Select Date", margin_bottom: "lg", id: "date-picker-onchange-string", }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-onchange")._flatpickr <%# Define Hook %> const changeHook = (selectedDates, dateStr) => { const object = document.querySelector('#date-picker-onchange-object') object.textContent = selectedDates[0] ? selectedDates[0].toString() : '' const string = document.querySelector('#date-picker-onchange-string') string.textContent = dateStr } <%# Push one or more hooks to onChange config array %> fp.config.onChange.push(changeHook) }) <% end %>
The onClose handler function has access to two arguments: dateStr and selectedDates.
The first, dateStr, is a string of the chosen date. The second, selectedDates, is an array of selected date objects. In many use cases selectedDates will have only one value but you'll still need to access it from index 0.
NOTE: onClose is the ideal handler function to use when allowInput is enabled.
<%= pb_rails("date_picker", props: { label: "Date Picker", picker_id: "date-picker-onclose", margin_bottom: "lg" }) %> <%= pb_rails("caption", props: { text: "Date Object" }) %> <%= pb_rails("body", props: { label: "Date Object", text: "Select Date", margin_bottom: "lg", id: "date-picker-onclose-object", }) %> <%= pb_rails("caption", props: { text: "Date String" }) %> <%= pb_rails("body", props: { label: "Date String", text: "Select Date", margin_bottom: "lg", id: "date-picker-onclose-string", }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-onclose")._flatpickr <%# Define Hook %> const closeHook = (selectedDates, dateStr) => { const object = document.querySelector('#date-picker-onclose-object') object.textContent = selectedDates[0] ? selectedDates[0].toString() : '' const string = document.querySelector('#date-picker-onclose-string') string.textContent = dateStr } <%# Push one or more hooks to onClose config array %> fp.config.onClose.push(closeHook) }) <% end %>
To use the quickpick:
mode must be set to rangeselection_type must be set to quickpickThis date range variant uses hidden inputs to handle start and end dates. While they are not required props, it is advisable to specify a unique start_date_id, start_date_name, end_date_id, and end_date_name for each quick pick instance you place in a form and/or on a page.
Like all other date pickers, the quick pick does require a picker_id.
<%= pb_rails("date_picker", props: { allow_input: true, end_date_id: "quick-pick-end-date", end_date_name: "quick-pick-end-date", mode: "range", picker_id: "date-picker-quick-pick", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "quick-pick-start-date", start_date_name: "quick-pick-start-date" }) %>
Use this_ranges_end_today/thisRangesEndToday to set end date on all ranges that start with 'this' to today's date. For instance, by default 'This Year' will set end day to 12/31/(current year), but if the this_ranges_end_today/thisRangesEndToday prop is used, end date on that range will be today's date.
<%= pb_rails("date_picker", props: { allow_input: true, end_date_id: "range-limit-end-date", end_date_name: "range-limit-end-date", mode: "range", picker_id: "thisRangesEndToday", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "range-limit-start-date", start_date_name: "range-limit-start-date", this_ranges_end_today: true }) %>
The customQuickPickDates/custom_quick_pick_dates prop allows for the user/dev to define their own quick pick dates.
The prop accepts an object with two key/value pairs: dates & override (separate doc example below).
The dates property accepts an array of objects. Each object in the array has label and value properties. The label is what will be displayed in the UI of the dropdown menu. The value property is just the date that is going to be passed to the datepicker. The value property can be an array of two strings that represent a range, allowing for the dev to be extremely specific. Additionally, the dates array allows for a clean, simple API under that automatically converts dates in a common vernacular.
The timePeriod property accepts "days", "weeks", "months", "quarters" or "years", representing past time periods.
The amount property accepts any number.
<%= pb_rails("date_picker", props: { allow_input: true, custom_quick_pick_dates: { dates: [ # Allow Playbook to handle the logic... { label: "Last 15 months", value: { timePeriod: "months", amount: 15, }, }, # Or, be explicit with an exact date range for more control... { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"], }, ], }, end_date_id: "quick-pick-end-date", end_date_name: "quick-pick-end-date", mode: "range", picker_id: "date-picker-quick-pick-custom", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "quick-pick-start-date", start_date_name: "quick-pick-start-date" }) %>
The customQuickPickDates/custom_quick_pick_dates prop allows for an override boolean. The override allows for the user to completely override the quick pick dates that ship with the component. Default of override is set to true. If you would like to simply append your dates to the default quick pick dates, set this prop to false explicitly.
<%= pb_rails("date_picker", props: { allow_input: true, custom_quick_pick_dates: { override: false, dates: [ { label: "Last 15 months", value: { timePeriod: "months", amount: 15, }, }, { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"], }, ], }, end_date_id: "quick-pick-end-date", end_date_name: "quick-pick-end-date", mode: "range", picker_id: "date-picker-quick-pick-override", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "quick-pick-start-date", start_date_name: "quick-pick-start-date" }) %>
To set a default value using Quick Pick, use the defaultDate or default_date prop. This prop should match one of the labels displayed in the UI of the dropdown menu.
<%= pb_rails("date_picker", props: { allow_input: true, default_date: "This month", end_date_id: "quick-pick-end-date", end_date_name: "quick-pick-end-date", mode: "range", picker_id: "quick-pick-default-date", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "quick-pick-start-date", start_date_name: "quick-pick-start-date" }) %> <%= pb_rails("date_picker", props: { allow_input: true, custom_quick_pick_dates: { dates: [ { label: "Last 15 months", value: { timePeriod: "months", amount: 15, }, }, { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"], }, ], }, default_date: "First Week of June 2022", end_date_id: "quick-pick-end-date", end_date_name: "quick-pick-end-date", label: "Custom Date Picker", mode: "range", picker_id: "custom-quick-pick-default-date", placeholder: "mm/dd/yyyy to mm/dd/yyyy", selection_type: "quickpick", start_date_id: "quick-pick-start-date", start_date_name: "quick-pick-start-date" }) %>
You can link a Quickpick Dropdown to standard DatePickers using the following props:
For the Quickpick DatePicker:
controls_start_id: ID of the DatePicker that should receive the start date.
controls_end_id: ID of the DatePicker that should receive the end date.
When a quickpick option like “This Year” is selected, it automatically populates the linked start and end inputs.
For the Start/End DatePickers:
sync_start_with: ID of the quickpick this start date is synced to.
sync_end_with: ID of the quickpick this end date is synced to.
When a user manually edits the start or end date, it clears the selected quickpick to prevent conflicting values.
<%= pb_rails("dropdown", props: { id: "dropdown-quickpick-with-date-pickers-1", label: "Date Range", name: "date_range", margin_bottom: "sm", variant: "quickpick", controls_start_id: "start-date-picker-1", controls_end_id: "end-date-picker-1", start_date_id: "quickpick_start_date_1", start_date_name: "start_date", end_date_id: "quickpick_end_date_1", end_date_name: "end_date" }) %> <%= pb_rails("date_picker", props: { picker_id: "start-date-picker-1", label: "Start Date", name: "start_date_picker", placeholder: "Select Start Date", sync_start_with: "dropdown-quickpick-with-date-pickers-1" }) %> <%= pb_rails("date_picker", props: { picker_id: "end-date-picker-1", label: "End Date", name: "end_date_picker", placeholder: "Select End Date", sync_end_with: "dropdown-quickpick-with-date-pickers-1" }) %>
A full list of formatting tokens, i.e. "m/d/Y" can be found here.
<%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "m-d-Y", picker_id: "date-picker-format1" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "m/d/y", picker_id: "date-picker-format2" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "n-j-y", picker_id: "date-picker-format3" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "Y-d-m", picker_id: "date-picker-format4" }) %>
<%= pb_rails("date_picker", props: { disable_date: [(DateTime.current + 1.day).utc.iso8601], label: "Disable Single Date", picker_id: "single-disabled-date" }) %> <%= pb_rails("date_picker", props: { disable_date: [(DateTime.current + 1.day).utc.iso8601, (DateTime.current + 2.day).utc.iso8601], label: "Disable Multiple Dates", picker_id: "multiple-disabled-dates" }) %> <%= pb_rails("date_picker", props: { disable_range: [ { from: DateTime.current.utc.iso8601, to: (DateTime.current + 7.day).utc.iso8601, }, ], label: "Disable Single Range", picker_id: "single-date-range" }) %> <%= pb_rails("date_picker", props: { disable_range: [ { from: (DateTime.current + 1.day).utc.iso8601, to: (DateTime.current + 2.day).utc.iso8601, }, { from: (DateTime.current + 7.day).utc.iso8601, to: (DateTime.current + 14.day).utc.iso8601, }, ], label: "Disable Multiple Ranges", picker_id: "multiple-date-ranges" }) %> <%= pb_rails("date_picker", props: { disable_weekdays: ['Sunday', 'Saturday'], label: "Disable Specific Weekdays", picker_id: "disabled-weekdays" }) %>
<%= pb_rails("date_picker", props: { label: "Dynamic dates", max_date: (DateTime.current + 1.day).utc.iso8601, min_date: (DateTime.current - 1.day).utc.iso8601, picker_id: "date-picker-min-max1" }) %> <%= pb_rails("date_picker", props: { format: "m/d/Y", label: "Absolute formatted dates", max_date: "10/20/2020", min_date: "10/10/2020", picker_id: "date-picker-min-max2" }) %>
<%= pb_rails("button", props: { id: "close-btn", margin_right: "sm", text: "Close" }) %> <%= pb_rails("button", props: { id: "clear-btn", margin_right: "sm", text: "Clear" }) %> <%= pb_rails("button", props: { id: "today-btn", text: "Today" }) %> <%= pb_rails("date_picker", props: { hide_label: true, margin_top: "sm", picker_id: "fp-methods" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { const fp = document.querySelector("#fp-methods")._flatpickr const closeBtn = document.querySelector("#close-btn") const clearBtn = document.querySelector("#clear-btn") const todayBtn = document.querySelector("#today-btn") closeBtn.addEventListener("click", () => { fp.close() }) clearBtn.addEventListener("click", () => { fp.clear() }) todayBtn.addEventListener("click", () => { fp.setDate(new Date(), true) }) }) <% end %>
You can find a full list of flatpickr events and hooks in their documentation.
Use window.addEventListener("DOMContentLoaded", () => {}), not document.addEventListener("DOMContentLoaded", () => {}).
<%= pb_rails("date_picker", props: { label: "onChange", picker_id: "date-picker-hooks-onchange" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-hooks-onchange")._flatpickr <%# Define Hook %> const changeHook = () => { alert('date changed') } <%# Push one or more hooks to onChange config array %> fp.config.onChange.push(changeHook) }) <% end %> <%= pb_rails("date_picker", props: { label: "onOpen", picker_id: "date-picker-hooks-onopen" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-hooks-onopen")._flatpickr <%# Define Hook %> const openHook = () => { alert('calendar opened') } <%# Push one or more hooks to onOpen config array %> fp.config.onOpen.push(openHook) }) <% end %>
Defaults to [1900, 2100]. Combine with min-max prop for best results.
<%= pb_rails("date_picker", props: { label: "Unformatted Date Object", default_date: Date.today, picker_id: "date-picker-anti-pattern1" }) %> <%= pb_rails("date_picker", props: { label: "Date Object Without Time - Displays Yesterday's Date", default_date: Date.today.to_datetime.utc.iso8601, picker_id: "date-picker-anti-pattern2" }) %> <%= pb_rails("date_picker", props: { label: "Unformatted DateTime Object", default_date: DateTime.current, picker_id: "date-picker-anti-pattern3" }) %> <%= pb_rails("date_picker", props: { label: "String Conversion Without ISO Formatting", default_date: DateTime.current.utc.to_s, picker_id: "date-picker-anti-pattern5" }) %>
<%= pb_rails("date_picker", props: { picker_id: "date-picker-none", margin_bottom: "none"}) %> <%= pb_rails("date_picker", props: { picker_id: "date-picker-xs", margin_bottom: "xs"}) %> <%= pb_rails("date_picker", props: { picker_id: "date-picker-sm", margin_bottom: "sm"}) %> <%= pb_rails("date_picker", props: { picker_id: "date-picker-md", margin_bottom: "md"}) %> <%= pb_rails("date_picker", props: { picker_id: "date-picker-lg", margin_bottom: "lg"}) %> <%= pb_rails("date_picker", props: { picker_id: "date-picker-xl", margin_bottom: "xl"}) %>
By default selectType prop is disabled. To activate it set selectionType prop in JSX/TSX to month. To activate it set selection_type prop in a rails file to month.
By default selectType prop is disabled. To activate it set selectionType prop in JSX/TSX to week. To activate it set selection_type prop in a rails file to week.
To select time as well, you should pass the enableTime boolean prop. You can also enable timezone display by passing showTimezone.
Datepicker supports position options from Flatpickr Options Documentation. There are multiple positioning options to choose from.
Note: In order for the above prop to work properly, you must also send staticPosition={false} to your Datepicker kit instance.
If you are using the Datepicker within a Dialog, you cannot use the staticPosition/static_position prop.
Upon adding static={false} to the date picker, you will notice that the date picker detaches from the input field while scrolling. This is a known Flatpickr nuance. By adding the scrollContainer prop, you can tell the date picker which DOM container it should watch for scroll events. In this example, you can see that scrollContainer=".pb--page--content--main" is being passed in order to keep the date picker correctly positioned on page scroll.
Useage: scrollContainer: .validQuerySelectorHere
<%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-positions1", position: "auto right", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the left)", picker_id: "date-picker-positions2", position: "auto left", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens above on the left)", picker_id: "date-picker-positions3", position: "above left", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens below on the right)", picker_id: "date-picker-positions4", position: "below right", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %>
<%= pb_rails("card", props: {id: "position-element", margin_bottom: "md"}) do %> <%= pb_rails("body") do %>👋 Datepicker will position from here based on ID.<% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-position-element", position: "auto right", position_element: "#position-element", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("card", props: {classname: "position-element", margin_bottom: "md"}) do %> <%= pb_rails("body") do %>👋 Datepicker will position from here based on class name.<% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-position-element2", position: "auto right", position_element: ".position-element", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %>
The requiredIndicator/required_indicator prop displays a red asterisk (*) next to the label to visually indicate that the field is required. This is purely visual and does not enforce validation—you can pair it with HTML required, client-side validation, or server-side validation as needed.
The custom_event_type prop allows you to specify a custom event that will trigger the date picker's initialization, in addition to the default initialization on DOMContentLoaded. This is particularly useful in dynamic contexts like Turbo Frame updates where you need to reinitialize the date picker after new content loads. For Turbo integration, use custom_event_type: "turbo:before-render" to ensure the date picker properly reinitializes after Turbo navigation events.
In this example, we demonstrate the setup pattern by connecting a button to dispatch a "datePickerLoader" event - while the date picker's event listener is properly configured through the custom_event_type prop, this demo only logs the event dispatch to the console to illustrate the connection structure.
<%= pb_rails("date_picker", props: { picker_id: "date-picker-turbo-frames", custom_event_type: "datePickerLoader" }) %> <%= pb_rails("button", props: { id: "date-picker-loader", text: "Click Event Simulation" }) %> <script> document.getElementById("date-picker-loader").addEventListener("click", () => { window.document.dispatchEvent( new CustomEvent("datePickerLoader", { bubbles: true, }) ) console.log("Event 'datePickerLoader' dispatched - in a real implementation, this event would trigger the date picker's reinitialization through the custom_event_type prop connection") }) </script>
This kit's options prop requires an array of objects, each of which will be used as the selectable options within the dropdown. Each option object can support any number of key-value pairs, but each MUST contain label, value and id.
The kit also comes with a custom event called "pb:dropdown:selected" which updates dynamically with the selection as it changes. See code snippet to see this in action.
In addition, a data attribute called data-option-selected with the selection is also rendered on the parent dropdown div.
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <% options2 = [ { label: 'India', value: 'India', id: 'in' }, { label: 'Mexico', value: 'Mexico', id: 'mx' }, { label: 'Brazil', value: 'Brazil', id: 'br' }, { label: 'Argentina', value: 'Argentina', id: 'ar' }, { label: 'Colombia', value: 'Colombia', id: 'co' }, { label: 'Chile', value: 'Chile', id: 'cl' }, { label: 'Peru', value: 'Peru', id: 'pe' }, ] %> <%= pb_rails("dropdown", props: {id: "country-dropdown", options: options}) %> <script> document.addEventListener("pb:dropdown:selected", (e) => { if (e.target.id === "country-dropdown") { const option = e.detail; const dropdown = e.target; console.log("Selected option:", option); } }); </script>
The autocomplete prop can be used to add autocomplete or typeahead functionality to the Dropdown's default Trigger. This prop is set to 'false' by default.
<% options = [ { label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "United Kingdom", value: "unitedKingdom", areaCode: "+44", icon: "🇬🇧", id: "gb" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" } ] %> <%= pb_rails("dropdown", props: {options: options, autocomplete: true}) %>
multi_select is a boolean prop that if set to true will allow for multiple options to be selected from the Dropdown.
multi_select is set to false by default.
<% options = [ { label: 'United States', value: 'unitedsStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'india', id: 'in' }, { label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' }, { label: 'Australia', value: 'australia', id: 'au' }, { label: 'New Zealand', value: 'newZealand', id: 'nz' }, { label: 'Germany', value: 'germany', id: 'de' }, { label: 'France', value: 'france', id: 'fr' }, { label: 'Italy', value: 'italy', id: 'it' }, ] %> <%= pb_rails("dropdown", props: { options: options, multi_select: true, }) %>
multiSelect can also be used with the autocomplete functionality.
<% options = [ { label: 'United States', value: 'unitedsStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'india', id: 'in' }, { label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' }, { label: 'Australia', value: 'australia', id: 'au' }, { label: 'New Zealand', value: 'newZealand', id: 'nz' }, { label: 'Germany', value: 'germany', id: 'de' }, { label: 'France', value: 'france', id: 'fr' }, { label: 'Italy', value: 'italy', id: 'it' }, ] %> <%= pb_rails("dropdown", props: { autocomplete: true, options: options, multi_select: true, }) %>
By default, the multi_select prop will render selected options as the default form_pill. form_pill_props however can be used to customize these Pills with props that exist for the form_pill. Currently, only the 'color' and 'size' props are supported as shown here.
<% options = [ { label: 'United States', value: 'unitedsStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'india', id: 'in' }, { label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' }, { label: 'Australia', value: 'australia', id: 'au' }, { label: 'New Zealand', value: 'newZealand', id: 'nz' }, { label: 'Germany', value: 'germany', id: 'de' }, { label: 'France', value: 'france', id: 'fr' }, { label: 'Italy', value: 'italy', id: 'it' }, ] %> <%= pb_rails("dropdown", props: { options: options, multi_select: true, form_pill_props: { size:"small", color:"neutral" }, }) %>
For the subtle variant, it is recommended that you set the Separators prop to false to remove the separator lines between the options for a cleaner look.
The dropdown is built using all of the following required subcomponents:
dropdown/dropdown_trigger is the UI component that users interact with to toggle the dropdown.
dropdown/dropdown_container is the floating container that wraps the list of dropdown options.
dropdown/dropdown_option renders options that are passed to the container.
Each of these subcomponents can be altered using global props and/or their respective props. See doc examples below for more information on each.
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %> <% end %> <% end %> <% end %>
autocomplete prop can also be used in conjunction with the subcomponent structure.
<% options = [ { label: "Jasper Furniss", value: "jasperFurniss", territory: "PHL", title: "Lead UX Engineer", id: "jasper-furniss", status: "Offline" }, { label: "Ramon Ruiz", value: "ramonRuiz", territory: "PHL", title: "Senior UX Designer", id: "ramon-ruiz", status: "Away" }, { label: "Carlos Lima", value: "carlosLima", territory: "PHL", title: "Nitro Developer", id: "carlos-lima", status: "Online" }, { label: "Courtney Long", value: "courtneyLong", territory: "PHL", title: "Lead UX Designer", id: "courtney-long", status: "Online" } ]; %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger", props: {placeholder: "Search...", autocomplete: true}) %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("user", props: {name: option[:label], align:"left", avatar: true, orientation:"horizontal", territory:option[:territory], title: option[:title]}) %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("badge", props: {rounded: true, dark: true, text: option[:status], variant: option[:status] == "Offline" ? "neutral" : option[:status] == "Online" ? "success" : "warning" }) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
The top-level Dropdown component optionally accepts any string through a label prop to produce a label above your trigger element.
Add an id to wire the label to the trigger so that clicking the label will move focus directly to the input, and open the drop-down.
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <%= pb_rails("dropdown", props: { id: "select_a_country", label: "Select a Country", options: options }) %>
dropdown/dropdown_option subcomponent accepts any child components to customize the options' contents and display. By default, options are Body kit text that is set by the label value from the option object.
<% options = [ { label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "Canada", value: "canada", areaCode: "+1", icon: "🇨🇦", id: "ca" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" } ] %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("flex") do %> <%= pb_rails("icon", props: {icon: option[:icon]}) %> <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %> <% end %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
<% options = [ { label: "United States", value: "unitedsStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "Canada", value: "canada", areaCode: "+1", icon: "🇨🇦", id: "ca" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" } ] %> <%= pb_rails("dropdown", props: { options: options, multi_select: true }) do %> <%= pb_rails("dropdown/dropdown_trigger", props:{ multi_select: true }) %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("flex") do %> <%= pb_rails("icon", props: {icon: option[:icon]}) %> <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %> <% end %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
Optionally utilize custom_display on the dropdown/dropdown_trigger subcomponent to customize its content after an option is selected. Pass in any combination of kits to create a custom display. When a user clicks on an option, the kits passed into custom_display will display as the selected option.
Make use of a script to help set the custom_display with the correct value. By using the pb:dropdown:selected event listener, you can target the kits with a querySelector and update them dynamically with the values needed to match the selected option. Make sure to add an ID to the kits being passed in.
The placeholder prop can also be used to customize the placeholder text for the default dropdown/dropdown_trigger.
The dropdown follows the typical rails pattern of utilizing hidden inputs for form submission. The hidden input value is the selected options' id.
<% options = [ { label: "Jasper Furniss", value: "jasperFurniss", territory: "PHL", title: "Lead UX Engineer", id: "jasper-furniss", status: "Offline" }, { label: "Ramon Ruiz", value: "ramonRuiz", territory: "PHL", title: "Senior UX Designer", id: "ramon-ruiz", status: "Away" }, { label: "Carlos Lima", value: "carlosLima", territory: "PHL", title: "Nitro Developer", id: "carlos-lima", status: "Online" }, { label: "Courtney Long", value: "courtneyLong", territory: "PHL", title: "Lead UX Designer", id: "courtney-long", status: "Online" } ]; %> <% custom_display = capture do pb_rails("flex", props: { align: "center" }) do concat(pb_rails("avatar", props: { name: "", size: "xs", id: "dropdown-avatar" })) concat(pb_rails("body", props: { text: "", size: "xs", margin_x: "xs", id: "dropdown-avatar-name" })) concat(pb_rails("badge", props: { text: "", id: "dropdown-avatar-status" })) end end %> <%= pb_rails("dropdown", props: {id: "user-dropdown", options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger", props: {placeholder: "Select a User", custom_display: custom_display}) %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("user", props: {name: option[:label], align:"left", avatar: true, orientation:"horizontal", territory:option[:territory], title: option[:title]}) %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("badge", props: {rounded: true, dark: true, text: option[:status], variant: option[:status] == "Offline" ? "neutral" : option[:status] == "Online" ? "success" : "warning" }) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %> <script> document.addEventListener("pb:dropdown:selected", (e) => { if (e.target.id !== "user-dropdown") return; const option = e.detail; const dropdown = e.target; const display = dropdown.querySelector("[data-dropdown-trigger-custom-display]"); if (!display) return; const nameEl = display.querySelector("#dropdown-avatar-name"); if (nameEl) nameEl.textContent = option.label; const avatarEl = display.querySelector("#dropdown-avatar").querySelector(".avatar_wrapper"); const initials = (option.label[0] + option.label.split(" ").pop()[0]).toUpperCase(); if (avatarEl) { avatarEl.dataset.name = option.label; avatarEl.setAttribute("data-initials", initials); } const badgeEl = display.querySelector("#dropdown-avatar-status"); const variant = option.status === "Online" ? "success" : option.status === "Offline" ? "neutral" : "warning"; if (badgeEl) { badgeEl.querySelector("span").textContent = option.status; badgeEl.className = 'pb_badge_kit_' + variant; } }); </script>
Optionally replace the default trigger's select element by passing child components directly to the dropdown/dropdown_trigger.
<% options = [ { label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "Canada", value: "canada", areaCode: "+1", icon: "🇨🇦", id: "ca" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" } ] %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger") do %> <%= pb_rails("icon_circle", props: {icon:"flag", cursor: "pointer", variant:"royal"}) %> <% end %> <%= pb_rails("dropdown/dropdown_container", props:{max_width:"xs"}) do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("flex") do %> <%= pb_rails("icon", props: {icon: option[:icon]}) %> <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %> <% end %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
The optional searchbar boolean prop can also be used on the dropdown/dropdown_container to render a searchbar with typeahead functionality within the dropdown itself. This is especially useful when a custom trigger is being used.
searchbar is set to false by default.
<% options = [ { label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "United Kingdom", value: "unitedKingdom", areaCode: "+44", icon: "🇬🇧", id: "gb" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" } ] %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger") do %> <%= pb_rails("icon_circle", props: {icon:"flag", cursor: "pointer", variant:"royal"}) %> <% end %> <%= pb_rails("dropdown/dropdown_container", props:{max_width:"xs", searchbar: true}) do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %> <%= pb_rails("flex", props: { align: "center", justify: "between", }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("flex") do %> <%= pb_rails("icon", props: {icon: option[:icon]}) %> <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %> <% end %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
By default, dropdown option paddingX is set to sm and paddingY is set to xs, but this padding can be overridden using our global padding props. In this example we are setting the option padding to sm all around.
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <%= pb_rails("dropdown", props: {options: options}) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: {option: option, padding:"sm"}) %> <% end %> <% end %> <% end %>
Use the dropdown/dropdown_option subcomponent structure to include custom layouts inside dropdown menus. Icons can be placed alongside the Body label text.
<% options = [ { label: "Item 1", value: "item-1", id: "1" }, { label: "Item 2", value: "item-2", id: "2" }, { label: "Item 3", value: "item-3", id: "3" } ] %> <%= pb_rails("dropdown", props: { label: "Multiple Icons", options: options }) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: { option: option }) do %> <%= pb_rails("flex", props: { align: "center", justify: "between" }) do %> <%= pb_rails("flex") do %> <%= pb_rails("icon", props: { icon: "calendar", padding_right: "xs" }) %> <%= pb_rails("body", props: { color: "default", text: option[:label] }) %> <% end %> <%= pb_rails("icon", props: { icon: "check" }) %> <% end %> <% end %> <% end %> <% end %> <% end %> <%= pb_rails("dropdown", props: { label: "Icon on Left", options: options, padding_y: "md" }) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: { option: option }) do %> <%= pb_rails("flex", props: { align: "center" }) do %> <%= pb_rails("icon", props: { icon: "calendar", padding_right: "xs" }) %> <%= pb_rails("body", props: { color: "default", text: option[:label] }) %> <% end %> <% end %> <% end %> <% end %> <% end %> <%= pb_rails("dropdown", props: { label: "Icon on Right", options: options }) do %> <%= pb_rails("dropdown/dropdown_trigger") %> <%= pb_rails("dropdown/dropdown_container") do %> <% options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: { option: option }) do %> <%= pb_rails("flex", props: { align: "center", justify: "between" }) do %> <%= pb_rails("body", props: { color: "default", text: option[:label] }) %> <%= pb_rails("icon", props: { icon: "check" }) %> <% end %> <% end %> <% end %> <% end %> <% end %>
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <%= pb_rails("dropdown", props: { error: raw(pb_rails("icon", props: { icon: "warning" }) + " Please make a valid selection"), options: options }) %>
<% options = [ { label: 'United States', value: 'unitedsStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'india', id: 'in' }, { label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' }, { label: 'Australia', value: 'australia', id: 'au' }, { label: 'New Zealand', value: 'newZealand', id: 'nz' }, { label: 'Germany', value: 'germany', id: 'de' }, { label: 'France', value: 'france', id: 'fr' }, { label: 'Italy', value: 'italy', id: 'it' }, ] %> <% default_value = [ { label: 'United States', value: 'United States', id: 'us' }, { label: 'Canada', value: 'Canada', id: 'ca' }, ] %> <%= pb_rails("dropdown", props: {options: options, multi_select: true, default_value: default_value}) %>
The blank_selection prop adds a blank option at the top of the dropdown options list. This allows users to explicitly clear their selection by choosing the blank option.
The blank selection option appears as the first item in the dropdown and has an empty value (id: "", value: ""). When selected, it effectively clears the dropdown selection.
The placeholder prop allows you to customize the placeholder text that appears when no option is selected in the dropdown.
The placeholder prop works with all dropdown variants (default, subtle, and quickpick). When no option is selected, the placeholder text is displayed. When an option is selected, the placeholder is replaced by the selected option's label. The default placeholder text is "Select..." if no placeholder is provided.
The clearable prop controls whether the clear (X) button appears in the dropdown. When set to false, the clear button is hidden.
This is useful in two scenarios:
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, ] %> <%= pb_rails("dropdown", props: { id: "date-range-quickpick-reset-closeable", label: "Quick Pick", variant: "quickpick", clearable: false }) %> <%= pb_rails("button", props: { margin_y: "md", text: "Reset", html_options: { onclick: "handleReset()" } }) %> <%= pb_rails("dropdown", props: { id: "closeable-default", options: options, clearable: false, default_value: options.last, margin_bottom: "md", label: "Default" }) %> <%= pb_rails("dropdown", props: { id: "closeable-subtle", options: options, clearable: false, default_value: options.second, variant: "subtle", separators: false, label: "Subtle" }) %> <script> function handleReset() { const dropdown = document.querySelector("#date-range-quickpick-reset-closeable[data-pb-dropdown]"); const instance = dropdown?._pbDropdownInstance; if (instance) { instance.clearSelection(); } } </script>
The constrain_height prop limits the dropdown container height to 18em and enables vertical scrolling when the content exceeds this height. This prevents long dropdown lists from rendering off-screen.
When constrain_height is true, the dropdown will:
This is particularly useful for dropdowns with many options, such as long lists or quickpick variants with many date range options.
<% # Create a long list of options to demonstrate height constraint options = (1..30).map do |i| { label: "Option #{i}", value: "option_#{i}", id: "opt_#{i}" } end %> <%= pb_rails("dropdown", props: { id: "dropdown-no-constrain", options: options, label: "Without Constrain Height (Default)", margin_bottom: "md" }) %> <%= pb_rails("dropdown", props: { id: "dropdown-constrain", options: options, constrain_height: true, label: "With Constrain Height" }) %>
The close_on_click prop allows you to control when the Dropdown closes in response to click interactions. The value any reflects the default behavior, where the dropdown will close after any click. Set it to outside to ensure interactive elements as dropdown options are able to be interacted with or modified. Set it to inside for a dropdown that only closes when the input or dropdown menu is clicked.
<% options = [ { label: "United States", value: "unitedStates", id: "us" }, { label: "Canada", value: "canada", id: "ca" }, { label: "Pakistan", value: "pakistan", id: "pk" }, ] %> <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Any" }) %> <%= pb_rails("dropdown", props: { options: options, close_on_click: "any", margin_bottom: "md" }) %> <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Outside" }) %> <%= pb_rails("dropdown", props: { options: options, close_on_click: "outside", margin_bottom: "md" }) %> <%= pb_rails("caption", props: { margin_bottom: "xs", text: "Inside" }) %> <%= pb_rails("dropdown", props: { options: options, close_on_click: "inside" }) %>
The quickpick variant provides predefined date based options when variant:"quickpick" is used.
Open the Dropdown above to see the default options.
The quickpick variant automatically generates hidden inputs for start_date and end_date which are populated when a date range is selected. These inputs are ready for form submission.
You can customize the input names and IDs using the following props:
start_date_name - The name attribute for the start date input (default: "start_date_name")start_date_id - The ID attribute for the start date input (default: "start_date_id")end_date_name - The name attribute for the end date input (default: "end_date_name")end_date_id - The ID attribute for the end date input (default: "end_date_id")Example with custom names:
pb_rails("dropdown", props: {
variant: "quickpick",
start_date_name: "filter[start_date]",
start_date_id: "filter_start_date",
end_date_name: "filter[end_date]",
end_date_id: "filter_end_date"
})
The Dropdown kit also comes with a custom event called "pb:dropdown:selected" which updates dynamically with the selection as it changes. See code snippet to see this in action.
In addition, a data attribute called data-option-selected with the selection is also rendered on the parent dropdown div.
<%= pb_rails("dropdown", props: {id: "date-range-quickpick-1", label: "Date Range", variant: "quickpick"}) %> <script> const dropdown = document.getElementById("date-range-quickpick-1"); if (dropdown) { dropdown.addEventListener("pb:dropdown:selected", (e) => { const option = e.detail; console.log("Selected option:", option); }); } </script>
The optional range_ends_today prop can be used with the quickpick variant to set end date on all ranges that start with 'this' to today's date. For instance, by default 'This Year' will set end day to 12/31/(current year), but if range_ends_today prop is used, end date on that range will be today's date.
<%= pb_rails("dropdown", props: { id: "date-range-quickpick-end-today", label: "Date Range", variant: "quickpick", range_ends_today: true }) %> <script> document.addEventListener("DOMContentLoaded", () => { const dropdown = document.getElementById("date-range-quickpick-end-today"); if (dropdown) { dropdown.addEventListener("pb:dropdown:selected", (e) => { const option = e.detail; console.log("Selected option:", option); }); } }); </script>
To set a default value for the Dropdown, you can use the label of the range you want set as default, for example "This Year", "Today", etc.
<%= pb_rails("dropdown", props: { id: "date-range-with-default", label: "Date Range", variant: "quickpick", default_value: "This Year" }) %> <script> document.addEventListener("DOMContentLoaded", () => { const dropdown = document.getElementById("date-range-with-default"); if (dropdown) { dropdown.addEventListener("pb:dropdown:selected", (e) => { const option = e.detail; console.log("Selected option:", option); }); } }); </script>
The custom_quick_pick_dates prop allows for defining custom quick pick date options for the dropdown. The prop accepts an object with two properties: dates and override.
The dates property accepts an array of objects. Each object has label and value properties. The label is what will be displayed in the dropdown menu. The value property defines the date range that will be selected, and can be:
["06/01/2022", "06/07/2022"])time_period and amount properties for dynamic date calculations:
time_period property accepts "days", "weeks", "months", "quarters", or "years", representing past time periods calculated from today.amount property accepts any number.The override property is a boolean that controls whether custom dates replace or append to the default quick pick options. Default is true (replaces defaults). Set to false to append your custom dates to the default quick pick options.
<%= pb_rails("dropdown", props: { custom_quick_pick_dates: { dates: [ # Allow Playbook to handle the logic... { label: "Last 15 months", value: { time_period: "months", amount: 15 } }, # Or, be explicit with an exact date range for more control... { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"] } ] }, id: "date-range-quickpick-custom", label: "Date Range", margin_bottom: "sm", variant: "quickpick" }) %> <%= pb_rails("dropdown", props: { custom_quick_pick_dates: { override: false, dates: [ { label: "Last 15 months", value: { time_period: "months", amount: 15 } }, { label: "First Week of June 2022", value: ["06/01/2022", "06/07/2022"] } ] }, id: "date-range-quickpick-custom-append-to-defaults", label: "Date Range - Append to Defaults", variant: "quickpick" }) %> <script> const dropdown1 = document.getElementById("date-range-quickpick-custom"); if (dropdown1) { dropdown1.addEventListener("pb:dropdown:selected", (e) => { const option = e.detail; console.log("Selected option:", option); }); } const dropdown2 = document.getElementById("date-range-quickpick-custom-append-to-defaults"); if (dropdown2) { dropdown2.addEventListener("pb:dropdown:selected", (e) => { const option = e.detail; console.log("Selected option:", option); }); } </script>
The quickpick variant can be synced with two DatePickers for a 3-input pattern. When a quickpick option is selected from the dropdown, both DatePickers are automatically populated. When either DatePicker is manually changed, the dropdown is cleared.
controls_start_id - ID of the start DatePicker to sync withcontrols_end_id - ID of the end DatePicker to sync withsync_start_with - ID of the dropdown to clear when start date changessync_end_with - ID of the dropdown to clear when end date changesThis pattern allows users to quickly select common date ranges or manually pick specific dates.
<%= pb_rails("dropdown", props: { id: "dropdown-quickpick-with-date-pickers", label: "Date Range", name: "date_range", margin_bottom: "sm", variant: "quickpick", controls_start_id: "start-date-picker", controls_end_id: "end-date-picker", start_date_id: "quickpick_start_date", start_date_name: "start_date", end_date_id: "quickpick_end_date", end_date_name: "end_date" }) %> <%= pb_rails("date_picker", props: { picker_id: "start-date-picker", label: "Start Date", name: "start_date_picker", placeholder: "Select Start Date", sync_start_with: "dropdown-quickpick-with-date-pickers" }) %> <%= pb_rails("date_picker", props: { picker_id: "end-date-picker", label: "End Date", name: "end_date_picker", placeholder: "Select End Date", sync_end_with: "dropdown-quickpick-with-date-pickers" }) %>
This example demonstrates the 3-input pattern with a default value. The dropdown is initialized with "This Month" selected, and both DatePickers are automatically populated with the corresponding start and end dates.
The default value can be set using the default_value prop with any of the quickpick option labels.
<%= pb_rails("dropdown", props: { id: "dropdown-quickpick-with-date-pickers-default", label: "Date Range", name: "date_range", margin_bottom: "sm", variant: "quickpick", default_value: "This Month", controls_start_id: "start-date-picker-default", controls_end_id: "end-date-picker-default", start_date_id: "quickpick_start_date_default", start_date_name: "start_date", end_date_id: "quickpick_end_date_default", end_date_name: "end_date" }) %> <%= pb_rails("date_picker", props: { picker_id: "start-date-picker-default", label: "Start Date", name: "start_date_picker", placeholder: "Select Start Date", sync_start_with: "dropdown-quickpick-with-date-pickers-default" }) %> <%= pb_rails("date_picker", props: { picker_id: "end-date-picker-default", label: "End Date", name: "end_date_picker", placeholder: "Select End Date", sync_end_with: "dropdown-quickpick-with-date-pickers-default" }) %>
The requiredIndicator/required_indicator prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
You can use requiredIndicator/required_indicator with any validation approach: HTML5 validation via the required prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the required prop.
<% options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' } ] %> <%= pb_rails("dropdown", props: { id: "dropdown_required_indicator", label: "Select a Country", options: options, required_indicator: true }) %>
The custom_event_type prop lets the dropdown clear when specific events are dispatched. Set it to a comma-separated list of event names (e.g. Turbo or app-specific); when any of those events fire with optional detail: { dropdownId }, the matching dropdown clears. You can also listen for selection changes (pb:dropdown:selected), clear by dispatching pb:dropdown:clear, or set the selection by dispatching pb:dropdown:select with detail: { dropdownId, optionId } (or optionIds for multi-select).
The examples show five distinct variants (default, multi select, autocomplete, quick pick, custom display). In each example, the first button clears the dropdown by dispatching a custom event that the dropdown listens for via custom_event_type. The second button sets the selection by dispatching pb:dropdown:select. The third button simulates a form submit by dispatching another custom event in custom_event_type, so the dropdown clears as it would after a real form submission.
Turbo events (e.g. turbo:frame-load, turbo:frame-render, turbo:submit-end) often fire for many actions or across the page, so using them as custom_event_type can clear the dropdown more often than intended. Use them when that scope is what you want; otherwise use app-specific event names and dispatch with detail: { dropdownId } when the action happens, or dispatch pb:dropdown:clear from your own handler.
Dropdowns with subcomponent structures that show custom displays (e.g. custom trigger or custom display) require a pb:dropdown:selected listener to update the visible UI from event.detail when a selection is made; clear is handled by the kit.
<% default_options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'India', id: 'in' }, { label: 'Mexico', value: 'Mexico', id: 'mx' }, ] multi_options = [ { label: 'United States', value: 'unitedStates', id: 'us' }, { label: 'Canada', value: 'canada', id: 'ca' }, { label: 'Pakistan', value: 'pakistan', id: 'pk' }, { label: 'India', value: 'india', id: 'in' }, { label: 'United Kingdom', value: 'unitedKingdom', id: 'uk' }, ] autocomplete_options = [ { label: "United States", value: "unitedStates", areaCode: "+1", icon: "🇺🇸", id: "us" }, { label: "United Kingdom", value: "unitedKingdom", areaCode: "+44", icon: "🇬🇧", id: "gb" }, { label: "Pakistan", value: "pakistan", areaCode: "+92", icon: "🇵🇰", id: "pk" }, ] custom_display_options = [ { label: "Strong Bad", value: "strongBad", id: "strong-bad", status: "Offline" }, { label: "Strong Mad", value: "strongMad", id: "strong-mad", status: "Online" }, { label: "Strong Sad", value: "strongSad", id: "strong-sad", status: "Away" }, ] custom_display_content = capture do pb_rails("flex", props: { align: "center" }) do concat(pb_rails("avatar", props: { name: "", size: "xs", id: "cet-dropdown-avatar" })) concat(pb_rails("body", props: { text: "", size: "xs", margin_x: "xs", id: "cet-dropdown-avatar-name" })) concat(pb_rails("badge", props: { text: "", id: "cet-dropdown-avatar-status" })) end end %> <!-- Example 1: Default dropdown --> <%= pb_rails("dropdown", props: { custom_event_type: "form:submitted,pb:dropdown:clearRequest", id: "dropdown-default-cet", label: "Default dropdown", margin_bottom: "sm", options: default_options, }) %> <%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %> <%= pb_rails("button", props: { id: "clear-default-cet", text: "Clear", variant: "primary" }) %> <%= pb_rails("button", props: { id: "select-default-cet", text: "Select Canada", variant: "secondary" }) %> <%= pb_rails("button", props: { id: "simulate-default-cet", text: "Simulate form submit", variant: "secondary" }) %> <% end %> <script> (function() { var id = "dropdown-default-cet"; document.getElementById("clear-default-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } })); }); document.getElementById("select-default-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "ca" } })); }); document.getElementById("simulate-default-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } })); }); })(); </script> <!-- Example 2: Multi select --> <%= pb_rails("dropdown", props: { custom_event_type: "form:submitted,pb:dropdown:clearRequest", id: "dropdown-multi-cet", label: "Multi select dropdown", margin_bottom: "sm", multi_select: true, options: multi_options, }) %> <%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %> <%= pb_rails("button", props: { id: "clear-multi-cet", text: "Clear", variant: "primary" }) %> <%= pb_rails("button", props: { id: "select-multi-cet", text: "Select US + UK", variant: "secondary" }) %> <%= pb_rails("button", props: { id: "simulate-multi-cet", text: "Simulate form submit", variant: "secondary" }) %> <% end %> <script> (function() { var id = "dropdown-multi-cet"; document.getElementById("clear-multi-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } })); }); document.getElementById("select-multi-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionIds: ["us", "uk"] } })); }); document.getElementById("simulate-multi-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } })); }); })(); </script> <!-- Example 3: Autocomplete --> <%= pb_rails("dropdown", props: { autocomplete: true, custom_event_type: "form:submitted,pb:dropdown:clearRequest", id: "dropdown-autocomplete-cet", label: "Autocomplete dropdown", margin_bottom: "sm", options: autocomplete_options, }) %> <%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %> <%= pb_rails("button", props: { id: "clear-autocomplete-cet", text: "Clear", variant: "primary" }) %> <%= pb_rails("button", props: { id: "select-autocomplete-cet", text: "Select Pakistan", variant: "secondary" }) %> <%= pb_rails("button", props: { id: "simulate-autocomplete-cet", text: "Simulate form submit", variant: "secondary" }) %> <% end %> <script> (function() { var id = "dropdown-autocomplete-cet"; document.getElementById("clear-autocomplete-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } })); }); document.getElementById("select-autocomplete-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "pk" } })); }); document.getElementById("simulate-autocomplete-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } })); }); })(); </script> <!-- Example 4: Quick pick (Date Range) --> <%= pb_rails("dropdown", props: { custom_event_type: "form:submitted,pb:dropdown:clearRequest", id: "dropdown-quickpick-cet", label: "Quickpick dropdown", margin_bottom: "sm", variant: "quickpick", }) %> <%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %> <%= pb_rails("button", props: { id: "clear-quickpick-cet", text: "Clear", variant: "primary" }) %> <%= pb_rails("button", props: { id: "select-quickpick-cet", text: "Select This Week", variant: "secondary" }) %> <%= pb_rails("button", props: { id: "simulate-quickpick-cet", text: "Simulate form submit", variant: "secondary" }) %> <% end %> <script> (function() { var id = "dropdown-quickpick-cet"; document.getElementById("clear-quickpick-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } })); }); document.getElementById("select-quickpick-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "quickpick-this-week" } })); }); document.getElementById("simulate-quickpick-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } })); }); })(); </script> <!-- Example 5: Custom display --> <%= pb_rails("dropdown", props: { custom_event_type: "form:submitted,pb:dropdown:clearRequest", id: "dropdown-custom-display-cet", label: "Custom display (Subcomponent) dropdown", margin_bottom: "sm", options: custom_display_options, }) do %> <%= pb_rails("dropdown/dropdown_trigger", props: { placeholder: "Select a User", custom_display: custom_display_content }) %> <%= pb_rails("dropdown/dropdown_container") do %> <% custom_display_options.each do |option| %> <%= pb_rails("dropdown/dropdown_option", props: { option: option }) do %> <%= pb_rails("flex", props: { align: "center", justify: "between" }) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("user", props: { name: option[:label], align: "left", avatar: true, orientation: "horizontal" }) %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("badge", props: { rounded: true, dark: true, text: option[:status], variant: option[:status] == "Offline" ? "neutral" : option[:status] == "Online" ? "success" : "warning" }) %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %> <%= pb_rails("flex", props: { wrap: true, gap: "sm", align: "center", margin_bottom: "md" }) do %> <%= pb_rails("button", props: { id: "clear-custom-display-cet", text: "Clear", variant: "primary" }) %> <%= pb_rails("button", props: { id: "select-custom-display-cet", text: "Select Strong Sad", variant: "secondary" }) %> <%= pb_rails("button", props: { id: "simulate-custom-display-cet", text: "Simulate form submit", variant: "secondary" }) %> <% end %> <script> (function() { var id = "dropdown-custom-display-cet"; document.getElementById("clear-custom-display-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:clearRequest", { detail: { dropdownId: id } })); }); document.getElementById("select-custom-display-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("pb:dropdown:select", { detail: { dropdownId: id, optionId: "strong-sad" } })); }); document.getElementById("simulate-custom-display-cet").addEventListener("click", function() { document.dispatchEvent(new CustomEvent("form:submitted", { detail: { dropdownId: id } })); }); document.addEventListener("pb:dropdown:selected", function(e) { if (e.target && e.target.id === id && e.detail) { var display = e.target.querySelector("[data-dropdown-trigger-custom-display]"); if (!display) return; var nameEl = display.querySelector("#cet-dropdown-avatar-name"); if (nameEl) nameEl.textContent = e.detail.label; var avatarEl = display.querySelector("#cet-dropdown-avatar"); if (avatarEl) { var wrapper = avatarEl.querySelector(".avatar_wrapper"); if (wrapper) { var initials = (e.detail.label[0] + (e.detail.label.split(" ").pop() || "")[0]).toUpperCase(); wrapper.dataset.name = e.detail.label; wrapper.setAttribute("data-initials", initials); } } var badgeEl = display.querySelector("#cet-dropdown-avatar-status"); if (badgeEl && e.detail.status) { var variant = e.detail.status === "Online" ? "success" : e.detail.status === "Offline" ? "neutral" : "warning"; badgeEl.querySelector("span").textContent = e.detail.status; badgeEl.className = "pb_badge_kit_" + variant; } } }); })(); </script>
The MultiLevelSelect kit renders a multi leveled select dropdown based on data from the user. treeData is a required prop that is expected to contain the data in the form of an array of objects. See code snippet for an example data array.
For the React version of the kit, the onSelect prop returns an array of objects. This array contains all checked items irrespective of whether it is a parent, child or grandchild. Open the console on this example and check and uncheck checkboxes to see this is action!
For the Rails version of the kit, there is no onselect. The form submits as a array of strings, following the typical rails pattern of utilizing hidden inputs. The strings are the values of the selected items' ids. For example, ["103", "106", "107"].
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "business Affairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-default-rails", name: "my_array", tree_data: treeData }) %>
Optionally enable the single variant to replace checkboxes with radios so the input accepts and returns only a single selection.
<% treeData = [ { label: "HQ", value: "hQ", id: "hq", }, { label: "Philadelphia", value: "philadelphia", id: "phl", children: [ { label: "Marketing & Sales PHL", value: "marketingAndSalesPhl", id: "marketingPHL", }, { label: "Installation Office PHL", value: "installationOfficePhl", id: "installationPHL", }, { label: "Warehouse PHL", value: "warehousePhl", id: "warehousePHL", }, ] }, { label: "New Jersey", value: "newJersey", id: "nj", children: [ { label: "New Jersey", value: "newJersey", id: "nj1", children: [ { label: "Marketing & Sales NJ", value: "marketingAndSalesNj", id: "marketingNJ", }, { label: "Installation Office NJ", value: "installationOfficeNj", id: "installationNJ", }, { label: "Warehouse NJ", value: "warehouseNj", id: "warehouseNJ", }, ], }, { label: "Princeton", value: "princeton", id: "princeton", children: [ { label: "Marketing & Sales Princeton", value: "marketingAndSalesPrinceton", id: "marketingPR", }, { label: "Installation Office Princeton", value: "installationOfficePrinceton", id: "installationPR", }, { label: "Warehouse Princeton", value: "warehousePrinceton", id: "warehousePR", }, ] }, ] }, { label: "Maryland", value: "maryland", id: "MD", children: [ { label: "Marketing & Sales MD", value: "marketingAndSalesMd", id: "marketingMD", }, { label: "Installation Office MD", value: "installationOfficeMd", id: "installationMD", }, { label: "Warehouse MD", value: "warehouseMd", id: "warehouseMD", }, ] }, { label: "Connecticut", value: "connecticut", id: "CT", children: [ { label: "Marketing & Sales CT", value: "marketingAndSalesCt", id: "marketingCT", }, { label: "Installation Office CT", value: "installationOfficeCt", id: "installationCT", }, { label: "Warehouse CT", value: "warehouseCt", id: "warehouseCT", }, ] }, ]; %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-single-rails", name: "my_single_select_array", tree_data: treeData, input_name: "Power", variant: "single" }) %>
Dynamically control your selectable options by passing hideRadio: true to any node within your tree data to omit that node's radio selector. In this example we've hidden all radios except ultimate children (nodes without descendants).
<% treeData = [ { label: "HQ", value: "hQ", id: "hq1", }, { label: "Philadelphia", value: "philadelphia", id: "phl1", hideRadio: true, children: [ { label: "Marketing & Sales PHL", value: "marketingAndSalesPhl", id: "marketingPHL1", }, { label: "Installation Office PHL", value: "installationOfficePhl", id: "installationPHL1", }, { label: "Warehouse PHL", value: "warehousePHL", id: "warehousePHL1", }, ] }, { label: "New Jersey", value: "newJersey", id: "nj2", hideRadio: true, children: [ { label: "New Jersey", value: "newJersey", id: "nj3", hideRadio: true, children: [ { label: "Marketing & Sales NJ", value: "marketingAndSalesNj", id: "marketingNJ1", }, { label: "Installation Office NJ", value: "installationOfficeNj", id: "installationNJ1", }, { label: "Warehouse NJ", value: "warehouseNj", id: "warehouseNJ1", }, ], }, { label: "Princeton", value: "princeton", id: "princeton1", hideRadio: true, children: [ { label: "Marketing & Sales Princeton", value: "marketingAndSalesPrinceton", id: "marketingPR1", }, { label: "Installation Office Princeton", value: "installationOfficePrinceton", id: "installationPR1", }, { label: "Warehouse Princeton", value: "warehousePrinceton", id: "warehousePR1", }, ] }, ] }, { label: "Maryland", value: "maryland", id: "MD1", hideRadio: true, children: [ { label: "Marketing & Sales MD", value: "marketingAndSalesMd", id: "marketingMD1", }, { label: "Installation Office MD", value: "installationOfficeMd", id: "installationMD1", }, { label: "Warehouse MD", value: "warehouseMd", id: "warehouseMD1", }, ] }, { label: "Connecticut", value: "connecticut", id: "CT1", hideRadio: true, children: [ { label: "Marketing & Sales CT", value: "marketingAndSalesCt", id: "marketingCT1", }, { label: "Installation Office CT", value: "installationOfficeCt", id: "installationCT1", }, { label: "Warehouse CT", value: "warehouseCt", id: "warehouseCT1", }, ] }, ]; %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-single-children-only-rails", name: "my_single_children_only_select_array", tree_data: treeData, input_name: "PowerChildren", variant: "single" }) %>
The returnAllSelected or return_all_selected prop can be used when users want data on all checked nodes from the dropdown, irrespective of whether it is a parent or child node.
NOTE: This variant also does not automatically uncheck the parent when any of the child nodes are unchecked. returnAllSelected is set to false by default.
NOTE: For larger trees that may return many pill selections, you can optionally set input_display: "none"(for Rails) or inputDisplay = "none"(for React) to hide all pills within the input.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-return-all-selected-rails", name: "my_data_array", tree_data: treeData, return_all_selected: true }) %>
Use the inputDisplay/input_display prop to optionally display only the count in the display as opposed to multiple pills. This prop is set to 'pills' by default.
NOTE: inputDisplay/input_display is particularly useful for larger trees that may return many pill selections, helping to keep the input field clean and compact. This prop should not be used with the Single Select variant.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-input-display-none", name: :foo, tree_data: treeData, input_display: "none", return_all_selected: true, }) %>
selected_ids is an optional prop that accepts an array of ids that, if present, will mark the corresponding nodes on the treeData as checked on page load.
Items that include checked:true on the treeData itself will also be selected on page load, even without being passed to selectedIds.
When an item is marked as checked on page load by any means, the dropdown will expand to show the checked items for easier accessibility.
When using selected_ids and variant: "single" together (see single select doc examples), the selected_ids array should contain only one id, because only one item can be selected and displayed at a time. If the selected_ids array has multiple ids in it, the first id in the array will be the treeData node checked upon page load.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-seelcted_ids", name: "my_data_array_selected", return_all_selected: true, selected_ids:["110","102"], tree_data: treeData, }) %>
<%= pb_form_with(scope: :example, url: "", method: :get) do |form| %> <% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "people", id: "people1", children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "talent1", }, { label: "Business Affairs", value: "businessAffairs", id: "business1", children: [ { label: "Initiatives", value: "initiatives", id: "initiative1", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "development1", }, ], }, { label: "People Experience", value: "peopleExperience", id: "experience1", }, ], }, { label: "Contact Center", value: "contactCenter", id: "contact1", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "appointment1", }, { label: "Customer Service", value: "customerService", id: "customer1", }, { label: "Energy", value: "energy", id: "energy1", }, ], }, ], }] %> <%= form.multi_level_select :example, props: {id: "with-form-multi-level-select", tree_data: treeData, return_all_selected: true, margin_bottom: "sm" } %> <%= form.actions do |action| %> <%= action.button props: { type: "submit", text: "Submit", variant: "primary", margin_top: "lg" } %> <% end %> <% end %>
Change the form pill color by passing the optional pill_color prop. Product, Data, and Status colors are available options. Check them out here in the Form Pill colors example.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-default-rails", name: "my_array", tree_data: treeData, pill_color: "neutral" }) %>
In order to clear the multilevelselect selection using an external trigger (like a reset button), the clearMultiLevelSelect function can be used. See the code snippet below to see this in action. The function is scoped by id so an id MUST be used on the multilevelselect kit and passed to the function as shown for it to work.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-reset-example", name: "my_array", tree_data: treeData, return_all_selected: true }) %> <%= pb_rails("button", props: { text: "Reset", margin_top: "lg", id:"multilevelselect-reset-button" }) %> <script> window.addEventListener('DOMContentLoaded', () => { const exampleResetButton = document.querySelector("#multilevelselect-reset-button"); exampleResetButton.addEventListener("click", () => { clearMultiLevelSelectById('multi-level-select-reset-example'); }); function clearMultiLevelSelectById(id) { const clearFunction = window[`clearMultiLevelSelect_${id}`]; if (clearFunction) { clearFunction(); } else { console.warn(`No clear function found for MultiLevelSelect with id: ${id}`); } } }) </script>
The MultiLevelSelect component optionally accepts a label prop to produce a label above the input.
Add an id to wire the label to the input so that clicking the label will move focus directly to the input, and open the drop-down.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "select_a_department", label: "Select a Department", tree_data: treeData }) %>
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { error: "Please make a valid selection", id: "multi-level-select-error-rails", name: "my_array", tree_data: treeData }) %>
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { disabled: true, id: "multi-level-select-disabled-rails", name: "my_array", tree_data: treeData }) %>
To disable individual items in the treeData, include disabled:true within the object on the treeData that you want disabled. See the code snippet below for an example of how to do this.
If a parent is selected, the parent will be returned in the selected items list, even if it has disabled children.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "people", id: "people1", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "talent1", disabled: true, }, { label: "Business Affairs", value: "businessAffairs", id: "business1", children: [ { label: "Initiatives", value: "initiatives", id: "initiative1", disabled: true, }, { label: "Learning & Development", value: "learningAndDevelopment", id: "development1", }, ], }, { label: "People Experience", value: "peopleExperience", id: "experience1", }, ], }, { label: "Contact Center", value: "contactCenter", id: "contact1", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "appointment1", }, { label: "Customer Service", value: "customerService", id: "customer1", disabled: true, }, { label: "Energy", value: "energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-disabled-options-default-rails", name: "disabled_options_default", tree_data: treeData }) %>
Individual items can also be disabled by including the disabled:true within the object on the treeData for the returnAllSelected/return_all_selected variant. As noted above, this variant will return data on all checked nodes from the dropdown, irrespective of whether it is a parent or child node.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "people", id: "people1", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "talent1", disabled: true, }, { label: "Business Affairs", value: "businessAffairs", id: "business1", children: [ { label: "Initiatives", value: "initiatives", id: "initiative1", disabled: true, }, { label: "Learning & Development", value: "learningAndDevelopment", id: "development1", }, ], }, { label: "People Experience", value: "peopleExperience", id: "experience1", }, ], }, { label: "Contact Center", value: "contactCenter", id: "contact1", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "appointment1", }, { label: "Customer Service", value: "customerService", id: "customer1", disabled: true, }, { label: "Energy", value: "energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-disabled-options-rails", name: "disabled_options", return_all_selected: true, tree_data: treeData }) %>
For the default variant, disabling the parent item will automatically disable it's children as well.
If you want to be able to disable a parent WITHOUT disabling it's children, use the returnAllSelected/return_all_selected variant.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "people", id: "people1", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "talent1", }, { label: "Business Affairs", value: "businessAffairs", id: "business1", expanded: true, disabled: true, children: [ { label: "Initiatives", value: "initiatives", id: "initiative1", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "development1", }, ], }, { label: "People Experience", value: "peopleExperience", id: "experience1", }, ], }, { label: "Contact Center", value: "contactCenter", id: "contact1", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "appointment1", }, { label: "Customer Service", value: "customerService", id: "customer1", }, { label: "Energy", value: "energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "mls-disabled-options-parent-default-rails", name: "disabled_options_parent_default", tree_data: treeData }) %>
For the returnAllSelected/return_all_selected variant, disabling the parent item will NOT automatically disable it's children since this version allows you to select a parent even if all children are unselected.
You can manually pass disabled:true to any and all children of a disabled parent if you want to do so.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "people", id: "people1", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "talent1", }, { label: "Business Affairs", value: "businessAffairs", id: "business1", expanded: true, disabled: true, children: [ { label: "Initiatives", value: "initiatives", id: "initiative1", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "development1", }, ], }, { label: "People Experience", value: "peopleExperience", id: "experience1", }, ], }, { label: "Contact Center", value: "contactCenter", id: "contact1", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "appointment1", }, { label: "Customer Service", value: "customerService", id: "customer1", }, { label: "Energy", value: "energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "mls-disabled-options-parent-rails", name: "disabled_options_parent", return_all_selected: true, tree_data: treeData }) %>
Individual items can be disabled by including disabled: true within the object on the treeData for the single variant. Disabled options are visibly disabled in the dropdown UI and cannot be selected via mouse click or keyboard navigation. When a parent node is disabled, all of its children are automatically disabled as well.
<% treeData = [ { label: "HQ", value: "hQ", id: "hq2", }, { label: "Philadelphia", value: "philadelphia", id: "phl2", disabled: true, children: [ { label: "Marketing & Sales PHL", value: "marketingAndSalesPhl", id: "marketingPHL2", }, { label: "Installation Office PHL", value: "installationOfficePhl", id: "installationPHL2", }, { label: "Warehouse PHL", value: "warehousePhl", id: "warehousePHL2", }, ] }, { label: "New Jersey", value: "newJersey", id: "nj2", children: [ { label: "New Jersey", value: "newJersey", id: "nj12", children: [ { label: "Marketing & Sales NJ", value: "marketingAndSalesNj", id: "marketingNJ2", disabled: true, }, { label: "Installation Office NJ", value: "installationOfficeNj", id: "installationNJ2", }, { label: "Warehouse NJ", value: "warehouseNj", id: "warehouseNJ2", }, ], }, { label: "Princeton", value: "princeton", id: "princeton2", children: [ { label: "Marketing & Sales Princeton", value: "marketingAndSalesPrinceton", id: "marketingPR2", }, { label: "Installation Office Princeton", value: "installationOfficePrinceton", id: "installationPR2", disabled: true, }, { label: "Warehouse Princeton", value: "warehousePrinceton", id: "warehousePR2", }, ] }, ] }, { label: "Maryland", value: "maryland", id: "MD2", children: [ { label: "Marketing & Sales MD", value: "marketingAndSalesMd", id: "marketingMD2", }, { label: "Installation Office MD", value: "installationOfficeMd", id: "installationMD2", }, { label: "Warehouse MD", value: "warehouseMd", id: "warehouseMD2", }, ] }, { label: "Connecticut", value: "connecticut", id: "CT2", children: [ { label: "Marketing & Sales CT", value: "marketingAndSalesCt", id: "marketingCT2", }, { label: "Installation Office CT", value: "installationOfficeCt", id: "installationCT2", }, { label: "Warehouse CT", value: "warehouseCt", id: "warehouseCT2", }, ] }, ] %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-single-disabled-rails", name: "single_disabled", tree_data: treeData, input_name: "Power", variant: "single" }) %>
The requiredIndicator/required_indicator prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
You can use requiredIndicator/required_indicator with any validation approach: HTML5 validation via the required prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the required prop.
<% treeData = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "businessAffairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "select_a_department", label: "Select a Department", required_indicator: true, tree_data: treeData }) %>
Use the placeholder prop to customize the initial text shown in the input when nothing is selected. The default is Start typing....
<% tree_base = [{ label: "Power Home Remodeling", value: "powerHomeRemodeling", id: "100", expanded: true, children: [ { label: "People", value: "people", id: "101", expanded: true, children: [ { label: "Talent Acquisition", value: "talentAcquisition", id: "102", }, { label: "Business Affairs", value: "business Affairs", id: "103", children: [ { label: "Initiatives", value: "initiatives", id: "104", }, { label: "Learning & Development", value: "learningAndDevelopment", id: "105", }, ], }, { label: "People Experience", value: "peopleExperience", id: "106", }, ], }, { label: "Contact Center", value: "contactCenter", id: "107", children: [ { label: "Appointment Management", value: "appointmentManagement", id: "108", }, { label: "Customer Service", value: "customerService", id: "109", }, { label: "Energy", value: "energy", id: "110", }, ], }, ], }] prefix_mls_ids = nil prefix_mls_ids = ->(nodes, pfx) { nodes.map do |n| h = n.dup h[:id] = "#{pfx}#{n[:id]}" h[:children] = prefix_mls_ids.call(n[:children], pfx) if n[:children].present? h end } tree_multi = prefix_mls_ids.call(tree_base, "phm_") tree_return_all = prefix_mls_ids.call(tree_base, "phr_") tree_single = prefix_mls_ids.call(tree_base, "phs_") %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-placeholder-multi-rails", label: "Multi (default)", margin_bottom: "sm", name: "placeholder_multi", tree_data: tree_multi, placeholder: "Search or choose options…", }) %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-placeholder-return-all-rails", label: "Multi (return all selected)", margin_bottom: "sm", name: "placeholder_return_all", placeholder: "Departments...", return_all_selected: true, tree_data: tree_return_all, }) %> <%= pb_rails("multi_level_select", props: { id: "multi-level-select-placeholder-single-rails", label: "Single", name: "placeholder_single", placeholder: "Select one option…", tree_data: tree_single, variant: "single", }) %>
<%= pb_rails("select", props: { label: "Favorite Coffee", name: "coffee", options: [ { value: "1", disabled: true, value_text: "Espresso", }, { value: "2", selected: true, value_text: "Americano", }, { value: "3", disabled: true, value_text: "Cappuccino", }, { value: "4", value_text: "Mocha", }, { value: "5", value_text: "Flat White", }, { value: "6", value_text: "Latte", }, ] }) %>
To create a select with non-selectable subheaders, use a Custom Select component to render a native <select> containing <optgroup> elements. The optgroup HTML element groups related options under a non-selectable label in the dropdown.
<%= pb_rails("select", props: { label: "Favorite Animal" }) do %> <select name="animal" id="animal"> <optgroup label="Mammal"> <option value="1">Cat</option> <option value="2">Dog</option> </optgroup> <optgroup label="Amphibian"> <option value="3">Frog</option> <option value="4">Salamander</option> </optgroup> </select> <% end %>
Select w/ Error shows that an option must be selected or is invalid (i.e. when used in a form it signals a user to fix an error).
<%= pb_rails("select", props: { error: raw(pb_rails("icon", props: { icon: "warning" }) + " Please make a valid selection"), label: "Favorite Food", name: "food", options: [ { value: "1", value_text: "Burgers", }, { value: "2", selected: true, value_text: "Pizza", }, { value: "3", value_text: "Tacos", }, { value: "4", value_text: "BBQ", }, ] }) %>
Inspect the element and notice the data-attribute being added to the <select> element
<%= pb_rails("select", props: { attributes: { data: { options: "data_attribute" }, }, label: "Favorite Food", name: "food", options: [ { value: "1", value_text: "Burgers", }, { value: "2", selected: true, value_text: "Pizza", }, { value: "3", value_text: "Tacos", }, { value: "4", value_text: "BBQ", }, ] }) %>
The multiple prop enables multiple selections; however, for a better user experience, we recommend our Typeahead kit.
<%= pb_rails("select", props: { label: "Favorite Food", name: "food", multiple: true, options: [ { value: "1", value_text: "Burgers", }, { value: "2", selected: true, value_text: "Pizza", }, { value: "3", value_text: "Tacos", }, { value: "4", value_text: "BBQ", }, { value: "4", value_text: "Sushi", }, { value: "4", value_text: "Chinese", }, { value: "4", value_text: "Hot Dogs", }, ] }) %>
Use the input_options / inputOptions prop to pass additional attributes directly to the underlying <select> element instead of the outer wrapper. This is useful for applying data attributes, custom IDs, or other HTML attributes that need to be on the select element itself.
<%= pb_rails("select", props: { label: "Favorite Food", name: "favorite_food", options: [ { value: "pizza", value_text: "Pizza" }, { value: "tacos", value_text: "Tacos" }, { value: "sushi", value_text: "Sushi" } ], input_options: { 'aria-label': "Select your favorite food", class: "custom-select-class", data: { controller: "search", action: "change->search#filter" }, id: "favorite-food-select" } }) %>
The requiredIndicator/required_indicator prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
You can use requiredIndicator/required_indicator with any validation approach: HTML5 validation via the required prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the required prop.
<%= pb_rails("select", props: { label: "Favorite Snacks", name: "food", required_indicator: true, options: [ { value: "1", value_text: "Popcorn", }, { value: "2", selected: true, value_text: "Chips", }, { value: "3", value_text: "Twizzlers", }, { value: "4", value_text: "Cookies", }, ] }) %>
Default Selectable Cards are multi select by default.
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card", props: { input_id: "selected_with_icon", name: "selected_with_icon", value: "selected_with_icon", checked: true, icon: true, }) do %> Selected, with icon <% end %> <%= pb_rails("selectable_card", props: { input_id: "selected_without_icon", name: "selected_without_icon", value: "selected_without_icon", checked: true, }) do %> Selected, without icon <% end %> <%= pb_rails("selectable_card", props: { input_id: "unselected", name: "unselected", value: "unselected", }) do %> Unselected <% end %> <%= pb_rails("selectable_card", props: { input_id: "disabled", name: "disabled", value: "disabled", disabled: true }) do %> Disabled <% end %> </div>
Single Select allows only one selectable card in the set to be selected.
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card", props: { input_id: "male", name: "gender", value: "male", multi: false }) do %> Male <% end %> <%= pb_rails("selectable_card", props: { input_id: "female", name: "gender", value: "female", multi: false }) do %> Female <% end %> <%= pb_rails("selectable_card", props: { input_id: "other", name: "gender", value: "other", multi: false }) do %> Other <% end %> </div>
Selectable Cards can pass text or block content.
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card", props: { input_id: "block", name: "block", value: "block", checked: true }) do %> <%= pb_rails("title", props: { text: "Block", size: 4 }) %> <%= pb_rails("body", props: { tag: "span"}) do %> This uses block <% end %> <% end %> <br><br> <%= pb_rails("selectable_card", props: { input_id: "tag", name: "tag", value: "tag", text: "This passes in text", checked: false }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card", props: { text: "Selected With Options", input_id: "selected_with options", name: "selected_with options", value: "selected_with options", checked: true, input_options: { class: "input-class", name: "overwriting-name", value: "overwriting-value", } }) %> </div>
Selectable Cards can pass images with optional text.
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card", props: { input_id: "selectable-image-default", name: "selectable-image-default", value: "selectable-image-default", checked: true, icon: true }) do %> <%= pb_rails("image", props: { rounded: true, size: "xl",url: "https://unsplash.it/500/400/?image=634" }) %> <%= pb_rails("body", props: { tag: "span"}) do %> Add text here <% end %> <% end %> <%= pb_rails("selectable_card", props: { input_id: "selectable-image-xl", name: "selectable-image-xl", value: "selectable-image-xl", checked: false, icon: true }) do %> <%= pb_rails("image", props: { rounded: true, size: "xl", url: "https://unsplash.it/500/400/?image=634" }) %> <% end %> </div>
Selectable Cards can show an input indicating state.
<%= pb_rails("title", props: { size: 3, text: "What programming languages do you know?" }) %> <br /> <%= pb_rails("selectable_card", props: { checked: true, input_id: "ruby", name: "ruby", value: "ruby", variant: "display_input" }) do %> Ruby <% end %> <%= pb_rails("selectable_card", props: { checked: true, input_id: "javascript", name: "javascript", value: "javascript", variant: "display_input" }) do %> JavaScript <% end %> <%= pb_rails("selectable_card", props: { input_id: "typescript", name: "typescript", value: "typescript", variant: "display_input" }) do %> TypeScript <% end %> <%= pb_rails("selectable_card", props: { input_id: "swift", name: "swift", value: "swift", variant: "display_input" }) do %> Swift <% end %> <br /> <%= pb_rails("title", props: { size: 3, text: "How likely are you to recommend Playbook to a friend?" }) %> <br /> <%= pb_rails("selectable_card", props: { checked: true, input_id: "first_radio", multi: false, name: "radio_group", value: "first_radio", variant: "display_input" }) do %> 5 <% end %> <%= pb_rails("selectable_card", props: { input_id: "second_radio", multi: false, name: "radio_group", value: "second_radio", variant: "display_input" }) do %> 4 <% end %> <%= pb_rails("selectable_card", props: { input_id: "third_radio", multi: false, name: "radio_group", value: "third_radio", variant: "display_input" }) do %> 3 <% end %> <%= pb_rails("selectable_card", props: { input_id: "fourth_radio", multi: false, name: "radio_group", value: "fourth_radio", variant: "display_input" }) do %> 2 <% end %> <%= pb_rails("selectable_card", props: { input_id: "fifth_radio", multi: false, name: "radio_group", value: "fifth_radio", variant: "display_input" }) do %> 1 <% end %>
<%= pb_rails("title", props: { size: 3, text: "What sports do you like?" }) %> <br /> <%= pb_rails("selectable_card", props: { error: true, input_id: "football", name: "football", value: "football", variant: "display_input" }) do %> Football <% end %> <%= pb_rails("selectable_card", props: { error: true, input_id: "basketball", name: "basketball", value: "basketball", variant: "display_input" }) do %> Basketball <% end %> <%= pb_rails("selectable_card", props: { error: true, input_id: "baseball", name: "baseball", value: "baseball", variant: "display_input" }) do %> Baseball <% end %>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card_icon", props: { icon: "chart-line", title_text: "Quarterly Report", body_text: "Export", input_id: 1, checked: true, }) %> <%= pb_rails("selectable_card_icon", props: { icon: "chart-pie", title_text: "Market Share", body_text: "Export", input_id: 2, }) %> <%= pb_rails("selectable_card_icon", props: { icon: "analytics", title_text: "Comprehensive", body_text: "Export", input_id: 3, disabled: true }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card_icon", props: { icon: "hat-cowboy", title_text: "Cowboy", body_text: "Howdy partner.", input_id: 4, checked: true, checkmark: true }) %> <%= pb_rails("selectable_card_icon", props: { icon: "hat-wizard", title_text: "Wizard", body_text: "Poof, you're a sandwich.", input_id: 5, checkmark: true }) %> <%= pb_rails("selectable_card_icon", props: { icon: "hat-chef", title_text: "Chef", body_text: "Where is the lamb sauce?", input_id: 6, disabled: true, checkmark: true }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card_icon", props: { icon: "car", title_text: "Car", input_id: 7, name: "select", multi: false, }) %> <%= pb_rails("selectable_card_icon", props: { icon: "bus", title_text: "Bus", input_id: 8, name: "select", multi: false, }) %> <%= pb_rails("selectable_card_icon", props: { icon: "subway", title_text: "Subway", input_id: 9, name: "select", multi: false, }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_card_icon", props: { icon: "chart-line", title_text: "Quarterly Report", body_text: "Export", input_id: "card-icon-id", checked: true, input_options: { class: "input-class", name: "overwriting-name", value: "overwriting-value", } }) %> </div>
When using custom icons it is important to introduce a "clean" SVG. In order to ensure these custom icons perform as intended within your kit(s), ensure these things have been modified from the original SVG markup:
Attributes must be React compatible e.g. xmlns:xlink should be xmlnsXlink and so on. There should be no hyphenated attributes and no semi-colons!.
Fill colors with regards to g or path nodes, e.g. fill="black", should be replaced with currentColor ala fill="currentColor". Your mileage may vary depending on the complexity of your SVG.
Pay attention to your custom icon's dimensions and viewBox attribute. It is best to use a viewBox="0 0 512 512" starting point when designing instead of trying to retrofit the viewbox afterwards!
You must source your own SVG into component/view you are working on. This can easily be done in programmatic and maintainable ways.
So long as you have a valid React <SVG> node, you can send it as the customIcon prop and the kit will take care of the rest.
Some Rails applications use only webpack(er) which means using image_url will be successful over image_path in most cases especially development where Webpack Dev Server is serving assets over HTTP. Rails applications still using Asset Pipeline may use image_path or image_url. Of course, YMMV depending on any custom configurations in your Rails application.
<div class="pb--doc-demo-row"> <% svg_url = "https://upload.wikimedia.org/wikipedia/commons/3/3b/Wrench_font_awesome.svg" %> <%= pb_rails("selectable_card_icon", props: { custom_icon: svg_url, title_text: "Customization", body_text: "Personalize everything", input_id: 1, checked: true, }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_icon", props: { icon: "dollar-sign", input_id: 1, checked: true, text: "US Dollar" }) %> <%= pb_rails("selectable_icon", props: { icon: "euro-sign", input_id: 2, text: "Euro" }) %> <%= pb_rails("selectable_icon", props: { icon: "yen-sign", input_id: 3, disabled: true, text: "Yen" }) %> </div>
<div class="pb--doc-demo-row"> <%= pb_rails("selectable_icon", props: { icon: "cassette-tape", input_id: 4, multi: false, name: "select", text: "Cassette" }) %> <%= pb_rails("selectable_icon", props: { icon: "compact-disc", input_id: 5, multi: false, name: "select", text: "CD" }) %> <%= pb_rails("selectable_icon", props: { icon: "album-collection", input_id: 6, multi: false, name: "select", text: "Vinyl" }) %> </div>
<%= pb_rails("radio", props: { text: "Power", input_options: { tabindex: 0 }, name: "group 1", value: "Power" }) %> <br> <%= pb_rails("radio", props: { text: "Nitro", name: "group 1", value: "Nitro" }) %> <br> <%= pb_rails("radio", props: { text: "Google", name: "group 1", value: "Google" }) %>
<%= pb_rails("radio", props: { text: "Power" }) do %> <input type="radio" name="group 3" value="power" > <% end %> <br> <%= pb_rails("radio", props: { text: "Nitro" }) do %> <input type="radio" name="group 3" value="nitro" checked> <% end %> <br> <%= pb_rails("radio", props: { text: "Google" }) do %> <input type="radio"name="group 3" value="google"> <% end %>
Error shows that the radio option must be selected or is invalid (i.e. when used in a form it signals a user to fix an error).
<%= pb_rails("flex") do %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Power", input_options: { tabindex: 0 }, margin_right: "sm", name: "group 1", value: "Power" }) %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Nitro", margin_right: "sm", name: "group 1", value: "Nitro" }) %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Google", name: "group 1", value: "Google" }) %> <% end %>
<%= pb_rails("flex", props: {orientation: "column"}) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("radio", props: { text: "Disabled unselected", input_options: { tabindex: 0, disabled: true, }, margin_bottom: "xs", name: "DisabledGroup", value: "Disabled unselected", }) %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("radio", props: { text: "Disabled selected", input_options: { tabindex: 0, disabled: true, }, checked: true, name: "DisabledGroup", value: "Disabled selected" }) %> <% end %> <% end %>
Use the custom_children prop to enable the use of kits instead of text labels.
<% options = [ { label: "Orange", value: "Orange" }, { label: "Red", value: "Red" }, { label: "Green", value: "Green" }, { label: "Blue", value: "Blue" }, ] %> <%= pb_rails("radio", props: { custom_children: true, label: "Select", margin_bottom: "sm", name: "Group1", value: "Select", }) do %> <%= pb_rails("select", props: { min_width: "xs", options: options, }) %> <% end %> <%= pb_rails("radio", props: { custom_children: true, label: "Typeahead", margin_bottom: "sm", name: "Group1", value: "Typeahead", }) do %> <%= pb_rails("typeahead", props: { id: "typeahead-radio", is_multi: false, min_width: "xs", options: options, placeholder: "Select...", }) %> <% end %> <%= pb_rails("radio", props: { custom_children: true, label: "Typography", name: "Group1", value: "Typography", }) do %> <%= pb_rails("title", props: { text: "Custom Typography", }) %> <% end %>
When using a custom checkbox wrapped in the Checkbox kit, hidden inputs are not automatically included and cannot be prop enabled. Manually add a hidden input before the checkbox if necessary to submit a value when the checkbox is unchecked (as is standard in Rails forms).
|
|
|---|
|
Coffee
|
|
Ice Cream
|
|
Chocolate
|
If you want to use indeterminate, "check/uncheck all" checkboxes, add indeterminate_main: true and an id to the main checkbox. Then, add an indeterminate_parent prop with the main checkbox's id to the children checkboxes.
If you want to customize the main checkbox labels, set an array indeterminate_main_labels with "Check All" and "Uncheck All" labels.
<% checkboxes = [ { name: 'Coffee', id: 'coffee', checked: false }, { name: 'Ice Cream', id: 'ice-cream', checked: false }, { name: 'Chocolate', id: 'chocolate', checked: true } ] %> <%= pb_rails("table", props: { container: false, size: "md" }) do %> <thead> <tr> <th> <%= pb_rails("checkbox", props: { value: "checkbox-value", name: "main-checkbox", indeterminate_main: true, indeterminate_main_labels: ["Check All Ice Cream", "Uncheck All Ice Cream"], id: "indeterminate-checkbox" }) %> </th> </tr> </thead> <tbody> <% checkboxes.each do |checkbox| %> <tr> <td> <%= pb_rails("checkbox", props: { checked: checkbox[:checked], text: checkbox[:name], value: checkbox[:id], name: "#{checkbox[:id]}-indeterminate-checkbox", id: "#{checkbox[:id]}-indeterminate-checkbox", indeterminate_parent: "indeterminate-checkbox", }) %> </td> </tr> <% end %> </tbody> <% end %>
<%= pb_rails("flex", props: {orientation: "column"}) do %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("checkbox" , props: { input_options: { tabindex: 0 }, margin_bottom: "xs", text: "Disabled unchecked", value: "checkbox-value", disabled: true, name: "checkbox-name" }) %> <% end %> <%= pb_rails("flex/flex_item") do %> <%= pb_rails("checkbox" , props: { input_options: { tabindex: 0 }, text: "Disabled checked", value: "checkbox-value", disabled: true, checked: true, name: "checkbox-name" }) %> <% end %> <% end %>
The way to access hidden inputs for form submission depends on which version of the kit being used within the form context.
If using the Rails Checkbox version of the kit, set hidden_input: true. Inspect Checkbox #1 in the example above to see the hidden input in the DOM.
If using the Form Builder version of the kit (reference the Form kit page for more on these), the hidden input will appear if the input has a set unchecked_value. Inspect Checkbox #2 in the example above (and the two checkbox examples on the Form kit page) to see the hidden input in the DOM. See the Rails check_box FormHelper docs for more.
<%= pb_form_with(scope: :example, url: "", method: :get) do |form| %> <%=pb_rails("flex", props: { gap: "sm", orientation: "column"}) do %> <%= pb_rails("checkbox" , props: { text: "1. pb_rails(\"checkbox\") Checkbox from kit", value: "checkbox-value", name: "checkbox-name", hidden_input: true }) %> <%= form.check_box :example_checkbox, data: { field1: "value1", field2: "value2" }, props: { text: "2. form.check_box Checkbox from Form Builder" }, unchecked_value: "no", id: "checkbox-id", name: "checkbox-name", class: "checkbox-class" %> <%= form.actions do |action| %> <%= action.button props: { type: "submit", text: "Submit", variant: "primary" } %> <% end %> <% end %> <% end %>
<%= pb_rails("selectable_list", props: { variant: "checkbox", items: [ { text: "Mild", input_options: { value: "1", name: "checkbox-name-1", } }, { text: "Medium", checked: true, input_options: { value: "2", name: "checkbox-name-2", } }, { text: "Hot", input_options: { value: "3", name: "checkbox-name-3", } } ] } ) %>
<%= pb_rails("selectable_list", props: { variant: "radio", items: [ { text: "Small", input_options: { value: "1", name: "radio-name", } }, { text: "Medium", checked: true, input_options: { value: "2", name: "radio-name", } }, { text: "Large", input_options: { value: "3", name: "radio-name", } } ] } ) %>
Use the Time Picker for time-only selection. For date and time selection, use the DatePicker with Time Selection Enabled instead.
Set time_format / timeFormat to 24hour to display the time selection dropdown in a 24-hour format.
The default_time / defaultTime prop sets a default time value and accepts both 12-hour and 24-hour formats.
<%= pb_rails("time_picker", props: { id: "time-picker-default-time-12hr", default_time: "2:30 PM", label: "12-Hour Format (2:30 PM)" }) %> <%= pb_rails("time_picker", props: { id: "time-picker-default-time-24hr", default_time: "14:30", label: "24-Hour Format (14:30)" }) %> <%= pb_rails("time_picker", props: { id: "time-picker-default-time-24hr-format", default_time: "14:30", label: "24-Hour Format with timeFormat (14:30)", time_format: "24hour" }) %>
Enable timezone display by passing show_timezone / showTimezone.
Use the min_time / minTime and max_time / maxTime props to restrict the selectable time range. This example demonstrates minimum-only, maximum-only, and combined ranges in both 12-hour and 24-hour formats.
<%= pb_rails("time_picker", props: { id: "time-picker-min-only", label: "Minimum Time Only", min_time: "09:00", }) %> <%= pb_rails("time_picker", props: { id: "time-picker-max-only", label: "Maximum Time Only", max_time: "17:00", time_format: "24hour", }) %> <%= pb_rails("time_picker", props: { id: "time-picker-min-max-12hr", label: "Min & Max Time Range (12-hour)", min_time: "09:00", max_time: "17:00", }) %> <%= pb_rails("time_picker", props: { id: "time-picker-min-max-24hr", label: "Min & Max Time Range (24-hour)", min_time: "09:00", max_time: "17:00", time_format: "24hour", }) %> <%= pb_rails("time_picker", props: { id: "time-picker-pm-only", label: "PM Only Range (AM disabled)", min_time: "13:00", max_time: "17:00", }) %> <%= pb_rails("time_picker", props: { id: "time-picker-am-only", label: "AM Only Range (PM disabled)", min_time: "06:00", max_time: "11:30" }) %>
The requiredIndicator/required_indicator prop displays a red asterisk (*) next to the label, visually indicating that the field is required. This is purely visual and does not enforce validation.
You can use requiredIndicator/required_indicator with any validation approach: HTML5 validation via the required prop, client-side validation, or backend validation. For this reason, it works independently and doesn't need to be paired with the required prop.
<%= pb_rails("time_picker", props: { id: "time-picker-input-options", label: "Appointment Time", input_options: { aria: { describedby: "appointment-help-text", label: "Select your preferred appointment time" }, data: { testid: "appointment-time-input" }, placeholder: "Choose a time" } }) %>