Back to Widget Patterns
v2.5Interactive Tooling

Product Feature Configurator

Tesla-style product builder. Option groups with select, radio, and checkbox types. A running price total computed from the selections. Composes PrimaryButtonCTA for the final action.

Live preview

Rendered with real component code. Each example demonstrates a documented variant.

Laptop configurator

Build your laptop

Choose your options and see the total update as you go.

RAM (required)
Storage (required)
Color (required)

Your configuration

RAM8 GB
Storage256 GB SSD
ColorSilver
Total$1,499

Side-by-side layout

Configure your monitor

Pick the specs that match your workspace.

Screen size (required)
Resolution (required)
Panel type (required)

Your configuration

Screen size24"
Resolution1080p (Full HD)
Panel typeIPS
Total$699

React props

Configure the component via props in React or via attributes in HTML.

PropTypeDefaultDescription
basePricenumberrequiredStarting price before any option deltas are applied
groupsOptionGroup[]requiredOrdered list of option groups; each group is a select, radio, or checkbox fieldset
titlestringundefinedOptional heading rendered above the option groups
descriptionstringundefinedOptional supporting copy shown below the title
currencystring"USD"ISO 4217 currency code passed to Intl.NumberFormat for price display
layout"stacked" | "side-by-side""stacked"Stacked renders groups above the summary; side-by-side places them in a two-column grid
showPreviewbooleanfalseWhen true, renders preview images from options that carry imageSrc
finalCta{ label: string; href: string }undefinedRenders a PrimaryButtonCTA below the price summary when provided
onConfigChange(selections: Record<string, string | string[]>, totalPrice: number) => voidundefinedCalled on every selection change with the full selections map and computed total
classNamestringundefinedExtra class applied to the root section element

HTML usage

The HTML variant uses the same shared styles and class names. Drop into any stack.

<link rel="stylesheet" href="path/to/product-feature-configurator/styles.css">

<section class="pfc pfc--stacked">
  <header class="pfc__header">
    <h2 class="pfc__title">Build your laptop</h2>
    <p class="pfc__description">Choose your options and see the total update as you go.</p>
  </header>

  <div class="pfc__body">
    <!-- Groups column -->
    <div class="pfc__groups">

      <!-- Radio group -->
      <fieldset class="pfc__group">
        <legend class="pfc__group-label">RAM</legend>
        <div class="pfc__options" role="group">
          <label class="pfc__option" for="ram-8gb">
            <input id="ram-8gb" type="radio" name="ram" value="8gb" checked>
            <span class="pfc__option-label">
              <span class="pfc__option-name">8 GB</span>
              <span class="pfc__option-desc">Great for everyday tasks</span>
            </span>
          </label>
          <label class="pfc__option" for="ram-16gb">
            <input id="ram-16gb" type="radio" name="ram" value="16gb">
            <span class="pfc__option-label">
              <span class="pfc__option-name">16 GB</span>
              <span class="pfc__option-desc">Recommended for developers</span>
            </span>
            <span class="pfc__option-price">+$200</span>
          </label>
        </div>
      </fieldset>

      <!-- Select group -->
      <fieldset class="pfc__group">
        <legend class="pfc__group-label">Storage</legend>
        <div class="pfc__options">
          <select class="pfc__select" name="storage">
            <option value="256gb">256 GB SSD</option>
            <option value="512gb">512 GB SSD (+$100)</option>
            <option value="1tb">1 TB SSD (+$250)</option>
          </select>
        </div>
      </fieldset>
    </div>

    <!-- Right column: summary + actions -->
    <div class="pfc__right">
      <div class="pfc__summary" aria-live="polite">
        <p class="pfc__summary-heading">Your configuration</p>
        <div class="pfc__summary-line">
          <span class="pfc__summary-key">RAM</span>
          <span class="pfc__summary-val">8 GB</span>
        </div>
        <div class="pfc__summary-line">
          <span class="pfc__summary-key">Storage</span>
          <span class="pfc__summary-val">256 GB SSD</span>
        </div>
        <div class="pfc__total">
          <span class="pfc__total-label">Total</span>
          <span class="pfc__total-price">$1,499</span>
        </div>
      </div>
      <div class="pfc__actions">
        <a class="pbc pbc--solid pbc--rounded" href="/checkout">Add to cart</a>
      </div>
    </div>
  </div>
</section>

<script>
/* Update summary and total when any input changes.
   Read current values, look up price deltas, and write the new total. */
(function () {
  var section = document.querySelector('.pfc');
  var totalEl = section.querySelector('.pfc__total-price');
  var BASE_PRICE = 1499;
  var DELTAS = { ram: { '8gb': 0, '16gb': 200, '32gb': 500 },
                 storage: { '256gb': 0, '512gb': 100, '1tb': 250 } };

  section.addEventListener('change', function () {
    var total = BASE_PRICE;
    Object.keys(DELTAS).forEach(function (name) {
      var el = section.querySelector('[name="' + name + '"]:checked') ||
               section.querySelector('[name="' + name + '"]');
      if (el) total += DELTAS[name][el.value] || 0;
    });
    totalEl.textContent = '$' + total.toLocaleString();
  });
}());
</script>

Customization

Override these CSS custom properties to integrate the component into your brand.

.pfc {
  --pfc-bg: var(--brand-surface, #ffffff);
  --pfc-text-color: var(--brand-ink, #102542);
  --pfc-muted-color: rgba(16, 37, 66, 0.55);
  --pfc-accent: var(--brand-accent, #1e5fcf);
  --pfc-group-bg: rgba(0, 0, 0, 0.025);
  --pfc-option-border: rgba(0, 0, 0, 0.12);
  --pfc-option-bg-selected: rgba(30, 95, 207, 0.07);
  --pfc-preview-bg: rgba(0, 0, 0, 0.03);
  --pfc-summary-bg: rgba(0, 0, 0, 0.03);
  --pfc-price-color: var(--brand-accent, #1e5fcf);
  --pfc-radius: 0.875rem;
  --pfc-padding: 2rem;
  --pfc-gap: 1.25rem;
}