Multi-Column Gravity Forms

There is no built-in way for the Gravity Forms plugin for WordPress to generate a true, multi-column form layout. However, this can easily be accomplished using a single filter hook and some accompanying CSS.

Occasionally, the mockups I’m using to build a WordPress site call for a multi-column form. Natively, Gravity Forms doesn’t support something likes this. Sure, they have what they call “CSS ready classes” for fields that will arrange form inputs in multiple columns when they’re ordered one right after the other. But what if the fields I want to appear side by side shouldn’t contextually be one right after the other?

Contextually, the third field should really come after the second in the HTML structure.

Contextually, the third field should really come after the second in the HTML structure.

From a user experience standpoint, it might make more sense to complete a form’s left column of fields before continuing on to the right column. You may be able to get away with using Gravity Form’s CSS ready classes to arrange them how you want but you wouldn’t be considering situations when the form has to go to a single column layout, such as for mobile devices. It’d really make more sense to group what you want to go in each column together so you may lay out each group side by side.

What We’re Working With

When looking at the HTML output by Gravity Forms, you’ll notice that all fields are organized into an unordered list with the class of .gform_fields:

html
<ul id="gform_fields_1" class="gform_fields top_label description_below">
	<li id="field_1_1" class="gfield">
		<label class="gfield_label" for="input_1_1">Field 1</label>
		<div class="ginput_container">
			<input name="input_1" id="input_1_1" type="text" value="" class="medium" tabindex="1">
		</div>
	</li>
	<li id="field_1_2" class="gfield">
		<label class="gfield_label" for="input_1_2">Field 2</label>
		<div class="ginput_container">
			<input name="input_2" id="input_1_2" type="text" value="" class="medium" tabindex="2">
		</div>
	</li>
	<li id="field_1_3" class="gfield">
		<label class="gfield_label" for="input_1_3">Field 3</label>
		<div class="ginput_container">
			<textarea name="input_6" id="input_1_3" class="textarea medium" tabindex="3" rows="10" cols="50"></textarea>
		</div>
	</li>
</ul>

The most logical way of splitting the third field off from the first two would be to separate it into its own list. If we can get the form’s fields to be organized into two separate lists, we could specify them to float left to get a two column layout while still being able to easily revert to a stacked layout for narrower devices.

Splitting the Columns

In order to split the fields list, first we’ll need to designate where we want these splits to occur. This can be achieved by adding special section break elements to our form using the form editor. We can mark these as column breaks by adding a special class to these section breaks, such as .gform_column. For example, for a two column layout, we’d have a section break as the first form field and another section break wherever we need the right column’s field to start. It’s also a good idea to specify the type of layout the form will take on by providing a class to the form itself, such as .two-column. Not only will we use these classes for styling the columns via CSS, we will use these classes to selectively target the correct column break points when generating the form’s HTML.

Using Gravity Forms’ gform_field_content filter hook, we can add the breaks to the unordered list to designate column breaks:

php
function gform_column_splits($content, $field, $value, $lead_id, $form_id) {
	if(!IS_ADMIN) { // only perform on the front end

		// target section breaks
		if($field['type'] == 'section') {
			$form = RGFormsModel::get_form_meta($form_id, true);

			// check for the presence of our special multi-column form class
			$form_class = explode(' ', $form['cssClass']);
			$form_class_matches = array_intersect($form_class, array('two-column'));

			// check for the presence of our special section break column class
			$field_class = explode(' ', $field['cssClass']);
			$field_class_matches = array_intersect($field_class, array('gform_column'));

			// if we have a column break field in a multi-column form, perform the list split
			if(!empty($form_class_matches) && !empty($field_class_matches)) {

				// we'll need to retrieve the form's properties for consistency
				$form = RGFormsModel::add_default_properties($form);
				$description_class = rgar($form, 'descriptionPlacement') == 'above' ? 'description_above' : 'description_below';

				// close current field's li and ul and begin a new list with the same form properties
				return '</li></ul><ul class="gform_fields '.$form&#91;'labelPlacement'&#93;.' '.$description_class.' '.$field&#91;'cssClass'&#93;.'"><li class="gfield gsection">';

			}
		}
	}

	return $content;
}
add_filter('gform_field_content', 'gform_column_splits', 10, 5);

Note how we’ve added the field’s CSS class to the new <ul>. This will allow us to target that list for applying a CSS float.

After this filter is applied, we should end up with something like this:

html
<ul id="gform_fields_1" class="gform_fields top_label description_below">
	<li id="field_1_1" class="gfield  gsection  gform_column"></li>
</ul>
<ul class="gform_fields top_label description_below gform_column">
	<li class="gfield gsection"></li>
	<li id="field_1_2" class="gfield">
		<label class="gfield_label" for="input_1_2">Field 1</label>
		<div class="ginput_container">
			<input name="input_2" id="input_1_2" type="text" value="" class="medium" tabindex="1">
		</div>
	</li>
	<li id="field_1_3" class="gfield">
		<label class="gfield_label" for="input_1_3">Field 2</label>
		<div class="ginput_container">
			<input name="input_3" id="input_1_3" type="text" value="" class="medium" tabindex="2">
		</div>
	</li>
	<li id="field_1_4" class="gfield  gsection  gform_column"></li>
