Skip to content

How to Customize Form Rendering

Edit this page

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) }}">
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version