How to Customize Form Rendering
This article explains how to customize individual form fields directly in your templates. For reusable customizations across your entire application, see Form Theming and Customization.
Rendering Forms
The simplest way to render a form is with a single function call:
1
{{ form(form) }}
This renders everything: the <form> tag, all fields, labels, errors, and
the closing tag. For more control, render each part separately:
1 2 3 4 5 6 7 8 9 10 11
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="row">
<div class="col">{{ form_row(form.firstName) }}</div>
<div class="col">{{ form_row(form.lastName) }}</div>
</div>
{{ form_row(form.email) }}
{{ form_rest(form) }}
{{ form_end(form) }}
Warning
It is considered a best practice to include {{ form_rest(form) }} before
form_end(). This ensures that hidden fields (such as CSRF tokens)
and any fields you may have forgotten are properly rendered.
Rendering Individual Field Parts
The form_row() function renders a complete field (label, widget, help,
errors). For finer control, render each part separately:
1 2 3 4 5 6
<div class="custom-field">
<i class="fa fa-calendar"></i> {{ form_label(form.dueDate) }}
{{ form_widget(form.dueDate) }}
<small>{{ form_help(form.dueDate) }}</small>
<div class="error">{{ form_errors(form.dueDate) }}</div>
</div>
Form Themes
The Twig functions and variables above help you customize individual fields. To customize all forms consistently (e.g., to match your CSS framework), use a built-in form theme or create your own.
Form Variables
Each form field exposes variables through its vars property. You can read
these variables directly or override them when calling rendering functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{# reading field variables #}
<label for="{{ form.email.vars.id }}"
class="{{ form.email.vars.required ? 'required' }}">
{{ form.email.vars.label }}
</label>
{% if form.email.vars.errors|length > 0 %}
<span class="has-errors">!</span>
{% endif %}
{# overriding variables when rendering #}
{{ form_label(form.email, 'Your Email Address') }}
{{ form_widget(form.email, {attr: {class: 'email-input', autofocus: true}}) }}
{{ form_row(form.email, {
label: 'Contact Email',
label_attr: {class: 'required'},
attr: {placeholder: 'user@example.com'}
}) }}
Note
Variables passed to form_widget(form) or form_row(form) are not
applied recursively to child fields. Render children individually to
customize them.
These variables are useful when building completely custom HTML without using
form themes. This is the full list of variables
available in form theme blocks and via form.field.vars:
| Variable | Description |
|---|---|
action |
The form's action URL |
attr |
HTML attributes for the input element (a key-value array) |
block_prefixes |
Array of block prefixes for this field's type hierarchy |
cache_key |
A unique key which is used for caching |
compound |
true if this field has a group of children (e.g. a choice field is a group of checkboxes) |
data |
The normalized data value |
disabled |
Whether the field is disabled |
errors |
Validation errors for this field. Note: form.errors only contains
global errors, not all field errors. Use valid to check form validity |
form |
The FormView instance itself |
full_name |
The name HTML attribute value (e.g., user[email]) |
help |
Help text displayed with the field |
id |
The id HTML attribute value (e.g., user_email) |
label_attr |
HTML attributes for the <label> element (a key-value array) |
label |
The label text |
method |
The form's HTTP method |
multipart |
Whether the form needs multipart/form-data encoding (file uploads) |
name |
The field name (e.g., email), not the full HTML name attribute |
required |
Whether the field is required (adds HTML5 validation to it) |
submitted |
Whether the form has been submitted |
translation_domain |
The translation domain for this form's labels and messages |
valid |
Whether the form/field passed validation |
value |
The field's current value for rendering. Only applies to the root form element |
Tip
Use {{ dump(form.field.vars) }} in your template to see all available
variables for a specific field.
Tip
Variables are set in the buildView() and finishView() methods of
each form type. Check those methods in the source code to see all available
variables for a specific field type.
Twig Form Functions
form(form_view, variables)
Renders a complete form including the opening and closing tags.
1
{{ form(form, {method: 'GET'}) }}
This is convenient for prototyping, but use the other helpers for more control:
1 2 3 4 5 6 7 8
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.name) }}
{{ form_row(form.dueDate) }}
{{ form_row(form.submit, {label: 'Submit me'}) }}
{{ form_end(form) }}
form_end(form_view, variables)
Renders the </form> closing tag. By default, it also calls form_rest()
to render any remaining fields, but you can disable that:
1 2 3 4
{{ form_end(form) }}
{# disable automatic form_rest() call #}
{{ form_end(form, {render_rest: false}) }}
form_errors(form_view, variables)
Renders validation errors. When called on the form itself, renders global errors:
1 2
{{ form_errors(form.name) }} {# field errors #}
{{ form_errors(form) }} {# global errors #}
Note
In the Bootstrap 4 and 5 themes, form_errors() is already included in
form_label(). See Bootstrap 5 theme documentation
for details.
form_help(form_view, variables)
Renders the help text configured via the help option.
1
{{ form_help(form.name) }}
form_label(form_view, label, variables)
Renders the <label> element. The second argument overrides the default label:
1 2 3 4 5 6 7 8 9
{{ form_label(form.name) }}
{{ form_label(form.name, 'Your Name', {label_attr: {'class': 'foo'}}) }}
{# this is equivalent #}
{{ form_label(form.name, null, {
label: 'Your name',
label_attr: {'class': 'foo'}
}) }}
form_parent(form_view)
Returns the parent form view, or null for the root form. Prefer this over
form.parent, which fails if the form has a field named parent.
When a form field has validation errors, all built-in form themes automatically
add the aria-invalid="true" attribute to the <input> element and link
it to its error messages using aria-describedby. If the field also has a
help text, both the help and error IDs are included in the aria-describedby
value. This improves accessibility for screen readers without any extra
configuration.
form_rest(form_view, variables)
Renders all fields not yet rendered, including hidden fields like CSRF tokens. Always include this to avoid missing fields:
1
{{ form_rest(form) }}
form_row(form_view, variables)
Renders a complete field row (label, widget, help, errors):
1
{{ form_row(form.name, {'label': 'Custom Label'}) }}
form_start(form_view, variables)
Renders the <form> opening tag with the configured method, action, and
enctype (when the form has file uploads).
1
{{ form_start(form, {method: 'GET'}) }}
form_widget(form_view, variables)
Renders the input element(s). Pass attr to add HTML attributes:
1
{{ form_widget(form.name, {attr: {'class': 'foo'}}) }}
HTML attributes are not applied recursively to child fields if you're rendering
many fields at once (e.g. form_widget(form)).
Twig Form Tests
Twig tests are used with the is Twig operator to create conditions.
selectedchoice(selected_value)
Checks if a choice equals the given value, useful for marking options as selected:
1
<option {% if choice is selectedchoice(value) %}selected="selected"{% endif %}>
rootform
Checks if a form view is the root form (has no parent). Prefer this over
checking form.parent is null, which fails if the form has a field named
parent:
1 2 3
{% if form is rootform %}
{{ form_errors(form) }}
{% endif %}
Form Field Helpers
The Twig form functions generate complete
HTML elements based on the active form theme. When you
need full control over the HTML (bypassing themes), use the field_*() helpers
that return raw values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<input
type="text"
name="{{ field_name(form.username) }}"
id="{{ form.username.vars.id }}"
value="{{ field_value(form.username) }}"
placeholder="{{ field_label(form.username) }}"
>
<select name="{{ field_name(form.country) }}">
<option value="">Choose...</option>
{% for label, value in field_choices(form.country) %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
field_choices(form_view)
Returns an iterable of label/value pairs for choice fields.
1 2 3 4 5 6
{# render a custom dropdown for a choice field #}
<ul class="dropdown" role="listbox">
{% for label, value in field_choices(form.country) %}
<li role="option" data-value="{{ value }}">{{ label }}</li>
{% endfor %}
</ul>
field_errors(form_view)
Returns an iterable of error messages for the field.
1 2 3
{% for error in field_errors(form.email) %}
<span class="error">{{ error }}</span>
{% endfor %}
field_help(form_view)
Returns the help text for the field.
1 2
<input type="text" name="{{ field_name(form.slug) }}" aria-describedby="slug-help">
<small id="slug-help">{{ field_help(form.slug) }}</small>
field_id(form_view)
Returns the id HTML attribute value for the field (e.g., user_email).
1 2
<label for="{{ field_id(form.email) }}">Your Email</label>
<input type="email" id="{{ field_id(form.email) }}" name="{{ field_name(form.email) }}">
field_label(form_view)
Returns the label text for the field.
1
<span class="floating-label">{{ field_label(form.username) }}</span>
field_name(form_view)
Returns the name HTML attribute value for the field (e.g., user[email]).
1
<input type="hidden" name="{{ field_name(form.token) }}" value="...">
field_value(form_view)
Returns the current value of the field.
1 2
{# pre-fill a custom input with the form field's value #}
<input type="range" name="{{ field_name(form.rating) }}" value="{{ field_value(form.rating) }}">