</ul>
<ul class="gform_fields top_label description_below gform_column">
	<li class="gfield gsection"></li>
	<li id="field_1_5" class="gfield">
		<label class="gfield_label" for="input_1_5">Field 3</label>
		<div class="ginput_container">
			<textarea name="input_5" id="input_1_5" class="textarea medium" tabindex="3" rows="10" cols="50"></textarea>
		</div>
	</li>
</ul>

We’ve successfully split the third field into it’s own list!

Column Styling

Now that we have the fields we want grouped together in separate lists, we can style the form so both of these groups appear side by side. The actual CSS will vary from project to project, depending on the site’s design, but this would be one way of accomplishing the two-column layout:

css
.gform_wrapper.two-column_wrapper ul.gform_fields {
	display: none;
}
.gform_wrapper.two-column_wrapper ul.gform_fields.gform_column {
	display: block;
	float: left;
	width: 50%;
}
.gform_wrapper.two-column_wrapper ul.gform_column li.gsection:first-child {
	display: none;
}

The first style definition hides the unecessary field list at the beginning of the form by hiding all .gform_fields lists. The next definition overrides the previous just for .gform_column lists. The final style definition hides the empty <li> elements at the beginning of each column list.

We now have a multi-column form that can easily be restructured into a single column while keeping each column’s fields grouped together!

Responsive Grid Column Resets

Since the release of Bootstrap 3.0, developers have been able to take advantage of the framework’s grid mixins to create their own custom grids. Semantically, this has its advantages as your code no longer needs to be cluttered the standard gird class names, such as .col-sm-8. Instead, you can now selectively target the elements you want to appear as a grid using class names that contextually make sense:

less
#main {
	.make-row();
}
#primary {
	.make-sm-column(9);
}
#secondary {
	.make-sm-column(3);
}

This is perfect for instances when you only have to deal with a single row of columns for wider screens that changes to a stacked layout for mobile devices, such as a page’s main content section and a sidebar. However, you may run into issues when your try to arrange multiple rows of elements of varying heights, such as a grid of differently-sized images. Since all elements in the grid are simply floating left, they could potentially get “caught” on taller elements from the previous row, preventing the new row from starting flush on the left edge.

Responsive Column Reset

The Bootstrap documentation recommends using what they call a responsive column reset. By adding a clearing div between rows, it’ll guarantee that each new row will begin flush against the left edge.

These responsive column resets can be programmatically added when looping through your grid elements during output:

php
for($i = 0; $i < count($elements); $i++) {
	if($i > 0 && $i % 4 == 0) echo '<div class="clearfix hidden-xs"></div>';
	echo $element[$i];
}

Unfortunately, this brings us back to relying on unsemantic class names on empty divs that serve no purpose other than keeping rows separate. What can we do to avoid this?

The :nth-child Selector

Possibly, the cleanest solution would be to target every nth child of the grid and set it to clear left using CSS’ :nth-child selector:

css
ul li:nth-child(4n+1) {
	clear: left;
}

This is great as long as you don’t need to account for older browsers that don’t support the :nth-child selector.

Manual nth Child Designation

An alternative method for targeting every nth child in a group of elements is to programmatically add classes during output:

php
for($i = 0; $i < count($elements); $i++) {
	echo '<li class="'.($i % 4 == 0 ? 'new-row' : '').'">'.$element[$i].'</li>';
}

What this does is add the .new-row class to every 4n+1 element, allowing you to set them to clear left, resetting the row:

css
ul li.new-row {
	clear: left;
}

Responsive Grid Breakpoints

Expanding on these approaches, you can selectively target break points in your custom grid system based on device width using media queries. For example, if you wanted a two column layout for small devices that transforms into a four column grid for larger devices using the :nth-child method:

css
ul li:nth-child(2n+1) {
	clear: left;
}
@media (min-width: 768px) {
	ul li:nth-child(2n+1) {
		clear: none;
	}
	ul li:nth-child(4n+1) {
		clear: left;
	}
}

For older browsers, as before, you’ll need to add the targeting classes manually on output. This could become somewhat complex as some elements would qualify for multiple classes. It may be useful to create a function to assign the classes based on the element’s index:

php
function getColResetClasses($index) {
	$classes = array();
	if($index == 0) return $classes;
	for($i = 2; $i <= 12 && $i <= $index; $i++) {
		if($index % $i == 0) $classes&#91;&#93; = 'new-row-'.$i;
	}
	return implode(' ', $classes);
}

for($i = 0; $i < count($elements); $i++) {
	echo '<li class="'.getColResetClasses($i).'">'.$element[$i].'</li>';
}

The accompanying CSS would look something like this:

css
ul li.new-row-2 {
	clear: left;
}
@media (min-width: 768px) {
	ul li.new-row-2 {
		clear: none;
	}
	ul li.new-row-4 {
		clear: left;
	}
}

With my last couple of projects, I’ve been trying to avoid using the Bootstrap classes in my HTML code and incorporating the the framework’s components through LESS mixins. Using the grid system mixins helps take a significant step towards a more semantic markup and offers greater flexibility for column layouts.