Revisited: Multi-Column Gravity Forms

Over two years ago, I wrote an article describing how I sent about creating a multi-column layout for Gravity Forms. Seeing as how Rocketgenius still hasn’t included a built-in option to achieve this, I recently decided to revisit it.


Gravity Forms, we all love this plugin. It handles all the complexities of form creation, management, submission validation, and entry storage while allowing us focus on more of the fun bits when building a website. However, one of the limitations we’ve found while using this plugin for the better part of four years is the inability to split our forms into multiple columns.

Imagine a scenario where you’re asked to create a form to look like the following mockup:

What are doing over there, message field? Get back in your column!
What are doing over there, message field? Get back in your column!

This is clearly not something Gravity Forms’ CSS-ready classes could handle as the first four fields in the form are all shifted to the left as a group while the message field is shifted right on its own. We’re going to require a system that actually separates the HTML elements so we can control the layout of each group in the form.

The Default Form Structure

Before we can dive too far into adding column splitting to our form, we need to understand how Gravity Forms structures it’s forms’ HTML output. Here’s the default output of the form body from the example above:

html
<div class="gform_body">
<ul id="gform_fields_2" class="gform_fields top_label form_sublabel_below description_below">
 	<li id="field_2_1" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_1">Your Name</label>
<div class="ginput_container ginput_container_text"><input id="input_2_1" class="medium" tabindex="1" name="input_1" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_2" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_2">Your Phone Number</label>
<div class="ginput_container ginput_container_text"><input id="input_2_2" class="medium" tabindex="2" name="input_2" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_3" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_3">Your Email Address</label>
<div class="ginput_container ginput_container_email"><input id="input_2_3" class="medium" tabindex="3" name="input_3" type="email" value="" /></div></li>
 	<li id="field_2_4" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_4">What can we help you with?</label>
<div class="ginput_container ginput_container_select"><select id="input_2_4" class="medium gfield_select" tabindex="4" name="input_4" aria-invalid="false">
<option value="">Select Option...</option>
<option value="Product Quote">Product Quote</option>
<option value="Technical Support">Technical Support</option>
<option value="General Inquiry">General Inquiry</option>
</select></div></li>
 	<li id="field_2_5" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_5">Message</label>
<div class="ginput_container"><textarea id="input_2_5" class="textarea medium" tabindex="5" cols="50" name="input_5" rows="10" aria-invalid="false"></textarea></div></li>
</ul>
</div>

As you can see, fields are output as line items within a single unordered list. Ideally, we’ll want to split that unordered list before the final field so it can be arranged separate from the rest.

Splitting the List of Fields

The first step in breaking the list of fields into separate groups is to define where in the list to create the split. To do this, we’ll create a new field that can be added to forms to indicate a column break:

Creating the Column Field Type

In this case, we’re following the development pattern of extending the GF_Field class in order to introduce a new field type to the form editor:

php
if(!class_exists('GF_Field_Column') &amp;&amp; class_exists('GF_Field')) {
class GF_Field_Column extends GF_Field {

public $type = 'column';

public function get_form_editor_field_title() {
return esc_attr__('Column Break', 'gravityforms');
}

public function is_conditional_logic_supported() {
return false;
}

function get_form_editor_field_settings() {
return array(
'column_description',
'css_class_setting'
);
}

public function get_field_input($form, $value = '', $entry = null) {
return '';
}

public function get_field_content($value, $force_frontend_label, $form) {

$is_entry_detail = $this-&gt;is_entry_detail();
$is_form_editor = $this-&gt;is_form_editor();
$is_admin = $is_entry_detail || $is_form_editor;

if($is_admin) {
$admin_buttons = $this-&gt;get_admin_buttons();
return $admin_buttons.'<label class="\'gfield_label\'">'.$this-&gt;get_form_editor_field_title().'</label>{FIELD}

<hr />

';
}

return '';
}

}
}

