Using a Theme Config to render your form is the recommended approach for quickly customising your form's output HTML. Put simply, it provides you an easy way to control and manipulate each HTML element and attribute that's used to build a form, through its pages, rows, fields, buttons and more. This allows you to change the attributes (commonly class attributes) for components like the submit button, field <input>
elements, and even the <form>
element itself.
For this reason, it's well-suited to being used to output opinionated class attributes used by utility CSS frameworks like Tailwind (opens new window), or other frameworks like Bootstrap (opens new window).
We recommend reading the theming overview docs before getting started, for an explanation of theme config compared to other methods of theming forms.
To gain a better understanding of what theme config is for, let's walk through the HTML output of a form. Take for example the default structure below (without every attribute shown):
<div class="fui-i">
<form class="fui-form">
<div class="fui-form-container">
<div class="fui-page">
<div class="fui-page-container">
<div class="fui-row fui-page-row">
<div class="fui-field">
<div class="fui-field-container">
<label class="fui-label">My Example Field</label>
<div class="fui-instructions">Some instructions</div>
<div class="fui-input-container">
<input type="text">
</div>
</div>
<div class="fui-errors">
<div class="fui-error">
</div>
</div>
Here, we have outlined the full structure of a form, containing a single field which would be rendered by craft.formie.renderForm()
. While there are many HTML elements, each serve a purpose with styling, semantic structure, accessibility and general organisation.
Let's say, we want to add a my-form
ID attribute to the <form>
element. Whilst we could use custom rendering or template overrides to gain control over the templating used to generate the <form>
element, that's quite heavy-handed, and relies on you now maintaining that template as Formie changes.
Instead, we can use theme config to manipulate the attributes for this form.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
form: {
attributes: {
id: 'my-form',
},
},
},
}) }}
{# Rendering #}
<div class="fui-i">
<form id="my-form" class="fui-form">
<div class="fui-form-container">
<div class="fui-page">
...
Let's try a few more things at once for fun:
<div class="fui-i">
<form>
element and add id="my-form"
<div class="fui-form-container">
to <fieldset class="fui-form-container">
<input>
element for all fields (Tailwind){{ craft.formie.renderForm('contactForm', {
themeConfig: {
formWrapper: false,
form: {
resetClass: true,
attributes: {
id: 'my-form',
},
},
formContainer: {
tag: 'fieldset',
},
field: {
attributes: {
class: 'p-4 w-full mb-4',
},
},
fieldInput: {
attributes: {
class: 'border border-red-500',
},
},
},
}) }}
{# Rendering #}
<form id="my-form">
<fieldset class="fui-form-container">
<div class="fui-page">
<div class="fui-page-container">
<div class="fui-row fui-page-row">
<div class="fui-field p-4 w-full mb-4">
<div class="fui-field-container">
<label class="fui-label">My Example Field</label>
<div class="fui-instructions">Some instructions</div>
<div class="fui-input-container">
<input type="text" class="border border-red-500">
</div>
</div>
<div class="fui-errors">
<div class="fui-error">
</div>
</div>
As you can see above, we're passing in a Twig object with keys for certain components of the rendered HTML, and either providing attributes as a nested Twig object, or the ability to exclude them from render altogether (returning false
or null
).
Formie has several of these "theme tags" defined, to allow us to define an API of sorts for how a form is structured, and how you can provide definitions for them. Some are for the overall form, for fields, and for specific field types. Every tag definition can have the following set:
Attribute | Type | Description |
---|---|---|
resetClass | Boolean | Whether to retain or remove any fui-* classes for the element. |
tag | String | the valid HTML to be used for the HTML element. |
attributes | Boolean | A collection of valid HTML attributes for the HTML element. Some items can be arrays like class , data , style or aria . This works exactly like Craft's attr function. |
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
formContainer: {
tag: 'fieldset',
resetClass: true,
attributes: {
class: ['one', 'two'],
disabled: true,
readonly: false,
style: {
'background-color': 'red',
'font-size': '20px',
},
},
},
},
}) }}
For a full list of available tags, refer to the below:
formWrapper
form
formContainer
alertError
alertSuccess
errors
error
formTitle
page
pageContainer
pageTitle
row
pageTabs
pageTab
pageTabLink
progressWrapper
progress
progressContainer
progressValue
buttonWrapper
submitButton
backButton
saveButton
field
fieldContainer
fieldLabel
fieldRequired
fieldInstructions
fieldInputContainer
fieldInput
fieldErrors
fieldError
subFieldRows
subFieldRow
fieldOption
fieldOptionLabel
fieldOptions
fieldOption
fieldOptionLabel
subFieldRows
subFieldRow
fieldSummary
fieldSummaryContainer
fieldSummaryItem
nestedFieldRows
nestedFieldRow
nestedFieldContainer
fieldHeading
fieldLimit
subFieldRows
subFieldRow
fieldOptions
fieldOption
fieldOptionLabel
nestedField
nestedFieldWrapper
nestedFieldRows
nestedFieldRow
nestedFieldContainer
fieldAddButton
fieldRemoveButton
fieldSection
fieldCanvas
fieldRemoveButton
fieldLimit
fieldSummaryBlocks
fieldSummaryBlock
fieldSummaryHeading
fieldTable
fieldTableHeader
fieldTableHeaderRow
fieldTableHeaderColumn
fieldTableBody
fieldTableBodyRow
fieldTableBodyColumn
fieldAddButton
fieldRemoveButton
In addition, you can also target fields on a particular type. You can then provide all the same field-level tag configs that you would for general fields.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
singleLineText: {
field: {
attributes: {
class: 'just-for-text',
},
},
},
},
}) }}
These tags are the camelCase
string for the field type class.
address
agree
calculations
categories
checkboxes
dateTime
dropdown
emailAddress
entries
fileUpload
group
heading
hiddenField
html
multiLineText
name
number
payment
password
phoneNumber
radioButtons
recipients
repeater
section
signature
singleLineText
summary
table
tags
users
products
variants
Below are some common examples to better understand what you can do with theme config.
We can append attributes to an element, ensuring that existing ones are kept in-place.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
field: {
attributes: {
class: 'appended-class',
'data-custom': 'my-field',
},
},
},
}) }}
{# Rendering #}
<div class="fui-field appended-class" data-field-type="single-line-text" data-custom="my-field">
...
If you don't want to retain Formie's default classes on tag, you can remove them by passing resetClass
.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
field: {
resetClass: true,
attributes: {
class: 'my-field',
},
},
},
}) }}
{# Rendering #}
<div class="my-field">
...
If you don't want to render a tag at all, you can return a falsey value.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
fieldContainer: false,
fieldInputContainer: null,
},
}) }}
{# Rendering #}
<div class="fui-field">
<label class="fui-label">My Example Field</label>
<div class="fui-instructions">Some instructions</div>
<input type="text">
</div>
Like using resetClass
on an individual tag, you can use resetClasses
at the theme config level to remove all classes for everything. This can provide you with a blank-slate for you to theme. No other attributes are reset to ensure accessibility and Formie's JS functionality.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
resetClasses: true,
},
}) }}
{# Rendering #}
<div>
<form id="uniqueId" method="post" data-fui-form>
<div>
<div>
<div>
<div>
<div data-field-handle="example" data-field-type="single-line-text">
<div>
<label for="uniqueId">My Example Field</label>
<div>Some instructions</div>
<div>
<input id="uniqueId" type="text" name="fields[example]">
</div>
</div>
</div>
...
You can also include Twig templating within the value of a class for even more powerful usage.
Let's take for example the Recipients field. There's 4 different display options for this field; Dropdown, Hidden, Checkboxes and Radio Buttons. In the case of when this field is Hidden, we clearly don't want the field to be visible (but still rendered), but there's not an easy way to change classes depending on settings on the field.
Fortunately, we've included the ability to write Twig in class values.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
recipients: {
field: {
attributes: {
class: [
'hidden-field',
"{{ field.getIsHidden() ? 'is-hidden' : false }}",
],
},
},
},
},
}) }}
Here, we're targeting all Recipients fields, and the field
theme tag. We're applying a hidden-field
class on this tag, but you'll also notice the Twig code surrounded by "
- {{ field.getIsHidden() ? 'is-hidden' : false }}
. When you provide a Twig template as a string, Formie will evaluate that Twig code in the context of the current field.
You'll notice we're calling field.getIsHidden()
which is a Field method. You could call any property or method on this field if your template. We're then using a ternary operator to return is-hidden
if the getIsHidden()
function returns true, or false
if not.
Your Twig template can be whatever you need, just ensure to wrap is as a string with either "
or '
characters.
We've put together a few full-featured and drop-in theme config's for you to use in your forms. Each example completely removes Formie's default fui-*
classes. You're welcome to use them as-is, or modify them for your next project.
Have a theme config you'd like to share? Drop us a line.
There are 3 methods for how you define a theme config, which are shown below in order of priority (with the first being the lowest priority).
craft.formie.renderForm()
.Although each method is a different way to define a theme config, it all ends up being treated the same - just a different way of registering this configuration. Your project could even use a combination of all methods!
Used with your craft.formie.renderForm()
in the form of Render Options, you can provide a config for your form at render time with Twig.
{{ craft.formie.renderForm('contactForm', {
themeConfig: {
formWrapper: {
attributes: {
class: 'border border-green-500',
},
},
form: {
attributes: {
class: 'border border-red-500',
},
},
field: {
attributes: {
class: 'border border-blue-500',
},
},
fieldInput: {
tag: 'div',
resetClass: true,
attributes: {
class: 'border border-blue-500',
},
},
},
}) }}
You can also use Events to define your theme config in the same manner. Using PHP gives you more control and flexibility than Twig, but does require knowledge of PHP and Craft's modules.
For form-level tags:
use verbb\formie\elements\Form;
use verbb\formie\events\ModifyFormHtmlTagEvent;
use yii\base\Event;
Event::on(Form::class, Form::EVENT_MODIFY_HTML_TAG, function(ModifyFormHtmlTagEvent $event) {
$form = $event->form;
$tag = $event->tag;
$key = $event->key;
$context = $event->context;
// For the field `<form>` element, change the tag and add attributes
if ($event->key === 'form') {
$event->tag->tag = 'div';
$event->tag->attributes['class'][] = 'p-4 w-full mb-4';
}
});
For field-level tags:
use verbb\formie\base\FormField;
use verbb\formie\fields\formfields\SingleLineText;
use verbb\formie\events\ModifyFieldHtmlTagEvent;
use yii\base\Event;
// Provide config for all fields
Event::on(FormField::class, FormField::EVENT_MODIFY_HTML_TAG, function(ModifyFieldHtmlTagEvent $event) {
$field = $event->field;
$tag = $event->tag;
$key = $event->key;
$context = $event->context;
// For the main field element, replace the class attribute entirely
if ($event->key === 'field') {
$event->tag->attributes['class'] = 'p-4 w-full mb-4';
}
// For the inner field wrapper, don't render the element
if ($event->key === 'fieldContainer') {
$event->tag = null;
}
});
// Single-line-text field-specific config
Event::on(SingleLineText::class, SingleLineText::EVENT_MODIFY_HTML_TAG, function(ModifyFieldHtmlTagEvent $event) {
$field = $event->field;
$tag = $event->tag;
$key = $event->key;
$context = $event->context;
// For the field `<input>` element, change the tag and add attributes
if ($event->key === 'fieldInput') {
$event->tag->tag = 'span';
$event->tag->attributes['class'][] = 'p-4 w-full mb-4';
}
});
You can also define your config at the global plugin level through the plugin configuration file. Because this has the least priority, all other methods can override your definition here. This allows you to create sane defaults at the plugin level - even across multiple projects - and tweak them as needed.
<?php
return [
'themeConfig' => [
// Remove all of Formie's classes by default
'resetClasses' => true,
// For the field `<form>` element, change the tag and add attributes
'form' => [
'attributes' => [
'class' => 'p-4 w-full mb-4',
'tag' => 'div',
],
],
// For the main field element
'field' => [
'attributes' => [
'class' => 'p-4 w-full mb-4',
],
],
// Specifically for a single-line-text field, we want the label uppercased
'singleLineText' => [
'fieldLabel' => [
'attributes' => [
'class' => 'uppercase',
],
],
],
],
];
Even if you want to use template overrides, you can still use theme config in a non-breaking manner. Let's look at the default Twig template for a Single-Line Text field.
{{ fieldtag('fieldInput', {
value: value ?? false,
}) }}
Well, that's certainly short and sweet! All the HTML for this field is actually defined at the class level, so there's very little HTML we need to write to output the <input>
HTML element. This allows us to provide you with the ability to easily override our default config.
You have two choices here; 1. Add your own attributes to the fieldInput
tag, or 2. Write your own HTML.
It might be a good idea to have your custom changes still allow theme config to be used. You can do this by extending the theme config used by the fieldtag()
Twig function.
The fieldtag()
Twig function and the {% fieldtag %}
Twig tag are almost identical to Craft's own tag()
and {% tag %}
functionality.
{{ fieldtag('fieldInput', {
value: value ?? false,
class: 'my-custom-class',
'data-custom-field': true,
}) }}
{# or #}
{% fieldtag 'fieldInput' with {
value: value ?? false,
class: 'my-custom-class',
'data-custom-field': true,
} %}
Some content
{% endfieldtag %}
Of course, there's nothing stopping you from writing your own HTML and Twig, you'll just lose the ability to use theme config for this field. However, it may very well be easier if you have complex logic for this field, want full control over output, or just find it easier to manage.
{# Just HTML #}
<input type="text" value="{{ value ?? false }}" class="my-custom-class" data-custom-field>
{# Using `tag()` #}
{{ tag('input', {
type: 'text',
value: value ?? false,
class: 'my-custom-class',
'data-custom-field': true,
}) }}