diff --git a/_build/redirection_map b/_build/redirection_map index c02a690e0f2..5bc5ef3a0df 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -403,3 +403,5 @@ /frontend/encore/installation-no-flex /frontend/encore/installation /http_cache/form_csrf_caching /security/csrf /console/logging /console +/reference/forms/twig_reference /form/form_customization +/form/rendering /form/form_customization diff --git a/_images/form/form-field-parts.svg b/_images/form/form-field-parts.svg new file mode 100644 index 00000000000..c9856c89a99 --- /dev/null +++ b/_images/form/form-field-parts.svg @@ -0,0 +1 @@ + diff --git a/_images/sources/form/form-field-parts.dia b/_images/sources/form/form-field-parts.dia new file mode 100644 index 00000000000..d6ed2dfc3fe Binary files /dev/null and b/_images/sources/form/form-field-parts.dia differ diff --git a/components/form.rst b/components/form.rst index 4c5431a92aa..29c21c6944c 100644 --- a/components/form.rst +++ b/components/form.rst @@ -177,8 +177,9 @@ between Twig and several Symfony components: $ composer require symfony/twig-bridge -The TwigBridge integration provides you with several :doc:`Twig Functions ` -that help you render the HTML widget, label and error for each field +The TwigBridge integration provides you with several +:ref:`Twig Functions ` +that help you render the HTML widget, label, help and errors for each field (as well as a few other things). To configure the integration, you'll need to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`:: @@ -231,7 +232,7 @@ The exact details of your `Twig Configuration`_ will vary, but the goal is always to add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension` to Twig, which gives you access to the Twig functions for rendering forms. To do this, you first need to create a :class:`Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine`, -where you define your :ref:`form themes ` +where you define your :doc:`form themes ` (i.e. resources/files that define form HTML markup). For general details on rendering forms, see :doc:`/form/form_customization`. @@ -510,8 +511,8 @@ Rendering the Form Now that the form has been created, the next step is to render it. This is done by passing a special form "view" object to your template (notice the -``$form->createView()`` in the controller above) and using a set of form -helper functions: +``$form->createView()`` in the controller above) and using a set of +:ref:`form helper functions `: .. code-block:: html+twig @@ -528,7 +529,7 @@ That's it! By printing ``form_widget(form)``, each field in the form is rendered, along with a label and error message (if there is one). While this is convenient, it's not very flexible (yet). Usually, you'll want to render each form field individually so you can control how the form looks. You'll learn how -to do that in the ":doc:`/form/rendering`" section. +to do that in the :doc:`form customization ` article. Changing a Form's Method and Action ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 7483d4b174c..cbe88040737 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -90,8 +90,9 @@ shipping options. Creating a Template for the Field --------------------------------- -Each field type is rendered by a template fragment, which is determined in part by -the class name of your type. For more information, see :ref:`form-customization-form-themes`. +Each field type is rendered by a template fragment whose name is determined in +part by the class name of your type. Read the :ref:`from fragment naming ` +rules for more details. .. note:: @@ -147,8 +148,8 @@ link for details), create a ``shipping_widget`` block to handle this: .. note:: Make sure the correct widget prefix is used. In this example the name should - be ``shipping_widget`` (see :ref:`form-customization-form-themes`). - Further, the main config file should point to the custom form template + be ``shipping_widget`` (see :ref:`form fragment naming ` + rules). Further, the main config file should point to the custom form template so that it's used when rendering all forms. When using Twig this is: diff --git a/form/create_form_type_extension.rst b/form/create_form_type_extension.rst index f2f1c235abd..2bd6bd86980 100644 --- a/form/create_form_type_extension.rst +++ b/form/create_form_type_extension.rst @@ -210,7 +210,7 @@ Override the File Widget Template Fragment Each field type is rendered by a template fragment. Those template fragments can be overridden in order to customize form rendering. For more information, -you can refer to the :ref:`form-customization-form-themes` article. +you can refer to the :ref:`form fragment naming ` rules. In your extension class, you added a new variable (``image_url``), but you still need to take advantage of this new variable in your templates. diff --git a/form/form_customization.rst b/form/form_customization.rst index b1e02f9e4f0..0aaa095ae6c 100644 --- a/form/form_customization.rst +++ b/form/form_customization.rst @@ -4,859 +4,418 @@ How to Customize Form Rendering =============================== -Symfony gives you a wide variety of ways to customize how a form is rendered. -In this guide, you'll learn how to customize every possible part of your -form with as little effort as possible whether you use Twig or PHP as your -templating engine. +Symfony gives you several ways to customize how a form is rendered. In this +article you'll learn how to make single customizations to one or more fields of +your forms. If you need to customize all your forms in the same way, create +instead a :doc:`form theme ` or use any of the built-in +themes, such as the :doc:`Bootstrap theme for Symfony forms `. -Form Rendering Basics ---------------------- +.. _form-rendering-basics: -Recall that the label, error and HTML widget of a form field can -be rendered by using the ``form_row()`` Twig function or the ``row`` PHP helper -method: +Form Rendering Functions +------------------------ -.. code-block:: twig - - {{ form_row(form.age) }} - -You can also render each of the four parts of the field individually: - -.. code-block:: html+twig - -
- {{ form_label(form.age) }} - {{ form_errors(form.age) }} - {{ form_widget(form.age) }} - {{ form_help(form.age) }} -
- -In both cases, the form label, errors and HTML widget are rendered by using -a set of markup that ships standard with Symfony. For example, both of the -above templates would render: - -.. code-block:: html - -
- -
    -
  • This field is required
  • -