This GF_Field_Column class sets its $type property to 'column' so we can easily identify this field later on. The rest of the methods we’re overriding from the parent class control how the field looks and behaves within the form editor. One thing to note here is the inclusion of the 'column_description' element in the returned field settings array. This will be used later on for the output of the column field’s description in the admin.

Next, we need to actually register the new field so Gravity Forms knows to include it in the editor:

php
function register_gf_field_column() {
if(!class_exists('GFForms') || !class_exists('GF_Field_Column')) return;
GF_Fields::register(new GF_Field_Column());
}
add_action('init', 'register_gf_field_column', 20);

We’re registering this function on the init hook with a priority of 20 to ensure that Gravity Forms has been properly initialized, if that plugin is activated.

You should now see this new little field type in the form editor:

We're getting fancy now.
We’re getting fancy now.

Adding this field to your form should should insert the controls for the column break element:

Not exactly the most useful field type...
Not exactly the most useful field type…

Now, this isn’t the most professional-looking UI as the general tab is completely empty. Let’s add something to it so users don’t get confused:

php
function add_gf_field_column_settings($placement, $form_id) {
if($placement == 0) {
$description = 'Column breaks should be placed between fields to split form into separate columns. You do not need to place any column breaks at the beginning or end of the form, only in the middle.';
echo '
<ul>
 	<li class="column_description field_setting">'.$description.'</li>
</ul>
';
}
}
add_action('gform_field_standard_settings', 'add_gf_field_column_settings', 10, 2);

This function specifies the controls to display for the 'column_description' field setting we defined earlier in our GF_Field_Column class. In this case, all we’re doing is providing a general description at the top of the general settings tab for how this column field type should be used.

Now we're talking!
Now we’re talking!

 

Starting a New List of Fields

At this point, now that we’ve inserted the new column field type, our HTML output of the form’s body should look something like this:

html
<div class="gform_body">
<ul id="gform_fields_2" class="gform_fields top_label form_sublabel_below description_below">
 	<li id="field_2_1" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_1">Your Name</label>
<div class="ginput_container ginput_container_text"><input id="input_2_1" class="medium" tabindex="1" name="input_1" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_2" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_2">Your Phone Number</label>
<div class="ginput_container ginput_container_text"><input id="input_2_2" class="medium" tabindex="2" name="input_2" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_3" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_3">Your Email Address</label>
<div class="ginput_container ginput_container_email"><input id="input_2_3" class="medium" tabindex="3" name="input_3" type="email" value="" /></div></li>
 	<li id="field_2_4" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_4">What can we help you with?</label>
<div class="ginput_container ginput_container_select"><select id="input_2_4" class="medium gfield_select" tabindex="4" name="input_4" aria-invalid="false">
<option value="">Select Option...</option>
<option value="Product Quote">Product Quote</option>
<option value="Technical Support">Technical Support</option>
<option value="General Inquiry">General Inquiry</option>
</select></div></li>
 	<li id="field_2_6" class="gfield field_sublabel_below field_description_below"></li>
 	<li id="field_2_5" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_5">Message</label>
<div class="ginput_container"><textarea id="input_2_5" class="textarea medium" tabindex="5" cols="50" name="input_5" rows="10" aria-invalid="false"></textarea></div></li>
</ul>
</div>

Not a lot has changed since we started with the exception of the empty list item in the location we specified in the form editor. We can now target our new field’s type to replace the output of this element with a closing unordered list tag as well as a new opening one to begin a new list of fields within the form body. This is achieved by adding a filter to the field container:

