In a Django template, what does {{ form }} output by default, and how do you render one field's label, errors and help text yourself?
answer
- a template, not Python string building
- div-based since 5.0
- a BoundField per field
- label_tag, errors, help_text
- no <form>, token or media
basics
~20 sSince Django 5.0, {{ form }} renders django/forms/div.html: top-level errors, then one <div> per visible field holding its label, help text, errors and widget. By hand, use each BoundField's label_tag, help_text, errors and the field itself.
solid answer
~40 s`{{ form }}` calls `Form.render()`, which renders the renderer's `form_template_name` — `django/forms/div.html` by default since Django 5.0. That template prints the non-field errors (plus any hidden-field errors) first, then a `<div>` per visible field containing its **field group** — label, help text, errors and widget — and slips hidden fields into the last field's `<div>`. It does not output the `<form>` tag, the submit button, the CSRF token or `{{ form.media }}`. To lay a field out yourself, `{{ form.amount }}` is a `BoundField`: `{{ form.amount.label_tag }}` gives the `<label>`, `{{ form.amount.errors }}` an error list, `{{ form.amount.help_text }}` the help text, and `{{ form.amount }}` the widget. A manual template must also render `{{ form.non_field_errors }}` and the fields in `form.hidden_fields`.
code
python · 14 linesfrom django import forms
class DonationForm(forms.Form):
required_css_class = "required"
error_css_class = "error"
amount = forms.DecimalField(min_value=1, decimal_places=2, help_text="In euros.")
frequency = forms.ChoiceField(
choices=[("once", "One-off"), ("monthly", "Monthly")],
widget=forms.RadioSelect,
)
email = forms.EmailField(help_text="We send the receipt here.")
campaign = forms.CharField(widget=forms.HiddenInput, required=False)go deeper
Know that {{ form }} renders div-based markup without the <form> tag or CSRF token, and name the BoundField pieces: label_tag, errors, help_text and the widget.
Explain that rendering goes through form_template_name on the renderer, what the div template's context holds, and what a manual template must add back, such as hidden fields and their errors.
Steer teams toward {{ form }} or as_field_group with shared templates instead of hand-written inputs, which drift from validation state and accessibility attributes.
Decide where the form markup contract lives for the whole product so every team renders fields the same way and design changes land in one template.
## What {{ form }} renders In Django, forms render themselves through **templates**. `{{ form }}` calls `Form.render()`, which looks up `Form.template_name` — by default a property returning the form renderer's `form_template_name`, which is `django/forms/div.html`. The `<div>` style became the default in Django 5.0; before that `{{ form }}` produced table rows. The div template receives a context of `form`, `fields` (visible bound fields with their errors), `hidden_fields` and `errors`, and outputs, in order: 1. The **top errors**: the form's non-field errors plus any errors on hidden fields, each prefixed with the hidden field's name. 2. For each visible field, a `<div>` whose `class` comes from `field.css_classes` and whose body is `{{ field.as_field_group }}` — label (or a `<fieldset>` and `<legend>` for multi-input widgets such as `RadioSelect`), help text, errors and widget. 3. The hidden fields, placed inside the last visible field's `<div>`. It deliberately leaves out the `<form>` element, its `method` and `enctype`, the submit button, the CSRF token and the form's `Media` assets — the page template supplies those. ## The other built-in styles | call | template | output | |---|---|---| | `{{ form }}` / `form.as_div()` | `django/forms/div.html` | a `<div>` per field, built from field groups | | `form.as_p()` | `django/forms/p.html` | a `<p>` per field | | `form.as_ul()` | `django/forms/ul.html` | `<li>` items, without the surrounding `<ul>` | | `form.as_table()` | `django/forms/table.html` | `<tr>` rows, without the surrounding `<table>` | `as_div()` is the recommended style because it groups multi-input widgets in `<fieldset>`/`<legend>`, which screen readers navigate more easily. Only the div template renders fields through `as_field_group()`, so a custom field template affects `{{ form }}` and `as_div()` but not the other three. ## Rendering fields by hand `form["amount"]` — written `{{ form.amount }}` in a template — returns a **`BoundField`**: the field plus the form's data, errors and prefix. The pieces you use most: - `{{ form.amount }}` — the widget's HTML, with `id`, `name`, value and accessibility attributes filled in. - `{{ form.amount.label_tag }}` — a `<label for="id_amount">Amount:</label>`. The `:` is the form's `label_suffix`, skipped when the label already ends in punctuation such as `?`; the `<label>` element itself is emitted only when the field has an `id`, which the default `auto_id="id_%s"` provides. - `{{ form.amount.label }}`, `{{ form.amount.help_text }}` — the plain strings. - `{{ form.amount.errors }}` — an `ErrorList` that renders as `<ul class="errorlist">`, or nothing when empty. - `{{ form.amount.id_for_label }}`, `{{ form.amount.auto_id }}`, `{{ form.amount.html_name }}`, `{{ form.amount.value }}` — for custom markup. - `{{ form.amount.css_classes }}` — the form's `error_css_class` and `required_css_class` when they apply. A manual template also has to render what `{{ form }}` did for free: - `{{ form.non_field_errors }}`, which renders with the extra class `nonfield`; - every field in `{{ form.hidden_fields }}` — and their errors, which `non_field_errors` does not include; - the visible fields, via `{% for field in form.visible_fields %}` or one by one. ## A middle road Between the whole form and hand-written markup sits `{{ form.amount.as_field_group }}` (Django 5.0+), which renders one field's label, help text, errors and widget through the field template. It lets a page arrange fields in columns without rewriting each group: ```django <form method="post"> {% csrf_token %} {{ form.non_field_errors }} <div class="row"> <div class="col">{{ form.amount.as_field_group }}</div> <div class="col">{{ form.frequency.as_field_group }}</div> </div> {{ form.email.as_field_group }} {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} <button type="submit">Donate</button> </form> ``` ## Choosing an approach - **`{{ form }}`** when the default layout is acceptable, or when a project-wide template already styles it. - **`as_field_group`** when the page needs its own arrangement but each field should look standard. - **Fully manual** only for genuinely unusual markup — and then you own labels, error display, hidden fields and accessibility attributes yourself. ## Common mistakes - **Hand-writing `<input>` tags** with `name="amount"`: the submitted value is not re-filled after a failed POST, and the `id`, `required` and `aria-*` attributes Django adds are lost. Render `{{ form.amount }}` instead. - **Forgetting `{{ form.non_field_errors }}`** in a manual template, so an error raised in `Form.clean()` never appears and the form seems to do nothing. - **Dropping hidden fields** when looping `visible_fields`, which loses their values on the next POST. - **Styling via `as_p()` or `as_table()` in new code**: those styles still work, but they skip field groups, so a project-wide field template will not reach them.
- Why might an error on a hidden field disappear when a template is converted from {{ form }} to manual rendering?`{{ form }}` adds hidden-field errors to the top error list, prefixed with the field name. `{{ form.non_field_errors }}` returns only errors not tied to a field, and `{{ hidden }}` renders just the input, so a manual template must render `hidden.errors` itself or the message is never shown.
- How do you add CSS classes to the wrapper of every required or invalid field without a custom template?Set `required_css_class` and `error_css_class` on the form class. `BoundField.css_classes()` adds them when the field is required or has errors, the default div template puts that string on each field's wrapper `<div>`, and `label_tag` adds the required class to the label.
saying these in an interview costs you the question
- {{ form }} renders the <form> tag and the CSRF token
- the default {{ form }} output is still table rows in current Django
- {{ form.non_field_errors }} also shows the errors of hidden fields
- {{ form.amount }} returns the Field object declared on the class
- {{ form }} includes the form's Media script and link tags