Important: The information in this article is over 12 months old, and may be out of date or no longer relevant.
But hey, you're here anyway, so give it a read see if it still applies to you.
Forms really frustrate me. They’re simple… but also not. Submissions, validations, integrations to minimise spam, and also layouts. Even more so when the user’s content management system allows the user to edit their form. It means that your template code needs to be able to work with an unknown form – what happens if a new field is added… renamed…
Don’t get me wrong: user-editable forms are great – it means users can be in control of the information they’re requesting without needing code-level changes. I just don’t enjoy doing any coding for them. So the fact that Statamic uses its Blueprints for front-facing forms is great – users can create and edit their own forms. But that introduced a new issues: how do we create an Antlers template that is able to cope with an unknown form structure?
Statamic 4 now includes a Spacer fieldtype, and when combined with 4’s form sections, insanely opens up the form editing potential for users while respecting the layout seen in the Blueprint editor.
What we did before the Spacer
Before the Spacer field, we had an Antlers template that was responsible for outputting a form’s fields. If a separate template existed that matched the Form’s handle, it would use that (meaning we, at a code level, could write more custom layouts). If a template didn’t exist, a simple loop would take over.
For the simple generic template, to help with respecting the Blueprint’s field’s widths, we had a wrapper that added a max-width matching the field’s width.
For the hand-coded form template, we had full control… except if a new field was added by the user, the template would also need to be updated. So that’s not great either.
But we felt this gave the right balance: if a user has a specific form they needed coded – with a unique layout, additional logic, and so on, we could write the code for it – and if they make their own simple form (like a basic contact form) it would fall back to the generic template.
And this worked well, but what happens if a user has created short fields, such as:
The user is expecting them to appear like this – but if we just output them without caring about width, they’ll appear in a single column. In these examples, the blue columns are to help show each 33% width column.
OK, so if we update the template to follow the Blueprint’s field widths:
But what if the user wants to have narrow fields, 33% in width, in a single column?
Without creating a custom template, there’s no way to work around this. Without doing weird things like using “codes” in the “Display” or “Instructions” for your generic template to follow. Yeah, we did that too. Ask me for more about that lunacy.
We welcome the Spacer with open arms
The new Spacer fieldtype helps us fix these issues.
Think of the Spacer as an empty space which can be used to push other fields around. If we think of the output in terms of a CSS Grid, the Spacer is basically designed to take up column width, which helps the next Field appear on a new row.
Let’s update our Blueprint to add a few Spacers to put our 33% width fields in a single column:
And we now get… uh… this glorious output with these spacers that make no sense for the user on the front end:
Spacer labels? No thanks... they need to go. OK, easy… all we need to do is honour the width of the spacer, but hide it:
And look at that… a front end form that is matching the expected layout based on the Blueprint.
To help you out, here’s the Antlers code for a form with the handle called “spacer” that will output the above. It’s basically an expanded version of the Statamic docs field template code but one that honours the field widths:
1<div class="relative"> 2 {{ form:spacer }} 3 <div class="grid grid-cols-12 gap-2 z-10 absolute inset-0"> 4 <div class="col-span-4 bg-blue-100"></div> 5 <div class="col-span-4 bg-blue-100"></div> 6 <div class="col-span-4 bg-blue-100"></div> 7 </div> 8 9 <div class="grid grid-cols-12 gap-2 relative z-20">10 {{ fields }}11 {{ _widthClass = switch(12 (width == '100') => 'sm:col-span-12',13 (width == '75') => 'sm:col-span-9',14 (width == '66') => 'sm:col-span-8',15 (width == '50') => 'sm:col-span-6',16 (width == '33') => 'sm:col-span-4',17 (width == '25') => 'sm:col-span-3',18 () => 'w-full' )19 }}20 <div class="{{ _widthClass }}">21 {{ if type !== 'spacer' }}22 <div class="p-2">23 <label>{{ display }}</label>24 <div class="p-1">{{ field }}</div>25 </div>26 {{ /if }}27 </div>28 {{ /fields }}29 </div>30 {{ /form:spacer }}31</div>
There are a few key things here to take away too:
Why a 12 column grid?
Statamic offers field widths of 25%, 33%, 50%, 66%, 75% or 100%. And these percentages split nicely in to a 12 column grid – 3, 4, 6, 8, 9 or 12 columns.
Using a 12 column grid allows us to take any of these percentages and output them exactly.
You’ll notice that in the example, the grid only applies sm
and above – smaller than that, it will be a simple list.
Why check for the type == 'spacer'?
This allows us to not output the field when the type is spacer
– but note that this is after the div that has the column width. Remember, we want to make sure the spacer grid cell exists so we can honour its width – but we just want it to be empty.
You can even expand this to loop sections, then fields too – just make sure to check your variable scope for things like Instructions that appear in both.
For this, we’re using the scope
of our loops so we can ensure we keep our variables in check: because properties like instructions
exist in both Sections and Fields, without scoping you will end up with unexpected instructions in your output.
1<div class="relative"> 2 {{ form:spacer }} 3 4 <div class="relative z-20"> 5 {{ sections scope="_section" }} 6 {{ if _section:display || _section:instructions }} 7 <div class="text-xl p-2 mb-6"> 8 {{ if _section:display }} 9 <div class="font-bold">10 {{ _section:display }}11 </div>12 {{ /if }}13 {{ if _section:instructions }}14 <div class="text-sm text-gray-500">15 {{ _section:instructions }}16 </div>17 {{ /if }}18 </div>19 {{ /if }}20 <div class="grid grid-cols-12 gap-2">21 22 {{ fields scope="_field" }}23 {{ _widthClass = switch(24 (_field:width == '100') => 'sm:col-span-12',25 (_field:width == '75') => 'sm:col-span-9',26 (_field:width == '66') => 'sm:col-span-8',27 (_field:width == '50') => 'sm:col-span-6',28 (_field:width == '33') => 'sm:col-span-4',29 (_field:width == '25') => 'sm:col-span-3',30 () => 'w-full' )31 }}32 <div class="{{ _widthClass }}">33 {{ if _field:type !== 'spacer' }}34 <div class="p-2">35 <label>{{ _field:display }}</label>36 37 {{ if _field:instructions && _field:instructions_position === 'above' }}38 <div class="text-sm text-gray-500">{{ _field:instructions }}</div>39 {{ /if }}40 41 <div class="p-1">{{ _field:field }}</div>42 43 {{ if _field:instructions && _field:instructions_position === 'below' }}44 <div class="text-sm text-gray-500">{{ _field:instructions }}</div>45 {{ /if }}46 </div>47 {{ /if }}48 </div>49 {{ /fields }}50 </div>51 {{ /sections }}52 </div>53 {{ /form:spacer }}54</div>
While this will copy-and-paste run (just change Form handle of course), don't forget there's still more to do including correctly connecting labels to your field output (and for checkboxes and radio, will need some tweaking of your field templates). Make sure you and your team work towards creating accessible forms.
The new Spacer fieldtype is seemingly simple… and ultimately it is… but it also solves the big issue of translating what a Statamic CP user sees when editing a Blueprint, and what appears on the front end.
Using a loop like the above examples, we can now honour widths, sections, instructions and have a glorious grid layout that matches expectations without needing to code an individual form layout. How freaking cool is that?