Combo box
Display various form fields in a standardized two-column layout.
Examples
Inline
<div class="combo-box">
<div class="combo-box-row">
<div class="combo-box-label">
<label for="inline-guests" class="form-label">Guests</label>
<div class="form-text">Adults and children</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="inline-guests" class="form-control text-center" value="5" min="1" max="10" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="inline-rooms" class="form-label">Rooms</label>
<div class="form-text">Max 8 rooms</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="inline-rooms" class="form-control text-center" value="1" min="1" max="8" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="inline-animals" class="form-label">Animals</label>
<div class="form-text">We love animals!</div>
</div>
<div class="combo-box-field" style="--bs-combo-box-field-width: auto">
<input type="checkbox" id="inline-animals" class="form-check-input" value="1">
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="inline-pickup" class="form-label">Airport pickup</label>
<div class="form-text">Do you need a ride?</div>
</div>
<div class="combo-box-field">
<select id="inline-pickup" class="form-select">
<option value="no">No, thanks.</option>
<option value="yes">Yes, please!</option>
</select>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="inline-postal" class="form-label">Postal code</label>
<div class="form-text">Your current location</div>
</div>
<div class="combo-box-field">
<input type="text" id="inline-postal" class="form-control">
</div>
</div>
</div>
Dropdown
You can display the combo box component as a fancy dropdown widget. You can use the data-of-combo-box
data attribute to enable the automatic generation of a summary.
<div class="dropdown" data-of-combo-box='{ "target": "#dropdown-guests" }'>
<label for="dropdown-guests" class="form-label">Your reservation</label>
<input type="text" id="dropdown-guests" class="form-select" data-bs-auto-close="outside" data-bs-toggle="dropdown" readonly>
<div class="dropdown-menu">
<div class="combo-box">
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-adults" class="form-label">Adults</label>
<div class="form-text">Ages 13 or above</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="dropdown-adults" name="adults" class="form-control text-center" value="2" min="1" max="10" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-children" class="form-label">Children</label>
<div class="form-text">Ages 13 below</div>
</div>
<div class="combo-box-field" >
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="dropdown-children" class="form-control text-center" value="0" min="0" max="10" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-rooms" class="form-label">Rooms</label>
<div class="form-text">Max 8 rooms</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="dropdown-rooms" name="rooms" class="form-control text-center" value="1" min="1" max="8" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-animals" class="form-label">Animals</label>
<div class="form-text">We love animals!</div>
</div>
<div class="combo-box-field" style="--bs-combo-box-field-width: auto">
<input type="checkbox" id="dropdown-animals" name="animals" class="form-check-input" value="1">
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-pickup" class="form-label">Airport pickup</label>
<div class="form-text">Do you need a ride?</div>
</div>
<div class="combo-box-field">
<select id="dropdown-pickup" class="form-select" name="pickup">
<option value="no">No, thanks.</option>
<option value="yes">Yes, please!</option>
</select>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="dropdown-postal" class="form-label">Postal code</label>
<div class="form-text">Your current location</div>
</div>
<div class="combo-box-field">
<input type="text" name="postal" id="dropdown-postal" class="form-control">
</div>
</div>
</div>
</div>
</div>
Custom formatting
The default formatter has a hardcoded way of generating a summary. You can also use your custom logic to compute a summary in any format you want. In that case, you shouldn’t use the data-of-combo-box
attribute, but you have to initialize the combo box on your own.
<div id="custom-combo-box" class="dropdown">
<label for="custom-guests" class="form-label">Your reservation</label>
<input type="text" id="custom-guests" class="form-select" data-bs-auto-close="outside" data-bs-toggle="dropdown" readonly>
<div class="dropdown-menu">
<div class="combo-box">
<div class="combo-box-row">
<div class="combo-box-label">
<label for="custom-adults" class="form-label">Adults</label>
<div class="form-text">Ages 13 or above</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="custom-adults" name="adults" class="form-control text-center" value="2" min="1" max="10" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="custom-children" class="form-label">Children</label>
<div class="form-text">Ages 13 below</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="custom-children" name="children" class="form-control text-center" value="0" min="0" max="10" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="custom-rooms" class="form-label">Rooms</label>
<div class="form-text">Max 8 rooms</div>
</div>
<div class="combo-box-field">
<div class="input-group" data-of-quantity-picker>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-minus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#dash"/>
</svg>
</button>
<input type="number" id="custom-rooms" name="rooms" class="form-control text-center" value="1" min="1" max="8" readonly>
<button type="button" class="btn btn-icon btn-primary" data-of-quantity-picker-plus><svg class="of-icon" width="32" height="32" fill="currentColor">
<use href="/assets/icons/bootstrap-icons.svg#plus"/>
</svg>
</button>
</div>
</div>
</div>
<div class="combo-box-divider"></div>
<div class="combo-box-row">
<div class="combo-box-label">
<label for="custom-pickup" class="form-label">Airport pickup</label>
<div class="form-text">Do you need a ride?</div>
</div>
<div class="combo-box-field">
<select id="custom-pickup" class="form-select" name="pickup">
<option value="no">No, thanks.</option>
<option value="yes">Yes, please!</option>
</select>
</div>
</div>
</div>
</div>
</div>
Create a new instance of the combo box component with the format
and target
options. The format
expects a function that receives the data
argument containing all input fields as a mapper object in field_name => field_element
format.
openFrontend.ComboBox.then(component => new component(document.getElementById('custom-combo-box'), {
target: document.getElementById('custom-guests'),
format(data) {
const guests = Number.parseInt(data.adults.value) + Number.parseInt(data.children.value)
const rooms = Number.parseInt(data.rooms.value)
const chunks = [
`${guests} Guest${guests > 1 ? 's' : ''}`,
`${rooms} Room${rooms > 1 ? 's' : ''}`,
]
if (data.pickup.value === 'yes') {
chunks.push(data.pickup.labels[0].textContent)
}
return chunks.join(', ')
}
}))
Usage
Options
You can pass extra options as JSON value of the data attribute. Here is the list of all available options (alphabetically):
Option | Type | Default | Explanation |
---|---|---|---|
format |
function |
undefined |
A custom callback to format the summary. This function accepts all input fields data as an argument. See the data structure details below. Note that this option cannot be defined in JSON but has to be passed on when initializing the component in your custom JavaScript code. |
target |
element|string |
undefined |
The target element that will receive a summary as textContent or input’s value . |
Methods
Method | Description |
---|---|
getData |
Get the input fields data. The very same object is passed on to the format callback. See the data structure details below. |
getSummary |
Get the summary of an input field. Accepts the HTML element and value as arguments. Returns a formatted string. You may find this method useful in your custom format callback. |
getValue |
Get the value of an input field for summary. Accepts the HTML element. Returns the parsed input field value. You may find this method useful in your custom format callback. |
const comboBox = await openFrontend.ComboBox.then(component => component.getInstance('#example')) // Returns a Bootstrap combo box instance
// getData example
comboBox.getData()
Data structure
The input fields data passed on to the format
option callback and used in the getData()
method has the following structure:
const data = {
first_name: document.querySelector('input[name="first_name"]'),
last_name: document.querySelector('input[name="last_name"]'),
}
data.first_name.value // access the value of input
Events
Event | Description |
---|---|
initialized.of.combo_box |
This event is fired immediately when the combo box JavaScript features are ready. |
changed.of.combo_box |
This event is fired immediately when any of the combo box input fields are changed. |
const element = document.getElementById('combo-box')
element.addEventListener('changed.of.combo_box', async () => {
const comboBox = await openFrontend.ComboBox.then(component => component.getInstance(element))
comboBox.getData()
})
CSS
--#{$prefix}combo-box-padding-x: #{$combo-box-padding-x};
--#{$prefix}combo-box-padding-y: #{$combo-box-padding-y};
--#{$prefix}combo-box-color: #{$combo-box-color};
--#{$prefix}combo-box-bg: #{$combo-box-bg};
--#{$prefix}combo-box-border-color: #{$combo-box-border-color};
--#{$prefix}combo-box-border-radius: #{$combo-box-border-radius};
--#{$prefix}combo-box-border-width: #{$combo-box-border-width};
--#{$prefix}combo-box-row-gap: #{$combo-box-row-gap};
--#{$prefix}combo-box-label-min-width: #{$combo-box-label-min-width};
--#{$prefix}combo-box-field-width: #{$combo-box-field-width};
--#{$prefix}combo-box-divider-bg: #{$combo-box-divider-bg};
--#{$prefix}combo-box-divider-margin-y: #{$combo-box-divider-margin-y};
--#{$prefix}combo-box-divider-height: #{$combo-box-divider-height};
--#{$prefix}combo-box-divider-style: #{$combo-box-divider-style};
Sass variables
$combo-box-padding-x: 1rem;
$combo-box-padding-y: .5rem;
$combo-box-color: var(--#{$prefix}body-color);
$combo-box-bg: var(--#{$prefix}body-bg);
$combo-box-border-color: var(--#{$prefix}border-color-translucent);
$combo-box-border-radius: var(--#{$prefix}border-radius);
$combo-box-border-width: var(--#{$prefix}border-width);
$combo-box-row-gap: 1.5rem;
$combo-box-label-min-width: 7em;
$combo-box-field-width: 9em;
$combo-box-divider-bg: $combo-box-border-color;
$combo-box-divider-margin-y: $spacer * .5;
$combo-box-divider-height: 1px;
$combo-box-divider-style: solid;