- -
- -To quickly prototype and test a form, you can render the entire form with -just one line: +A single call to the :ref:`form() Twig function ` is +enough to render an entire form, including all its fields and error messages: .. code-block:: twig - {# renders all fields #} - {{ form_widget(form) }} - - {# renders all fields *and* the form start and end tags #} + {# form is a variable passed from the controller and created + by calling to the $form->createView() method #} {{ form(form) }} -The remainder of this recipe will explain how every part of the form's markup -can be modified at several different levels. For more information about form -rendering in general, see :doc:`/form/rendering`. - -.. _form-customization-form-themes: - -What are Form Themes? ---------------------- - -Symfony uses form fragments - a small piece of a template that renders just -one part of a form - to render each part of a form - field labels, errors, -``input`` text fields, ``select`` tags, etc. - -The fragments are defined as blocks in Twig and as template files in PHP. - -A *theme* is nothing more than a set of fragments that you want to use when -rendering a form. In other words, if you want to customize one portion of -how a form is rendered, you'll import a *theme* which contains a customization -of the appropriate form fragments. - -Symfony comes with some **built-in form themes** that define each and every -fragment needed to render every part of a form: - -* `form_div_layout.html.twig`_, wraps each form field inside a ``
`` element. -* `form_table_layout.html.twig`_, wraps the entire form inside a ```` - element and each form field inside a ```` element. -* `bootstrap_3_layout.html.twig`_, wraps each form field inside a ``
`` element - with the appropriate CSS classes to apply the default `Bootstrap 3 CSS framework`_ - styles. -* `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 ``
`` element - with the appropriate CSS classes to apply the default `Foundation CSS framework`_ - styles. - -.. caution:: - - When you use the Bootstrap form themes and render the fields manually, - calling ``form_label()`` for a checkbox/radio field doesn't show anything. - Due to Bootstrap internals, the label is already shown by ``form_widget()``. - -.. tip:: - - Read more about the :doc:`Bootstrap 4 form theme `. - -In the next section you will learn how to customize a theme by overriding -some or all of its fragments. - -For example, when the widget of an ``integer`` type field is rendered, an ``input`` -``number`` field is generated - -.. code-block:: html+twig - - {{ form_widget(form.age) }} - -renders: - -.. code-block:: html - - - -Internally, Symfony uses the ``integer_widget`` fragment to render the field. -This is because the field type is ``integer`` and you're rendering its ``widget`` -(as opposed to its ``label`` or ``errors``). - -In Twig that would default to the block ``integer_widget`` from the `form_div_layout.html.twig`_ -template. - -In PHP it would rather be the ``integer_widget.html.php`` file located in -the ``FrameworkBundle/Resources/views/Form`` folder. - -The default implementation of the ``integer_widget`` fragment looks like this: - -.. code-block:: twig - - {# form_div_layout.html.twig #} - {% block integer_widget %} - {% set type = type|default('number') %} - {{ block('form_widget_simple') }} - {% endblock integer_widget %} - -As you can see, this fragment itself renders another fragment - ``form_widget_simple``: - -.. code-block:: html+twig - - {# form_div_layout.html.twig #} - {% block form_widget_simple %} - {% set type = type|default('text') %} - - {% endblock form_widget_simple %} - -The point is, the fragments dictate the HTML output of each part of a form. To -customize the form output, you need to identify and override the correct -fragment. A set of these form fragment customizations is known as a form "theme". -When rendering a form, you can choose which form theme(s) you want to apply. - -In Twig a theme is a single template file and the fragments are the blocks defined -in this file. - -In PHP a theme is a folder and the fragments are individual template files in -this folder. - -.. _form-customization-sidebar: - -.. sidebar:: Knowing which Block to Customize - - In this example, the customized fragment name is ``integer_widget`` because - you want to override the HTML ``widget`` for all ``integer`` field types. If - you need to customize ``textarea`` fields, you would customize ``textarea_widget``. - - The ``integer`` part comes from the class name: ``IntegerType`` becomes ``integer``, - based on a standard. - - As you can see, the fragment name is a combination of the field type and - which part of the field is being rendered (e.g. ``widget``, ``label``, - ``errors``, ``row``). For example, to change how errors are rendered specifically - for input fields of type ``text``, you would customize the ``text_errors`` fragment. - - More commonly, however, you'll want to customize how errors are displayed - across *all* fields. You can do this by customizing the ``form_errors`` - fragment. This takes advantage of field type inheritance. Specifically, - since the ``text`` type extends from the ``form`` type, the Form component - will first look for the type-specific fragment (e.g. ``text_errors``) before - falling back to its parent fragment name if it doesn't exist (e.g. ``form_errors``). - - For more information on this topic, see :ref:`form-template-blocks`. - -.. _form-theming-methods: - -Form Theming ------------- - -To see the power of form theming, suppose you want to wrap every input ``number`` -field with a ``div`` tag. The key to doing this is to customize the -``integer_widget`` fragment. - -Form Theming in Twig --------------------- - -When customizing the form field block in Twig, you have two options on *where* -the customized form block can live: - -+--------------------------------------+-----------------------------------+-------------------------------------------+ -| Method | Pros | Cons | -+======================================+===================================+===========================================+ -| Inside the same template as the form | Quick and easy | Can't be reused in other templates | -+--------------------------------------+-----------------------------------+-------------------------------------------+ -| Inside a separate template | Can be reused by many templates | Requires an extra template to be created | -+--------------------------------------+-----------------------------------+-------------------------------------------+ - -Both methods have the same effect but are better in different situations. - -Method 1: Inside the same Template as the Form -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The easiest way to customize the ``integer_widget`` block is to customize it -directly in the template that's actually rendering the form. +The next step is to use the :ref:`form_start() `, +:ref:`form_end() `, +:ref:`form_errors() ` and +:ref:`form_row() ` Twig functions to render the +different form parts so you can customize them adding HTML elements and attributes: .. code-block:: html+twig - {% extends 'base.html.twig' %} - - {% form_theme form _self %} - - {% block integer_widget %} -
- {% set type = type|default('number') %} - {{ block('form_widget_simple') }} + {{ form_start(form) }} +
+ {{ form_errors(form) }}
- {% endblock %} - - {% block content %} - {# ... render the form #} - - {{ form_row(form.age) }} - {% endblock %} -By using the special ``{% form_theme form _self %}`` tag, Twig looks inside -the same template for any overridden form blocks. Assuming the ``form.age`` -field is an ``integer`` type field, when its widget is rendered, the customized -``integer_widget`` block will be used. - -The disadvantage of this method is that the customized form block can't be -reused when rendering other forms in other templates. In other words, this method -is most useful when making form customizations that are specific to a single -form in your application. If you want to reuse a form customization across -several (or all) forms in your application, read on to the next section. - -Method 2: Inside a separate Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also choose to put the customized ``integer_widget`` form block in a -separate template entirely. The code and end-result are the same, but you -can now re-use the form customization across many templates: - -.. code-block:: html+twig - - {# templates/form/fields.html.twig #} - {% block integer_widget %} -
- {% set type = type|default('number') %} - {{ block('form_widget_simple') }} +
+
+ {{ form_row(form.task) }} +
+
+ {{ form_row(form.dueDate) }} +
- {% endblock %} - -Now that you've created the customized form block, you need to tell Symfony -to use it. Inside the template where you're actually rendering your form, -tell Symfony to use the template via the ``form_theme`` tag: - -.. code-block:: html+twig - - {% form_theme form 'form/fields.html.twig' %} - - {{ form_widget(form.age) }} + {{ form_end(form) }} -When the ``form.age`` widget is rendered, Symfony will use the ``integer_widget`` -block from the new template and the ``input`` tag will be wrapped in the -``div`` element specified in the customized block. +The ``form_row()`` function outputs the entire field contents, including the +label, help message, HTML elements and error messages. All this can be further +customized using other Twig functions, as illustrated in the following diagram: -Multiple Templates -.................. +.. raw:: html -A form can also be customized by applying several templates. To do this, pass the -name of all the templates as an array using the ``with`` keyword: + -.. code-block:: html+twig - - {% form_theme form with ['common.html.twig', 'form/fields.html.twig'] %} - - {# ... #} - -The templates can also be located in different bundles, use the Twig namespaced -path to reference these templates, e.g. ``@AcmeFormExtra/form/fields.html.twig``. - -Disabling usage of globally defined themes -.......................................... - -Sometimes you may want to disable the use of the globally defined form themes in order -to have more control over rendering of a form. You might want this, for example, -when creating an admin interface for a bundle which can be installed on a wide range -of Symfony apps (and so you can't control what themes are defined globally). - -You can do this by including the ``only`` keyword after the list form themes: +The :ref:`form_label() `, +:ref:`form_widget() `, +:ref:`form_help() ` and +:ref:`form_errors() ` Twig functions give you total +control over how each form field is rendered, so you can fully customize them: .. code-block:: html+twig - {% form_theme form with ['common.html.twig', 'form/fields.html.twig'] only %} - - {# ... #} - -.. caution:: +
+ {{ form_label(form.dueDate) }} + {{ form_widget(form.dueDate) }} - 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. + {{ form_help(form.dueDate) }} - .. code-block:: html+twig +
+ {{ form_errors(form.dueDate) }} +
+
- {# templates/form/common.html.twig #} - {% use "form_div_layout.html.twig" %} +.. note:: - {# ... #} + Later in this article you can find the full reference of these Twig + functions with more usage examples. -Child Forms -........... +Form Rendering Variables +------------------------ -You can also apply a form theme to a specific child of your form: +Some of the Twig functions mentioned in the previous section allow to pass +variables to configure their behavior. For example, the ``form_label()`` +function lets you define a custom label to override the one defined in the form: .. code-block:: html+twig - {% form_theme form.a_child_form 'form/fields.html.twig' %} + {{ form_label(form.task, 'My Custom Task Label') }} -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: +Some :doc:`form field types ` have additional rendering +options that can be passed to the widget. These options are documented with each +type, but one common option is ``attr``, which allows you to modify HTML +attributes on the form element. The following would add the ``task_field`` CSS +class to the rendered input text field: .. code-block:: html+twig - {% form_theme form 'form/fields.html.twig' %} - - {% form_theme form.a_child_form 'form/fields_child.html.twig' %} - -.. _referencing-base-form-blocks-twig-specific: - -Referencing base Form Blocks ----------------------------- - -So far, to override a particular form block, the best method is to copy -the default block from `form_div_layout.html.twig`_, paste it into a different template, -and then customize it. In many cases, you can avoid doing this by referencing -the base block when customizing it. - -This is not a lot of work, but varies slightly depending on if your form block customizations -are in the same template as the form or a separate template. + {{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }} -Referencing Blocks from inside the same Template as the Form -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: -Import the blocks by adding a ``use`` tag in the template where you're rendering -the form: + If you're rendering an entire form at once (or an entire embedded form), + the ``variables`` argument will only be applied to the form itself and + not its children. In other words, the following will **not** pass a + "foo" class attribute to all of the child fields in the form: -.. code-block:: twig + .. code-block:: twig - {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %} + {# does **not** work - the variables are not recursive #} + {{ form_widget(form, { 'attr': {'class': 'foo'} }) }} -Now, when the blocks from `form_div_layout.html.twig`_ are imported, the -``integer_widget`` block is called ``base_integer_widget``. This means that when -you redefine the ``integer_widget`` block, you can reference the default markup -via ``base_integer_widget``: +If you need to render form fields "by hand" then you can access individual +values for fields (such as the ``id``, ``name`` and ``label``) using its +``vars`` property. For example to get the ``id``: .. code-block:: html+twig - {% block integer_widget %} -
- {{ block('base_integer_widget') }} -
- {% endblock %} - -Referencing base Blocks from an external Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If your form customizations live inside an external template, you can reference -the base block by using the ``parent()`` Twig function: - -.. code-block:: html+twig - - {# templates/form/fields.html.twig #} - {% extends 'form_div_layout.html.twig' %} - - {% block integer_widget %} -
- {{ parent() }} -
- {% endblock %} + {{ form.task.vars.id }} .. note:: - It is not possible to reference the base block when using PHP as the - templating engine. You have to manually copy the content from the base block - to your new template file. - -.. _twig: - -Making Application-wide Customizations --------------------------------------- - -If you'd like a certain form customization to be global to your application, -you can accomplish this by making the form customizations in an external -template and then importing it inside your application configuration. - -By using the following configuration, any customized form blocks inside the -``form/fields.html.twig`` template will be used globally when a form is -rendered. - -.. configuration-block:: - - .. code-block:: yaml + Later in this article you can find the full reference of these Twig + variables and their description. - # config/packages/twig.yaml - twig: - form_themes: - - 'form/fields.html.twig' - # ... +Form Themes +----------- - .. code-block:: xml +The Twig functions and variables shown in the previous sections can help you +customize one or more fields of your forms. However, this customization can't +be applied to the rest of the forms of your app. - - - +If you want to customize all forms in the same way (for example to adapt the +generated HTML code to the CSS framework used in your app) you must create a +:doc:`form theme `. - - form/fields.html.twig - - - +.. _reference-form-twig-functions-variables: - .. code-block:: php - - // config/packages/twig.php - $container->loadFromExtension('twig', [ - 'form_themes' => [ - 'form/fields.html.twig', - ], - - // ... - ]); - -By default, Twig uses a *div* layout when rendering forms. Some people, however, -may prefer to render forms in a *table* layout. Use the ``form_table_layout.html.twig`` -resource to use such a layout: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/twig.yaml - twig: - form_themes: - - 'form_table_layout.html.twig' - # ... - - .. code-block:: xml - - - - - - - form_table_layout.html.twig - - - - - .. code-block:: php - - // config/packages/twig.php - $container->loadFromExtension('twig', [ - 'form_themes' => [ - 'form_table_layout.html.twig', - ], - - // ... - ]); +Form Functions and Variables Reference +-------------------------------------- -If you only want to make the change in one template, add the following line to -your template file rather than adding the template as a resource: +.. _reference-form-twig-functions: -.. code-block:: html+twig +Functions +~~~~~~~~~ - {% form_theme form 'form_table_layout.html.twig' %} +.. _reference-forms-twig-form: -Note that the ``form`` variable in the above code is the form view variable -that you passed to your template. +form(form_view, variables) +.......................... -How to Customize an individual Field ------------------------------------- +Renders the HTML of a complete form. -So far, you've seen the different ways you can customize the widget output -of all text field types. You can also customize individual fields. For example, -suppose you have two ``text`` fields in a ``product`` form - ``name`` and -``description`` - but you only want to customize one of the fields. This can be -accomplished by customizing a fragment whose name is a combination of the field's -``id`` attribute and which part of the field is being customized. For example, to -customize the ``name`` field only: +.. code-block:: twig -.. code-block:: html+twig + {# render the form and change the submission method #} + {{ form(form, {'method': 'GET'}) }} - {% form_theme form _self %} +You will mostly use this helper for prototyping or if you use custom form +themes. If you need more flexibility in rendering the form, you should use +the other helpers to render individual parts of the form instead: - {% block _product_name_widget %} -
- {{ block('form_widget_simple') }} -
- {% endblock %} - - {{ form_widget(form.name) }} +.. code-block:: twig -Here, the ``_product_name_widget`` fragment defines the template to use for the -field whose *id* is ``product_name`` (and name is ``product[name]``). + {{ form_start(form) }} + {{ form_errors(form) }} -.. tip:: + {{ form_row(form.name) }} + {{ form_row(form.dueDate) }} - The ``product`` portion of the field is the form name, which may 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. + {{ form_row(form.submit, { 'label': 'Submit me' }) }} + {{ form_end(form) }} - If you want to change the ``product`` or ``name`` portion of the block - name ``_product_name_widget`` you can set the ``block_name`` option in your - form type:: +.. _reference-forms-twig-start: - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\Form\Extension\Core\Type\TextType; +form_start(form_view, variables) +................................ - public function buildForm(FormBuilderInterface $builder, array $options) - { - // ... +Renders the start tag of a form. This helper takes care of printing the +configured method and target action of the form. It will also include the +correct ``enctype`` property if the form contains upload fields. - $builder->add('name', TextType::class, [ - 'block_name' => 'custom_name', - ]); - } +.. code-block:: twig - Then the block name will be ``_product_custom_name_widget``. + {# render the start tag and change the submission method #} + {{ form_start(form, {'method': 'GET'}) }} -You can also override the markup for an entire field row using the same method: +.. _reference-forms-twig-end: -.. code-block:: html+twig +form_end(form_view, variables) +.............................. - {% form_theme form _self %} +Renders the end tag of a form. - {% block _product_name_row %} -
- {{ form_label(form) }} - {{ form_errors(form) }} - {{ form_widget(form) }} - {{ form_help(form) }} -
- {% endblock %} - - {{ form_row(form.name) }} +.. code-block:: twig -.. _form-custom-prototype: + {{ form_end(form) }} -How to Customize a Collection Prototype ---------------------------------------- +This helper also outputs ``form_rest()`` (which is explained later in this +article) unless you set ``render_rest`` to false: -When using a :doc:`collection of forms `, -the prototype can be overridden with a completely custom prototype by -overriding a block. For example, if your form field is named ``tasks``, you -will be able to change the widget for each task as follows: +.. code-block:: twig -.. code-block:: html+twig + {# don't render unrendered fields #} + {{ form_end(form, {'render_rest': false}) }} - {% form_theme form _self %} +.. _reference-forms-twig-label: - {% block _tasks_entry_widget %} -
- - - - {% endblock %} +form_label(form_view, label, variables) +....................................... -Not only can you override the rendered widget, but you can also change the -complete form row or the label as well. For the ``tasks`` field given above, -the block names would be the following: +Renders the label for the given field. You can optionally pass the specific +label you want to display as the second argument. -================ ======================= -Part of the Form Block Name -================ ======================= -``label`` ``_tasks_entry_label`` -``widget`` ``_tasks_entry_widget`` -``row`` ``_tasks_entry_row`` -================ ======================= +.. code-block:: twig -Other common Customizations ---------------------------- + {{ form_label(form.name) }} -So far, this recipe has shown you several different ways to customize a single -piece of how a form is rendered. The key is to customize a specific fragment that -corresponds to the portion of the form you want to control (see -:ref:`naming form blocks `). + {# The two following syntaxes are equivalent #} + {{ form_label(form.name, 'Your Name', {'label_attr': {'class': 'foo'}}) }} -In the next sections, you'll see how you can make several common form customizations. -To apply these customizations, use one of the methods described in the -:ref:`form-theming-methods` section. + {{ form_label(form.name, null, { + 'label': 'Your name', + 'label_attr': {'class': 'foo'} + }) }} -Customizing Error Output -~~~~~~~~~~~~~~~~~~~~~~~~ +See ":ref:`twig-reference-form-variables`" to learn about the ``variables`` +argument. -.. note:: +.. _reference-forms-twig-help: - The Form component only handles *how* the validation errors are rendered, - and not the actual validation error messages. The error messages themselves - are determined by the validation constraints you apply to your objects. - For more information, see the article on :doc:`validation `. +form_help(form_view) +.................... -There are many different ways to customize how errors are rendered when a -form is submitted with errors. The error messages for a field are rendered -when you use the ``form_errors()`` helper: +Renders the help text for the given field. .. code-block:: twig - {{ form_errors(form.age) }} - -By default, the errors are rendered inside an unordered list: - -.. code-block:: html + {{ form_help(form.name) }} -
    -
  • This field is required
  • -
+.. _reference-forms-twig-errors: -To override how errors are rendered for *all* fields, copy, paste -and customize the ``form_errors`` fragment. +form_errors(form_view) +...................... -.. code-block:: html+twig - - {% form_theme form _self %} - - {# form_errors.html.twig #} - {% block form_errors %} - {% spaceless %} - {% if errors|length > 0 %} -
    - {% for error in errors %} -
  • {{ error.message }}
  • - {% endfor %} -
- {% endif %} - {% endspaceless %} - {% endblock form_errors %} +Renders any errors for the given field. -.. tip:: +.. code-block:: twig - See :ref:`form-theming-methods` for how to apply this customization. + {# render only the error messages related to this field #} + {{ form_errors(form.name) }} -You can also customize the error output for just one specific field type. -To customize *only* the markup used for these errors, follow the same directions -as above but put the contents in a relative ``_errors`` block (or file in case -of PHP templates). For example: ``text_errors`` (or ``text_errors.html.php``). + {# render any "global" errors not associated to any form field #} + {{ form_errors(form) }} -.. tip:: +.. _reference-forms-twig-widget: - See :ref:`form-template-blocks` to find out which specific block or file you - have to customize. +form_widget(form_view, variables) +................................. -Certain errors that are more global to your form (i.e. not specific to just one -field) are rendered separately, usually at the top of your form: +Renders the HTML widget of a given field. If you apply this to an entire +form or collection of fields, each underlying form row will be rendered. .. code-block:: twig - {{ form_errors(form) }} + {# render a widget, but add a "foo" class to it #} + {{ form_widget(form.name, {'attr': {'class': 'foo'}}) }} -To customize *only* the markup used for these errors, follow the same directions -as above, but now check if the ``compound`` variable is set to ``true``. 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. +The second argument to ``form_widget()`` is an array of variables. The most +common variable is ``attr``, which is an array of HTML attributes to apply +to the HTML widget. In some cases, certain types also have other template-related +options that can be passed. These are discussed on a type-by-type basis. +The ``attributes`` are not applied recursively to child fields if you're +rendering many fields at once (e.g. ``form_widget(form)``). -.. code-block:: html+twig +See ":ref:`twig-reference-form-variables`" to learn more about the ``variables`` +argument. - {% form_theme form _self %} - - {# form_errors.html.twig #} - {% block form_errors %} - {% spaceless %} - {% if errors|length > 0 %} - {% if compound %} -
    - {% for error in errors %} -
  • {{ error.message }}
  • - {% endfor %} -
- {% else %} - {# ... display the errors for a single field #} - {% endif %} - {% endif %} - {% endspaceless %} - {% endblock form_errors %} - -Customizing the "Form Row" -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you can manage it, the easiest way to render a form field is via the -``form_row()`` function, which renders the label, errors and HTML widget of -a field. To customize the markup used for rendering *all* form field rows, -override the ``form_row`` fragment. For example, suppose you want to add a -class to the ``div`` element around each row: +.. _reference-forms-twig-row: -.. code-block:: html+twig +form_row(form_view, variables) +.............................. - {# form_row.html.twig #} - {% block form_row %} -
- {{ form_label(form) }} - {{ form_errors(form) }} - {{ form_widget(form) }} - {{ form_help(form) }} -
- {% endblock form_row %} +Renders the "row" of a given field, which is the combination of the field's +label, errors, help and widget. -.. tip:: +.. code-block:: twig - See :ref:`form-theming-methods` for how to apply this customization. + {# render a field row, but display a label with text "foo" #} + {{ form_row(form.name, {'label': 'foo'}) }} -Adding a "Required" Asterisk to Field Labels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The second argument to ``form_row()`` is an array of variables. The templates +provided in Symfony only allow to override the label as shown in the example +above. -If you want to denote all of your required fields with a required asterisk (``*``), -you can do this by customizing the ``form_label`` fragment. +See ":ref:`twig-reference-form-variables`" to learn about the ``variables`` +argument. -If you're making the form customization inside the same template as your -form, modify the ``use`` tag and add the following: +.. _reference-forms-twig-rest: -.. code-block:: html+twig +form_rest(form_view, variables) +............................... - {% use 'form_div_layout.html.twig' with form_label as base_form_label %} +This renders all fields that have not yet been rendered for the given form. +It's a good idea to always have this somewhere inside your form as it'll +render hidden fields for you and make any fields you forgot to render more +obvious (since it'll render the field for you). - {% block form_label %} - {{ block('base_form_label') }} +.. code-block:: twig - {% if label is not same as(false) and required %} - * - {% endif %} - {% endblock %} + {{ form_rest(form) }} -If you're making the form customization inside a separate template, use -the following: +Tests +~~~~~ -.. code-block:: html+twig +Tests can be executed by using the ``is`` operator in Twig to create a +condition. Read `the Twig documentation`_ for more information. - {% extends 'form_div_layout.html.twig' %} +.. _form-twig-selectedchoice: - {% block form_label %} - {{ parent() }} +selectedchoice(selected_value) +.............................. - {% if label is not same as(false) and required %} - * - {% endif %} - {% endblock %} +This test will check if the current choice is equal to the ``selected_value`` +or if the current choice is in the array (when ``selected_value`` is an +array). -.. tip:: +.. code-block:: twig - See :ref:`form-theming-methods` for how to apply this customization. +
{{ form_widget(form.task) }}{{ form_widget(form.dueDate) }}
`` + element and each form field inside a ```` element. +* `bootstrap_3_layout.html.twig`_, wraps each form field inside a ``
`` + 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 ``
`` + element with the appropriate CSS classes to apply the default styles of the + `Foundation CSS framework`_. -Symfony uses templates to render each and every part of a form, such as -``label`` tags, ``input`` tags, error messages and everything else. +.. tip:: -In Twig, each form "fragment" is represented by a Twig block. To customize -any part of how a form renders, you need to override the appropriate block. + Read the article about the :doc:`Bootstrap 4 Symfony form theme ` + to learn more about it. -In PHP, each form "fragment" is rendered via an individual template file. -To customize any part of how a form renders, override the -existing template by creating a new one. +.. _forms-theming-global: +.. _forms-theming-twig: -To understand how this works, customize the ``form_row`` fragment and -add a class attribute to the ``div`` element that surrounds each row. To -do this, create a new template file that will store the new markup: +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: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/twig.yaml + twig: + form_themes: ['bootstrap_4_horizontal_layout.html.twig'] + # ... + + .. code-block:: xml + + + + + + + bootstrap_4_horizontal_layout.html.twig + + + + + .. code-block:: 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 +:ref:`form_theme Twig tag `: .. code-block:: html+twig - {# templates/form/fields.html.twig #} - {% block form_row %} - {% spaceless %} -
- {{ form_label(form) }} - {{ form_errors(form) }} - {{ form_widget(form) }} - {{ form_help(form) }} -
- {% endspaceless %} - {% endblock form_row %} + {# 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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``form_row`` form fragment is used when rendering most fields via the -``form_row()`` function. To tell the Form component to use your new ``form_row`` -fragment defined above, add the following to the top of the template that -renders the form: +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): .. code-block:: html+twig - {# templates/default/new.html.twig #} - {% form_theme form 'form/fields.html.twig' %} + {# 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' + ] %} - {# or if you want to use multiple themes #} - {% form_theme form 'form/fields.html.twig' 'form/fields2.html.twig' %} + {# ... #} - {# ... render the form #} +Applying Different Themes to Child Forms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``form_theme`` tag (in Twig) "imports" the fragments defined in the given -template and uses them when rendering the form. In other words, when the -``form_row()`` function is called later in this template, it will use the ``form_row`` -block from your custom theme (instead of the default ``form_row`` block -that ships with Symfony). +You can also apply a form theme to a specific child of your form: -Your custom theme does not have to override all the blocks. When rendering a block -which is not overridden in your custom theme, the theming engine will fall back -to the global theme (defined at the bundle level). +.. code-block:: html+twig + + {% form_theme form.a_child_form 'form/my_custom_theme.html.twig' %} -If several custom themes are provided they will be searched in the listed order -before falling back to the global theme. +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: -To customize any portion of a form, override the appropriate -fragment. Knowing exactly which block or file to override is the subject of -the next section. +.. code-block:: html+twig -For a more extensive discussion, see :doc:`/form/form_customization`. + {% form_theme form 'form/my_custom_theme.html.twig' %} + {% form_theme form.a_child_form 'form/my_other_theme.html.twig' %} -.. index:: - single: Forms; Template fragment naming +.. _disabling-global-themes-for-single-forms: + +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 apps (and so you can't control what themes +are enabled globally). To do that, add the ``only`` keyword after the list of +form themes: + +.. code-block:: html+twig + + {% 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. + + .. code-block:: html+twig + + {# 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, +```` text fields, `` -.. _form-template-blocks: +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-customization-sidebar: +.. _form-fragment-naming: Form Fragment Naming --------------------- +~~~~~~~~~~~~~~~~~~~~ + +The naming of form fragments varies depending on your needs: -In Symfony, every part of a form that is rendered - HTML form elements, errors, -labels, etc. - is defined in a base theme, which is a collection of blocks -in Twig and a collection of template files in PHP. +* If you want to customize **all fields of the same type** (e.g. all ``