- How to Work with Form Themes
- How to Work with Form Themes
- Symfony Built-In Form Themes
- Applying Themes to all Forms
- Applying Themes to Single Forms
- Creating your Own Form Theme
How to Work with Form Themes
How to Work with Form Themes
This article explains how to use in your app any of the form themes provided by Symfony and how to create your own custom form theme.
Symfony Built-In Form Themes
Symfony comes with several built-in form themes that make your forms look great when using some of the most popular CSS frameworks. Each theme is defined in a single Twig template and they are enabled in the twig.form_themes option:
- form_div_layout.html.twig, wraps each form field inside a
<div>
element and it’s the theme used by default in Symfony applications unless you configure it as explained later in this article. - form_table_layout.html.twig, wraps the entire form inside a
<table>
element and each form field inside a<tr>
element. - bootstrap_3_layout.html.twig, wraps each form field inside a
<div>
element with the appropriate CSS classes to apply the styles used by the Bootstrap 3 CSS framework. - bootstrap_3_horizontal_layout.html.twig, it’s similar to the previous theme, but the CSS classes applied are the ones used to display the forms horizontally (i.e. the label and the widget in the same row).
- bootstrap_4_layout.html.twig, same as
bootstrap_3_layout.html.twig
, but updated for Bootstrap 4 CSS framework styles. - bootstrap_4_horizontal_layout.html.twig, same as
bootstrap_3_horizontal_layout.html.twig
but updated for Bootstrap 4 styles. - foundation_5_layout.html.twig, wraps each form field inside a
<div>
element with the appropriate CSS classes to apply the default styles of the Foundation CSS framework.
Tip
Read the article about the Bootstrap 4 Symfony form theme to learn more about it.
Applying Themes to all Forms
Symfony forms use by default the form_div_layout.html.twig
theme. If you want to use another theme for all the forms of your app, configure it in the twig.form_themes
option:
YAML
# config/packages/twig.yaml
twig:
form_themes: ['bootstrap_4_horizontal_layout.html.twig']
# ...
XML
<!-- config/packages/twig.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd">
<twig:config>
<twig:form-theme>bootstrap_4_horizontal_layout.html.twig</twig:form-theme>
<!-- ... -->
</twig:config>
</container>
PHP
// config/packages/twig.php
$container->loadFromExtension('twig', [
'form_themes' => [
'bootstrap_4_horizontal_layout.html.twig',
],
// ...
]);
You can pass multiple themes to this option because sometimes form themes only redefine a few elements. This way, if some theme doesn’t override some element, Symfony looks up in the other themes.
The order of the themes in the twig.form_themes
option is important. Each theme overrides all the previous themes, so you must put the most important themes at the end of the list.
Applying Themes to Single Forms
Although most of the times you’ll apply form themes globally, you may need to apply a theme only to some specific form. You can do that with the form_theme Twig tag:
{# this form theme will be applied only to the form of this template #}
{% form_theme form 'foundation_5_layout.html.twig' %}
{{ form_start(form) }}
{# ... #}
{{ form_end(form) }}
The first argument of the form_theme
tag (form
in this example) is the name of the variable that stores the form view object. The second argument is the path of the Twig template that defines the form theme.
Applying Multiple Themes to Single Forms
A form can also be customized by applying several themes. To do this, pass the path of all the Twig templates as an array using the with
keyword (their order is important, because each theme overrides all the previous ones):
{# apply multiple form themes but only to the form of this template #}
{% form_theme form with [
'foundation_5_layout.html.twig',
'forms/my_custom_theme.html.twig'
] %}
{# ... #}
Applying Different Themes to Child Forms
You can also apply a form theme to a specific child of your form:
{% form_theme form.a_child_form 'form/my_custom_theme.html.twig' %}
This is useful when you want to have a custom theme for a nested form that’s different than the one of your main form. Specify both your themes:
{% form_theme form 'form/my_custom_theme.html.twig' %}
{% form_theme form.a_child_form 'form/my_other_theme.html.twig' %}
Disabling Global Themes for Single Forms
Global form themes defined in the app are always applied to all forms, even those which use the form_theme
tag to apply their own themes. You may want to disable this for example when creating an admin interface for a bundle which can be installed on different Symfony applications (and so you can’t control what themes are enabled globally). To do that, add the only
keyword after the list of form themes:
{% form_theme form with ['foundation_5_layout.html.twig'] only %}
{# ... #}
Caution
When using the only
keyword, none of Symfony’s built-in form themes (form_div_layout.html.twig
, etc.) will be applied. In order to render your forms correctly, you need to either provide a fully-featured form theme yourself, or extend one of the built-in form themes with Twig’s use
keyword instead of extends
to re-use the original theme contents.
{# templates/form/common.html.twig #}
{% use "form_div_layout.html.twig" %}
{# ... #}
Creating your Own Form Theme
Symfony uses Twig blocks to render each part of a form - field labels, errors, <input>
text fields, <select>
tags, etc. A theme is a Twig template with one or more of those blocks that you want to use when rendering a form.
Consider for example a form field that represents an integer property called age
. If you add this to the template:
{{ form_widget(form.age) }}
The generated HTML content will be something like this (it will vary depending upon the form themes enabled in your app):
<input type="number" id="form_age" name="form[age]" required="required" value="33"/>
Symfony uses a Twig block called integer_widget
to render that field. This is because the field type is integer
and you’re rendering its widget
(as opposed to its label
or errors
or help
). The first step to create a form theme is to know which Twig block to override, as explained in the following section.
Form Fragment Naming
The naming of form fragments varies depending on your needs:
- If you want to customize all fields of the same type (e.g. all
<textarea>
) use thefield-type_field-part
pattern (e.g.textarea_widget
). - If you want to customize only one specific field (e.g. the
<textarea>
used for thedescription
field of the form that edits products) use the_field-id_field-part
pattern (e.g._product_description_widget
).
In both cases, the field-part
can be any of these valid form field parts:
Fragment Naming for All Fields of the Same Type
These fragment names follow the type_part
pattern, where the type
corresponds to the field type being rendered (e.g. textarea
, checkbox
, date
, etc) and the part
corresponds to what is being rendered (e.g. label
, widget
, etc.)
A few examples of fragment names are:
form_row
- used by form_row() to render most fields;textarea_widget
- used by form_widget() to render atextarea
field type;form_errors
- used by form_errors() to render errors for a field;
Fragment Naming for Individual Fields
These fragment names follow the _id_part
pattern, where the id
corresponds to the field id
attribute (e.g. product_description
, user_age
, etc) and the part
corresponds to what is being rendered (e.g. label
, widget
, etc.)
The id
attribute contains both the form name and the field name (e.g. product_price
). The form name can be set manually or generated automatically based on your form type name (e.g. ProductType
equates to product
). If you’re not sure what your form name is, look at the HTML code rendered for your form. You can also define this value explicitly with the block_name
option:
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// ...
$builder->add('name', TextType::class, [
'block_name' => 'custom_name',
]);
}
In this example, the fragment name will be _product_custom_name_widget
instead of the default _product_name_widget
.
Custom Fragment Naming for Individual Fields
The block_prefix
option allows form fields to define their own custom fragment name. This is mostly useful to customize some instances of the same field without having to create a custom form type:
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('name', TextType::class, [
'block_prefix' => 'wrapped_text',
]);
}
New in version 4.3: The block_prefix
option was introduced in Symfony 4.3.
Now you can use wrapped_text_row
, wrapped_text_widget
, etc. as the block names.
Fragment Naming for Collections
When using a collection of forms, the fragment of each collection item follows a predefined pattern. For example, consider the following complex example where a TaskManagerType
has a collection of TaskListType
which in turn has a collection of TaskType
:
class TaskManagerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = []): void
{
// ...
$builder->add('taskLists', CollectionType::class, [
'entry_type' => TaskListType::class,
'block_name' => 'task_lists',
]);
}
}
class TaskListType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = []): void
{
// ...
$builder->add('tasks', CollectionType::class, [
'entry_type' => TaskType::class,
]);
}
}
class TaskType
{
public function buildForm(FormBuilderInterface $builder, array $options = []): void
{
$builder->add('name');
// ...
}
}
Then you get all the following customizable blocks (where *
can be replaced by row
, widget
, label
, or help
):
{% block _task_manager_task_lists_* %}
{# the collection field of TaskManager #}
{% endblock %}
{% block _task_manager_task_lists_entry_* %}
{# the inner TaskListType #}
{% endblock %}
{% block _task_manager_task_lists_entry_tasks_* %}
{# the collection field of TaskListType #}
{% endblock %}
{% block _task_manager_task_lists_entry_tasks_entry_* %}
{# the inner TaskType #}
{% endblock %}
{% block _task_manager_task_lists_entry_tasks_entry_name_* %}
{# the field of TaskType #}
{% endblock %}
Template Fragment Inheritance
Each field type has a parent type (e.g. the parent type of textarea
is text
, and the parent type of text
is form
) and Symfony uses the fragment for the parent type if the base fragment doesn’t exist.
When Symfony renders for example the errors for a textarea type, it looks first for a textarea_errors
fragment before falling back to the text_errors
and form_errors
fragments.
Tip
The “parent” type of each field type is available in the form type reference for each field type.
Creating a Form Theme in the same Template as the Form
This is recommended when doing customizations specific to a single form in your app, such as changing all <textarea>
elements of a form or customizing a very special form field which will be handled with JavaScript.
You only need to add the special {% form_theme form _self %}
tag to the same template where the form is rendered. This causes Twig to look inside the template for any overridden form blocks:
{% extends 'base.html.twig' %}
{% form_theme form _self %}
{# this overrides the widget of any field of type integer, but only in the
forms rendered inside this template #}
{% block integer_widget %}
<div class="...">
{# ... render the HTML element to display this field ... #}
</div>
{% endblock %}
{# this overrides the entire row of the field whose "id" = "product_stock" (and whose
"name" = "product[stock]") but only in the forms rendered inside this template #}
{% block _product_stock_row %}
<div class="..." id="...">
{# ... render the entire field contents, including its errors ... #}
</div>
{% endblock %}
{# ... render the form ... #}
The main disadvantage of this method is that it only works if your template extends another ('base.html.twig'
in the previous example). If your template does not, you must point form_theme
to a separate template, as explained in the next section.
Another disadvantage is that the customized form blocks can’t be reused when rendering other forms in other templates. If that’s what you need, create a form theme in a separate template as explained in the next section.
Creating a Form Theme in a Separate Template
This is recommended when creating form themes that are used in your entire app or even reused in different Symfony applications. You only need to create a Twig template somewhere and follow the form fragment naming rules to know which Twig blocks to define.
For example, if your form theme is simple and you only want to override the <input type="integer">
elements, create this template:
{# templates/form/my_theme.html.twig #}
{% block integer_widget %}
{# ... add all the HTML, CSS and JavaScript needed to render this field #}
{% endblock %}
Now you need to tell Symfony to use this form theme instead of (or in addition to) the default theme. As explained in the previous sections of this article, if you want to apply the theme globally to all forms, define the twig.form_themes
option:
YAML
# config/packages/twig.yaml
twig:
form_themes: ['form/my_theme.html.twig']
# ...
XML
<!-- config/packages/twig.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd">
<twig:config>
<twig:form-theme>form/my_theme.html.twig</twig:form-theme>
<!-- ... -->
</twig:config>
</container>
PHP
// config/packages/twig.php
$container->loadFromExtension('twig', [
'form_themes' => [
'form/my_theme.html.twig',
],
// ...
]);
If you only want to apply it to some specific forms, use the form_theme
tag:
{% form_theme form 'form/my_theme.html.twig' %}
{{ form_start(form) }}
{# ... #}
{{ form_end(form) }}
Reusing Parts of a Built-In Form Theme
Creating a complete form theme takes a lot of work because there are too many different form field types. Instead of defining all those Twig blocks, you can define only the blocks you are interested in and then configure multiple form themes in your app or template. This works because when rendering a block which is not overridden in your custom theme, Symfony falls back to the other themes.
Another solution is to make your form theme template extend from one of the built-in themes using the Twig “use” tag instead of the extends
tag so you can inherit all its blocks (if you are unsure, extend from the default form_div_layout.html.twig
theme):
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}
{# ... override only the blocks you are interested in #}
Finally, you can also use the Twig parent() function to reuse the original content of the built-in theme. This is useful when you only want to make minor changes, such as wrapping the generated HTML with some element:
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}
{% block integer_widget %}
<div class="some-custom-class">
{{ parent() }}
</div>
{% endblock %}
This technique also works when defining the form theme in the same template that renders the form. However, importing the blocks from the built-in themes is a bit more complicated:
{% form_theme form _self %}
{# import a block from the built-in theme and rename it so it doesn't
conflict with the same block defined in this template #}
{% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}
{% block integer_widget %}
<div class="some-custom-class">
{{ block('base_integer_widget') }}
</div>
{% endblock %}
{# ... render the form ... #}
Customizing the Form Validation Errors
If you define validation rules for your objects, you’ll see some validation error messages when the submitted data is not valid. These messages are displayed with the form_errors() function and can be customized with the form_errors
Twig block in any form theme, as explained in the previous sections.
An important thing to consider is that certain errors are associated to the entire form instead of a specific field. In order to differentiate between global and local errors, use one of the variables available in forms called compound
. If it is true
, it means that what’s being currently rendered is a collection of fields (e.g. a whole form), and not just an individual field:
{# templates/form/my_theme.html.twig #}
{% block form_errors %}
{% if errors|length > 0 %}
{% if compound %}
{# ... display the global form errors #}
<ul>
{% for error in errors %}
<li>{{ error.message }}</li>
{% endfor %}
</ul>
{% else %}
{# ... display the errors for a single field #}
{% endif %}
{% endif %}
{% endblock form_errors %}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.