Developers
Styling with CSS
The colour settings in App settings cover most stores. When you need more control, you can style the bundle form with CSS from your theme. Every part of the form has a stable class name to target.
Where to add CSS
- Theme editor: open Theme settings → Custom CSS to add CSS for the whole store, or a section's Custom CSS setting to style just that section.
- Theme code: add your CSS to your theme's stylesheet if you're comfortable editing theme files.
BundleM8's own styles use low-specificity selectors so your CSS can override them without !important.
Two ways to style the form
Adjust the default styling
Keep Use theme on in the BundleM8 Scripts app embed and add CSS to change specific things. This is the quickest option for small changes like spacing, font weights or button shapes.
Start from scratch
Turn Use theme off to remove BundleM8's visual styling. The form keeps only the minimum CSS needed to work, such as hiding groups that shouldn't be shown and arranging grid columns. Your theme's own styles for labels, buttons and inputs then apply, and you add the rest.
Keep the form accessible
When styling from scratch, make sure selected options, keyboard focus and error messages are clearly visible. See Accessibility.
To get going quickly, copy the starter stylesheet at the end of this page.
CSS variables
The form's colours and corner radius are CSS custom properties on the bundle-selection element. Settings from Settings → Appearance set these for you, and you can override them in your CSS.
| Variable | Used for | Default |
|---|---|---|
--bundlem8-primary-color | Add to cart button, selected options, focus outlines | #57804c |
--bundlem8-primary-foreground-color | Text and marks on the primary colour | #ffffff |
--bundlem8-border-color | Option borders and control outlines | #d1d5db |
--bundlem8-error-color | Error messages | #d3451a |
--bundlem8-disabled-color | Checkboxes that can't be changed | #b8b8b849 |
--bundlem8-radius | Corner radius of buttons, controls, tiles and images | 0px |
The grid layout's columns are set from the Bundle Selection block, and can also be overridden:
| Variable | Used for |
|---|---|
--bundlem8-grid-columns-mobile, -tablet, -desktop | Tiles per row in product groups |
--bundlem8-branch-columns-mobile, -tablet, -desktop | Tiles per row for condition group branches |
For example, to use your theme's accent colour and rounded corners on one product template:
bundle-selection {
--bundlem8-primary-color: var(--color-button);
--bundlem8-radius: 8px;
}
Structure and class names
The form is made of custom elements, each wrapping elements with BEM-style class names. This outline shows the structure for a group of products with custom form handling on:
<bundle-selection data-layout="list">
<div class="bundle-selection-wrapper">
<bundle-selection-group>
<div class="bundle-selection-group">
<fieldset class="bundle-selection-group__field bundle-selection-group__field--optional">
<legend class="bundle-selection-group__label">Choose 3 packs</legend>
<p class="bundle-selection-group__hint">Choose 3.</p>
<div class="bundle-selection-group__error" role="alert"></div>
<bundle-selection-product>
<div class="bundle-selection-product">
<label class="bundle-selection-product__label">
<input class="bundle-selection-product__input" type="checkbox">
<span class="bundle-selection-product__title">M8 Blend</span>
<span class="bundle-selection-product__price">$24.00</span>
<span class="bundle-selection-product__sku">SKU: CM8-M8-250</span>
<span class="bundle-selection-product__status">[Unavailable]</span>
</label>
</div>
</bundle-selection-product>
</fieldset>
</div>
</bundle-selection-group>
<form class="bundle-selection-form">
<bundle-selection-error><div class="bundle-selection-error"></div></bundle-selection-error>
<bundle-selection-quantity>…</bundle-selection-quantity>
<bundle-selection-price>…</bundle-selection-price>
<bundle-selection-button>…</bundle-selection-button>
</form>
</div>
</bundle-selection>
Bundle and groups
| Selector | Element |
|---|---|
bundle-selection | The whole bundle form |
bundle-selection[data-layout="list"], "grid", "rows" | The form in each layout |
.bundle-selection-wrapper | Wrapper around the groups and form |
.bundle-selection-loading | Loading spinner for very large bundles |
.bundle-selection-group | A group. Has the hidden attribute while it's hidden |
.bundle-selection-group__field | The group's fieldset |
.bundle-selection-group__field--optional | Optional products in a group |
.bundle-selection-group__field--includes | Fixed products, under the Includes heading |
.bundle-selection-group__field--conditional | A condition group's branches |
.bundle-selection-group__label | The group's title |
.bundle-selection-group__hint | The selection hint |
.bundle-selection-group__error | The group's error message. Empty when there's no error |
.bundle-selection-group__options | Container for the options in grid and row layouts |
Options
| Selector | Element |
|---|---|
.bundle-selection-product__label | A product option |
.bundle-selection-product__input | Its radio button or checkbox |
.bundle-selection-product__title | Its name |
.bundle-selection-product__price | Its price |
.bundle-selection-product__sku | Its SKU, when Show product SKUs is on |
.bundle-selection-product__status | The [Unavailable] label |
.bundle-none-selection__label, __input, __title | The No selection option |
.bundle-branch-selection__label, __input, __title | A condition branch |
Grid and row layouts
In the grid and row layouts, every option label also has these classes:
| Selector | Element |
|---|---|
.bundle-selection-option | The option tile or row |
.bundle-selection-option--has-image | An option with an image |
.bundle-selection-option__media | The image container |
.bundle-selection-option__image | The image |
.bundle-selection-option__details | The option's name, SKU and status |
Selected options can be targeted with :has(), for example .bundle-selection-option:has(input:checked).
Form
| Selector | Element |
|---|---|
.bundle-selection-form | The add to cart form |
.bundle-selection-error__msg | The form-level error message |
.bundle-selection-quantity__label | The quantity label |
.bundle-selection-quantity__control--decrease, --input, --increase | The quantity controls |
.bundle-selection-price__label, __value | The price total |
.bundle-selection-button | The add to cart button's wrapper. Has .is-loading while adding |
.bundle-selection-button__label | The button's text |
.bundle-selection-button__loading | The button's spinner |
Helpers
.bundlem8-visually-hidden hides text visually while keeping it available to screen readers. It's used for hints when Show selection hints is off, and for the "(Included)" label.
Examples
Full-width add to cart button
.bundle-selection-button button {
width: 100%;
}
Larger group titles
.bundle-selection-group__label {
font-size: 1.125rem;
line-height: 1.3;
}
Hide prices on fixed products
.bundle-selection-group__field--includes .bundle-selection-product__price {
display: none;
}
A softer selected tile in the grid layout
bundle-selection[data-layout="grid"] .bundle-selection-option:has(input:checked) {
background-color: color-mix(in srgb, var(--bundlem8-primary-color) 8%, transparent);
}
Starter stylesheet
This stylesheet is a starting point for styling the form from scratch. Copy it into your theme, then adjust it to match your store. It:
- Styles all three layouts: list, grid and rows.
- Uses your browser's standard radio buttons and checkboxes, tinted with your primary colour, so selected and focus states stay clear without extra work.
- Reads the colours and corner roundness from Settings → Appearance, with fallbacks, so the colour settings still work.
- Uses only the class names and variables documented on this page.
bundle-selection {
--bundle-primary: var(--bundlem8-primary-color, #57804c);
--bundle-on-primary: var(--bundlem8-primary-foreground-color, #ffffff);
--bundle-border: var(--bundlem8-border-color, #d1d5db);
--bundle-error: var(--bundlem8-error-color, #d3451a);
--bundle-radius: var(--bundlem8-radius, 4px);
}
/* Spacing between groups and the add to cart form */
.bundle-selection-wrapper > * + * {
margin-top: 1.5rem;
}
bundle-selection-group:has(> .bundle-selection-group[hidden]) {
display: none;
}
/* Groups */
.bundle-selection-group__field {
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
.bundle-selection-group__field + .bundle-selection-group__field {
margin-top: 1rem;
}
.bundle-selection-group__field > * + * {
margin-top: 0.75rem;
}
.bundle-selection-group__label {
padding: 0;
font-weight: 600;
}
.bundle-selection-group__hint {
margin-bottom: 0;
font-size: 0.875rem;
}
.bundle-selection-group__error,
.bundle-selection-error__msg {
color: var(--bundle-error);
font-weight: 500;
}
.bundle-selection-group__error:empty {
margin-top: 0;
}
/* Options */
.bundle-selection-product__label,
.bundle-none-selection__label,
.bundle-branch-selection__label {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem 0.625rem;
cursor: pointer;
}
.bundle-selection-product__label > .bundle-selection-product__title {
flex: 1;
}
.bundle-selection-product__label > .bundle-selection-product__sku,
.bundle-selection-product__label > .bundle-selection-product__status {
flex-basis: 100%;
padding-left: 1.75rem;
font-size: 0.8125rem;
}
bundle-selection input[type="radio"],
bundle-selection input[type="checkbox"] {
flex-shrink: 0;
width: 1.125rem;
height: 1.125rem;
margin: 0;
accent-color: var(--bundle-primary);
}
bundle-selection label:has(input:disabled) {
cursor: default;
}
/* Grid and row layouts */
.bundle-selection-option {
gap: 0.625rem;
padding: 0.75rem;
border: 1px solid var(--bundle-border);
border-radius: var(--bundle-radius);
}
.bundle-selection-option:has(input:checked) {
border-color: var(--bundle-primary);
box-shadow: inset 0 0 0 1px var(--bundle-primary);
}
.bundle-selection-option:has(input:disabled:not(:checked)) {
opacity: 0.6;
}
.bundle-selection-option__media {
border-radius: var(--bundle-radius);
}
.bundle-selection-option__details {
gap: 0.25rem;
}
bundle-selection[data-layout="grid"] .bundle-selection-option input {
position: absolute;
top: 1.25rem;
right: 1.25rem;
}
bundle-selection[data-layout="grid"] .bundle-selection-option:not(.bundle-selection-option--has-image) input {
top: 0.75rem;
right: 0.75rem;
}
bundle-selection[data-layout="grid"] .bundle-selection-option:not(.bundle-selection-option--has-image) .bundle-selection-option__details {
padding-right: 1.75rem;
}
/* Quantity, price and add to cart */
bundle-selection-quantity,
bundle-selection-price,
bundle-selection-button {
display: block;
margin-top: 1rem;
}
.bundle-selection-quantity__label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.bundle-selection-quantity__control {
display: inline-flex;
border: 1px solid var(--bundle-border);
border-radius: var(--bundle-radius);
}
.bundle-selection-quantity__control button {
padding: 0.5rem 0.875rem;
border: 0;
background: none;
color: inherit;
font: inherit;
cursor: pointer;
}
.bundle-selection-quantity__control--input {
width: 3rem;
border: 0;
background: none;
color: inherit;
font: inherit;
text-align: center;
appearance: textfield;
}
.bundle-selection-quantity__control--input::-webkit-inner-spin-button {
appearance: none;
}
.bundle-selection-price {
display: flex;
gap: 0.5rem;
font-weight: 600;
}
.bundle-selection-button button {
position: relative;
justify-content: center;
width: 100%;
padding: 0.875rem 1.5rem;
border: 0;
border-radius: var(--bundle-radius);
background: var(--bundle-primary);
color: var(--bundle-on-primary);
font: inherit;
font-weight: 600;
cursor: pointer;
}
.bundle-selection-button button:hover {
filter: brightness(0.92);
}
.bundle-selection-button button:focus-visible {
outline: 2px solid var(--bundle-primary);
outline-offset: 2px;
}
.bundle-selection-button.is-loading .bundle-selection-button__label {
opacity: 0;
}
.bundle-selection-button__loading {
position: absolute;
inset: 0;
width: auto;
height: auto;
display: flex;
align-items: center;
justify-content: center;
}
Your theme's own fonts, text colours and base styles for buttons and inputs still apply alongside this stylesheet, so check the result on your product page and adjust as needed.