php
function filter_gf_field_column_container($field_container, $field, $form, $css_class, $style, $field_content) {
if(IS_ADMIN) return $field_container;
if($field['type'] == 'column') {
$column_index = 2;
foreach($form['fields'] as $form_field) {
if($form_field['id'] == $field['id']) break;
if($form_field['type'] == 'column') $column_index++;
}
return '
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">
 	<li style="list-style-type: none;">
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">';</ul>
</li>
</ul>
&nbsp;
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">
 	<li style="list-style-type: none;">
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">}</ul>
</li>
</ul>
&nbsp;
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">
 	<li style="list-style-type: none;">
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">return $field_container;</ul>
</li>
</ul>
&nbsp;
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">
 	<li style="list-style-type: none;">
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">}</ul>
</li>
</ul>
&nbsp;
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">
 	<li style="list-style-type: none;">
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">add_filter('gform_field_container', 'filter_gf_field_column_container', 10, 6);</ul>
</li>
</ul>
&nbsp;
<ul class="'.GFCommon::get_ul_classes($form).' column column_'.$column_index.' '.$field&#91;'cssClass'&#93;.'">

This closes the previous unordered list and starts a new one with a similar configuration to the original, effectively splitting the groups of fields. We’re also adding classes to the new list to specify this is a column and which column it is in relation to the other column fields found in the form’s configuration.

The output should look something like this:

html
<div class="gform_body">
<ul id="gform_fields_2" class="gform_fields top_label form_sublabel_below description_below">
 	<li id="field_2_1" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_1">Your Name</label>
<div class="ginput_container ginput_container_text"><input id="input_2_1" class="medium" tabindex="1" name="input_1" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_2" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_2">Your Phone Number</label>
<div class="ginput_container ginput_container_text"><input id="input_2_2" class="medium" tabindex="2" name="input_2" type="text" value="" aria-invalid="false" /></div></li>
 	<li id="field_2_3" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_3">Your Email Address</label>
<div class="ginput_container ginput_container_email"><input id="input_2_3" class="medium" tabindex="3" name="input_3" type="email" value="" /></div></li>
 	<li id="field_2_4" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_4">What can we help you with?</label>
<div class="ginput_container ginput_container_select"><select id="input_2_4" class="medium gfield_select" tabindex="4" name="input_4" aria-invalid="false">
<option value="">Select Option...</option>
<option value="Product Quote">Product Quote</option>
<option value="Technical Support">Technical Support</option>
<option value="General Inquiry">General Inquiry</option>
</select></div></li>
</ul>
<ul class="gform_fields top_label form_sublabel_below description_below column column_2 ">
 	<li id="field_2_5" class="gfield field_sublabel_below field_description_below"><label class="gfield_label" for="input_2_5">Message</label>
<div class="ginput_container"><textarea id="input_2_5" class="textarea medium" tabindex="5" cols="50" name="input_5" rows="10" aria-invalid="false"></textarea></div></li>
</ul>
</div>

We did it!

Now that we’ve added column splitting to the form’s fields, it may be useful to add some additional CSS classes to the form wrapper to aid in styling the form’s layout:

php
function filter_gf_multi_column_pre_render($form, $ajax, $field_values) {
$column_count = 0;
$prev_page_field = null;
foreach($form['fields'] as $field) {
if($field['type'] == 'column') {
$column_count++;
} else if($field['type'] == 'page') {
if($column_count &gt; 0 &amp;&amp; empty($prev_page_field)) {
$form['firstPageCssClass'] = trim((isset($field['firstPageCssClass']) ? $field['firstPageCssClass'] : '').' gform_page_multi_column gform_page_column_count_'.($column_count + 1));
} else if($column_count &gt; 0) {
$prev_page_field['cssClass'] = trim((isset($prev_page_field['cssClass']) ? $prev_page_field['cssClass'] : '').' gform_page_multi_column gform_page_column_count_'.($column_count + 1));
}
$prev_page_field = $field;
$column_count = 0;
}
}
if($column_count &gt; 0 &amp;&amp; empty($prev_page_field)) {
$form['cssClass'] = trim((isset($form['cssClass']) ? $form['cssClass'] : '').' gform_multi_column gform_column_count_'.($column_count + 1));
} else if($column_count &gt; 0) {
$prev_page_field['cssClass'] = trim((isset($prev_page_field['cssClass']) ? $prev_page_field['cssClass'] : '').' gform_page_multi_column gform_page_column_count_'.($column_count + 1));
}
return $form;
}
add_filter('gform_pre_render', 'filter_gf_multi_column_pre_render', 10, 3);

This function loops through all the form’s fields and tracks how many column field types it finds. If there are any included, CSS classes are automatically added to the form wrapper (or form page wrapper if applicable) to convey this should be styled as a multi-column layout as well as indicate the number of columns it contains:

html
<div id="gform_wrapper_2" class="gform_wrapper gform_multi_column_wrapper gform_column_count_2_wrapper"><form id="gform_2" class="gform_multi_column gform_column_count_2" action="/" enctype="multipart/form-data" method="post">...

Styling the Form Columns

All that should be left is just defining how the separate columns should be laid out. Typically, we’ll want to display each column side by side. We’ll just need to include a few CSS definitions to accomplish this:

css
.gform_multi_column_wrapper .gform_body,
.gform_page_multi_column .gform_page_fields {
margin-left: -15px;
margin-right: -15px;
}
.gform_multi_column_wrapper .gform_body:after,
.gform_page_multi_column .gform_page_fields:after {
content: ' ';
display: table;
clear: both;
}
.gform_multi_column_wrapper ul.gform_fields,
.gform_page_multi_column ul.gform_fields {
float: left;
width: 100%;
padding-left: 15px;
padding-right: 15px;
}
@media (min-width: 768px) {
.gform_column_count_2_wrapper ul.gform_fields,
.gform_page_column_count_2 ul.gform_fields {
width: 50%;
}
.gform_column_count_3_wrapper ul.gform_fields,
.gform_page_column_count_3 ul.gform_fields {
width: 33.333333%;
}
.gform_column_count_4_wrapper ul.gform_fields,
.gform_page_column_count_4 ul.gform_fields {
width: 25%;
}
}

If the CSS classes we’re automatically including in multi-column forms are present, the lists of fields are evenly distributed horizontally for viewports 768px wide and up. These CSS definitions can be further customized to your liking, but the above rules should provide the basis of a multi-column form layout.


For a complete set of the code outlined here, you may download a standalone WordPress plugin to install that adds the described multi-column layout feature to your site:

Download Plugin

Discussion

    1. Hey Dave, for that, you’ll need to add some additional CSS to float that button to the right, like so:

      .gform_multi_column_wrapper .gform_footer:after {
      	content: ' ';
      	display: table;
      	clear: both;
      }
      .gform_multi_column_wrapper .gform_footer .gform_button {
      	float: right;
      	margin-right: 0;
      }
      
      Reply
      1. Hey Cameron,
        thanks for the tutorial, works great! Can you also place the buttons (both next and submit) directly below the content in the right column? Currently I have more content in the left column which pushes the button far too low.

        Reply
        1. Hey Steven, this may be a bit tricky since Gravity Forms doesn’t output the form footer until after the closing form body container. You may be able to call the GFFormDisplay::gform_footer immediately after the final field in the form and then suppress the default one, but I haven’t tested this to know if this would work or not.

  1. This is cool. Thanks for the plugin. I have a multi-page form. How can I get the multi-column effect to only occur on the page where it is placed?

    Reply
    1. Hmmm… I haven’t thought about forms with multiple pages as we don’t use those as often. I’ll need to do some digging to see if there’s a way this can still work for that setup.

      Reply
    2. I’ve updated the filter_gf_multi_column_pre_render function and CSS above to account for multi-column layouts for forms with multiple pages. I’ve also uploaded a new version of the plugin with the same revisions applied. Let me know if that works for you!

      Reply
  2. Love the plugin and code! Very clever and is exactly what we were looking for so thank you very much! Do you know if there is an easy way to change the css for two columns, so that the left is say 30% width and right side is 70% width instead of 50% for both?

    Many thanks!

    Reply
    1. Hey Steve, you should be able add some CSS like the following to make the columns different widths:

      @media (min-width: 768px) {
      	.gform_column_count_2_wrapper ul.gform_fields,
      	.gform_page_column_count_2 ul.gform_fields {
      		width: 30%;
      	}
      	.gform_column_count_2_wrapper ul.gform_fields.column_2,
      	.gform_page_column_count_2 ul.gform_fields.column_2 {
      		width: 70%;
      	}
      }
      

      What that does is makes two-column forms have columns that are 30% by default, but their second column would be 70% wide. Hope this helps!

      Reply
  3. Great stuff – exactly what I’ve been looking to achieve, so thanks for this!

    Curious, however, if it’s possible to go from 2 column back to single column and then back to 2 column again. So for example:

    currently it seems like once you activate a column break, everything else in the form past that break is destined to be trapped in the multi-column layout:

    thanks!

    Reply
    1. Yes, this would possible by defining some custom CSS classes in the form editor for each column break you add. That way, in your CSS, you can specify which of the <ul> elements should be full-width and which should only be 50% wide.

      Reply
      1. Hi Cameron, would you by chance have any example code on this part to split up some to be 100% and others 50%?

        Reply
        1. I haven’t tested this, but off the top of my head something like the following should work:

          @media (min-width: 768px) {
          	.gform_force_50_50_wrapper ul.gform_fields {
          		width: 50%;
          	}
          	.gform_multi_column_wrapper ul.gform_fields.full_width {
          		width: 100%;
          		clear: both;
          	}
          }
          

          For this to work, you’d need to add the class gform_force_50_50 to the form itself to force all columns to be 50% wide, regardless of how many separate columns you have. You’ll also need to at the full_width class to the columns you want to span the full width of the container.

          Hope this helps!

        2. Hey Cameron,

          It nearly works but not quite and can’t seem to figure it out – is this something we could potentially hire you to do for us?

          Thanks!

          Steve

  4. Thanks you for this!
    I have installed it with your plugin but it doesn’t seem to work… It’s basically 2 steps that I took:
    1. install plugin
    2. place column break between two fields

    Its all still in one column. Do I give the column break a css class? Or what else could I try?

    Reply
    1. Hey Jackie, would you be able to provide a link to the page with the form you’re trying to split into two columns? I can try to take a look at the output to see what might be going on.

      Reply
        1. Strange, the gform_multi_column_wrapper class isn’t getting applied to the form wrapper div. Is it possible to update to the latest version of Gravity Forms? It looks like they added a fix for this in version 2.0

        2. Well I’ll be damned. Cameron, thank you. I’ve been searching for hours and the solution was that simple. Great job!!

        3. Hmmm, would it be possible to position the second image absolutely, relative to the containing form column since the first image is at the top of the column already?

  5. Hey Cameron,

    this is much better than the usual solution – which I also loved and often used.

    Pretty thank you!

    Reply
  6. Hi Cameron, I love this – not sure why I haven’t run into it earlier. The lack of columns in Gravity feels like the Holy Grail until they do something about it.

    You have Column Break, but I’m curious if you’ve ever considered adding another field option Row Break which would add a simple drag and drop field to create a new row.

    This would allow for simple splitting of fields or groups of fields into columns, and then a Row Break to start a new row that would either be 100% width or broken up into additional columns based on the number of Column Breaks.

    For sake of formatting I’m trying to do the following:

    Row 1 – 1 column
    Row 2 – 2 columns
    Row 3 – 1 column
    Row 4 – 2 columns
    Row 5 – 2 columns

    More than happy to pay for or make some type of donation to make this happen. Please let me know.

    All the best,
    John

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

425.407.5160

Get a Quote

  • Contact Information

  • Project Details

  • Accepted file types: doc, pdf.