Skip to content

How to build a product configurator with Gravity Forms and Image Choices

Here at JetSloth, we’re always trying to push the boundaries of design and code. With previous demos, we’ve tried to show you all how great Gravity Forms is with our Image Choices add-on. We wanted to design and prepare another great example of these two plugins, but this time push it a little further.

Let’s look at how far you can take JetSloth’s Image Choices addon for Gravity Forms, with a little design skill, and some heavy custom CSS, the results speak for themselves. Let’s make forms great again. In this tutorial showcase we show you how to build a product configurator with Gravity Forms and Image Choices.

How to build a product configurator with Gravity Forms and Image Choices

The design

He’res the design mockup we put together before jumping into custom CSS and jQuery. This helps us understand what needs to be put together, and how each element could be generated within the constraints of just using Gravity Forms and Image Choices add-on. We wanted to be able to recreate this design only using standard Gravity Forms with Image Choices, no theme updates or server logins required. Just WordPress logins, Gravity Forms and Image Chocies.

How to build a product configurator with Gravity Forms and Image Choices
The form setup

So how did we do it?

SO this tutorial is already getting long, watch the below video where we’ll show you step by step on how to build your own product configurator using Gravity Forms and Image Choices. Make sure to grab the demo CSS and JS below the video.

Class names

Make sure you add the below class names to the appropriate form fields. Listed below are the only two you’ll need and where to put them.

Class nameDescription
preview-areaAdd to the preview HTML field
config-layerAdd to the image choices radio field

Example CSS

Copy the below CSS, and paste it into your Image Choices settings > custom CSS screen.

/*Preview Area*/
.preview-area {
    width: 100%;
    height: 580px;
    display: block;
    margin-bottom: 0!important;
    bottom: 0;
/*Image Choices styles overides*/
.ic-theme--circle {
    --ic-width: 100px;
    --ic-selected-icon-size: 15px;
/*Position the config layer options*/
    position: absolute;
    left: 50px;
    bottom: 50px;
    position: absolute;
    right: 50px;
    bottom: 50px;
/*Iphone screen positions*/
.preview-layer-2 img {
    position: absolute;
    width: auto;
    height: 356px;
    top: 107px;
    left: 522px;
    border-radius: 21px;
    overflow: hidden;
    transform: rotateY(358deg) rotateZ(21deg);
/*Mobile */
@media (max-width: 640px){
    .preview-area {
        height: auto;
    /*Position the config layer options*/
        position: relative;
        left: unset;
        right: unset;
        bottom: unset;
        display: flex!important;
        justify-content: center;
        justify-items: center;
    .preview-layer-2 img {
        height: 100px;
        top: 29px;
        left: 145px;

Example JS

Create a new HTML field, add it to the bottom of your form within the Gravity Forms form editor, and paste in the below jQuery script.

<script type="text/javascript">
 $(document).bind('gform_post_render', function(e, formId, currentFormPage) {

  var formSelector = '#gform_' + formId;
  var $form = $(formSelector);

  if ( $'configurator-init') !== true ) {
   // Add the preview layers
   $(formSelector + ' .gform_body .gfield.image-choices-field.config-layer').each(function(i, el){

    var $field = $(this);
    if ( !$field.find('.ginput_container_radio').length && !$field.find('.ginput_container_checkbox').length  ) {
     return true;

    var layerNum = i+1;
    var layerRef = 'preview-layer-' + layerNum;

    var fieldID = $field.attr('id');

    var $previewArea = $field.closest('form').find('.gform_body div.preview-area');
    $previewArea.append('<div class="preview-layer '+layerRef+' '+fieldID+'" style="z-index:'+layerNum+';"></div>');

    $'layerRef', layerRef);


   // update preview on choice click
   $(document).on('change', formSelector + ' .config-layer.image-choices-field .ginput_container input', function (e) {

    var $input = $(this);
    var inputType = $input.attr('type');
    var validInputType = ( inputType === "checkbox" || inputType === "radio" );

    var inputID = $input.attr('id');
    var $thisForm = $input.closest('form');
    var $label = $'label');
    var $choice = $label.closest('.image-choices-choice');
    var $field = $choice.closest('.gfield');
    var $previewArea = $thisForm.find('.gform_body div.preview-area');

    var layerRef = $'layerRef');
    var $layer = $previewArea.find('.'+layerRef);

    if ( validInputType && !$':checked') && $layer.find('.preview-item.' + inputID).length ) {
     $layer.find('.preview-item.' + inputID).remove();
    else if ( validInputType && !$layer.find('.preview-item.' + inputID).length ) {
     // if this choice is a config-reset, it clears all other layers when changed
     if ( $field.hasClass('config-reset') ) {
      $previewArea.find('.preview-item:not(.' + inputID + ')').remove();
     // put selected image into the preview
     var $img = $choice.find('span.image-choices-choice-image-wrap img:first').clone();
     $img.attr('class', 'preview-item ' + inputID);
     // handle src if lazy loading is on
     if ( $'src') ) {
      $img.attr('src', $'src') )
     else if ( $'lazy-src') ) {
      $img.attr('src', $'lazy-src') )

     if ( inputType === 'radio' ) {


   $'configurator-init', true);


   // start by triggering click on everything selected by default, getting them into the preview
   $('.gfield.image-choices-field.config-layer:not([style*="display: none"]):not([style*="display:none"]) .ginput_container_radio input:checked, .gfield.image-choices-field.config-layer:not([style*="display: none"]):not([style*="display:none"]) .ginput_container_checkbox input:checked').each(function(i, el){
  }, 100);


 if ( window.gform && window.gform.addAction ) {
  // trigger click on any choices with default values, after conditional logic changes
  window.gform.addAction('gform_post_conditional_logic_field_action', function (formId, action, targetId, defaultValues, isInit) {
   if ( !isInit ) {
    var $input = $(targetId);
    var $field = $input.closest('.gfield');
    if ( defaultValues && $':visible') && $field.hasClass('config-layer') ) {
     $.each(defaultValues, function(i, inputID){


Advanced Demo

We’ve rebuilt an older demo from years ago to now be more suited and compatible with the latest versions fo Gravity Forms and Image Choices.

Download the form export

Notes & things to consider

Now whilst this example might get you 70% of what you’re after, you’ll probably need to further customise the CSS to get it looking how you want it. WordPress themes differ dramatically, so more often than not, you’ll need to tweak the example custom CSS provided further.

Due to the somewhat custom nature of this tutorial, we’re only able to offer very basic support to help you get these examples working. Every WordPress install is unique for your needs and thus near impossible for us to help implement these examples into everyone’s site. Take these examples as inspiration to try it yourself and see if you learn anything along the way. We’ll help where we can but can’t promise the world. Thanks for understanding.

These examples are currently not mobile-friendly.Yeh we know we know, we’ll get to it as soon as possible and update the CSS example code above when we do.