skip to content

Rendering & Bound Fields

Forms render through templates: the div-based default, as_field_group and field templates (5.0), and BoundField attributes like label_tag and errors. Interviewers probe custom output without raw HTML.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Django template, what does {{ form }} output by default, and how do you render one field's label, errors and help text yourself?

level: juniorimportance: must knowfreq 55%

answer

  1. a template, not Python string building
  2. div-based since 5.0
  3. a BoundField per field
  4. label_tag, errors, help_text
  5. no <form>, token or media

basics

~20 s

Since 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 lines
python
from 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

for a junior

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.

for a middle

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.

for a senior

Steer teams toward {{ form }} or as_field_group with shared templates instead of hand-written inputs, which drift from validation state and accessibility attributes.

for a principal

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
open as a page

In Django 5.0+, what does BoundField.as_field_group() render, and how do you restyle every form field on a site with one template?

level: middleimportance: should knowfreq 35%

basics

~10 s

as_field_group() renders one field's label, help text, errors and widget through a field template, django/forms/field.html by default. To restyle every field, point a custom FORM_RENDERER's field_template_name at your own template.

open as a page

In Django, what does a form or widget's inner Media class declare, and how does {{ form.media }} combine and order those assets?

level: middleimportance: should knowfreq 30%

basics

~20 s

An inner Media class lists the CSS, by medium, and JavaScript a widget or form needs. form.media merges all widgets' assets plus the form's own, dropping duplicates but keeping order; {{ form.media }} renders the tags.

open as a page

Why does a Django project's override of django/forms/field.html in its template DIRS get ignored, and what does the FORM_RENDERER setting change?

level: middleimportance: should knowfreq 30%

basics

~20 s

Django's default form renderer, DjangoTemplates, uses its own engine that loads the built-in form templates and apps' templates folders, not the project's TEMPLATES DIRS. Set FORM_RENDERER to TemplatesSetting, or a subclass, to make overrides work.

open as a page

When a team writes a custom Django field template, what must it keep so each input stays linked to its label, help text and errors?

level: seniorimportance: nice to knowfreq 16%

basics

~10 s

Keep rendering the widget with {{ field }}, labels with label_tag or legend_tag, errors with {{ field.errors }}, and give help text the id <auto_id>_helptext: Django's aria-describedby on the input points at those ids.

open as a page