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.

VariableUsed forDefault
--bundlem8-primary-colorAdd to cart button, selected options, focus outlines#57804c
--bundlem8-primary-foreground-colorText and marks on the primary colour#ffffff
--bundlem8-border-colorOption borders and control outlines#d1d5db
--bundlem8-error-colorError messages#d3451a
--bundlem8-disabled-colorCheckboxes that can't be changed#b8b8b849
--bundlem8-radiusCorner radius of buttons, controls, tiles and images0px

The grid layout's columns are set from the Bundle Selection block, and can also be overridden:

VariableUsed for
--bundlem8-grid-columns-mobile, -tablet, -desktopTiles per row in product groups
--bundlem8-branch-columns-mobile, -tablet, -desktopTiles 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

SelectorElement
bundle-selectionThe whole bundle form
bundle-selection[data-layout="list"], "grid", "rows"The form in each layout
.bundle-selection-wrapperWrapper around the groups and form
.bundle-selection-loadingLoading spinner for very large bundles
.bundle-selection-groupA group. Has the hidden attribute while it's hidden
.bundle-selection-group__fieldThe group's fieldset
.bundle-selection-group__field--optionalOptional products in a group
.bundle-selection-group__field--includesFixed products, under the Includes heading
.bundle-selection-group__field--conditionalA condition group's branches
.bundle-selection-group__labelThe group's title
.bundle-selection-group__hintThe selection hint
.bundle-selection-group__errorThe group's error message. Empty when there's no error
.bundle-selection-group__optionsContainer for the options in grid and row layouts

Options

SelectorElement
.bundle-selection-product__labelA product option
.bundle-selection-product__inputIts radio button or checkbox
.bundle-selection-product__titleIts name
.bundle-selection-product__priceIts price
.bundle-selection-product__skuIts SKU, when Show product SKUs is on
.bundle-selection-product__statusThe [Unavailable] label
.bundle-none-selection__label, __input, __titleThe No selection option
.bundle-branch-selection__label, __input, __titleA condition branch

Grid and row layouts

In the grid and row layouts, every option label also has these classes:

SelectorElement
.bundle-selection-optionThe option tile or row
.bundle-selection-option--has-imageAn option with an image
.bundle-selection-option__mediaThe image container
.bundle-selection-option__imageThe image
.bundle-selection-option__detailsThe option's name, SKU and status

Selected options can be targeted with :has(), for example .bundle-selection-option:has(input:checked).

Form

SelectorElement
.bundle-selection-formThe add to cart form
.bundle-selection-error__msgThe form-level error message
.bundle-selection-quantity__labelThe quantity label
.bundle-selection-quantity__control--decrease, --input, --increaseThe quantity controls
.bundle-selection-price__label, __valueThe price total
.bundle-selection-buttonThe add to cart button's wrapper. Has .is-loading while adding
.bundle-selection-button__labelThe button's text
.bundle-selection-button__loadingThe 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.

Previous
Accessibility