Proposal: Make it easy to add CSS classes to a `BoundField`

I wanted to add a CSS class to the boundfield wrapping the label, input field and help text, on the same element as required_css_class and error_css_class. This was previously discussed and rejected here #29189 (Allow customizing the CSS classes of the <tr>, <ul>, and <p> in Form.as_table(), as_ul(), and_p()) – Django and it was also discussed here: Add a CSS class to the DIVs of the fields of all forms - #4 by KenWhitesell

The trouble is, if I want to add this class I have to override django/forms/div.html and friends, and this template contains a lot already, see django/django/forms/templates/django/forms/div.html at main · django/django · GitHub – nothing tricky, but definitely something which has to be kept up-to-date when upgrading Django in the project. Since BoundField.css_classes exists already and is documented I have opened a ticket with a proposal to add Form.field_css_class and include that CSS class in the wrapping element if it is truthy, see #35521 (Make it easy to add CSS classes to a `BoundField`) – Django

Does anyone have any concerns or opinions on this? Thanks in advance!

Hi @matthiask.

Django’s div.html template is artificially complicated. It’s only the way it is in order to match the exact historical output along different branches, to avoid any breaking issues when moving to the template rendering.

In my own projects I simplifying it a lot. I move the actual <div> into my field.html so as to make that easier to customise (and enhance) per-field. But if I didn’t do that it would look like:

{{ errors }}
{% for field, errors in fields %}
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
    {{ field.as_field_group }}
</div>
{% endfor %}
{% for field in hidden_fields %}{{ field }}{% endfor %}

That’s a much simpler thing to override.

But, yes, looking at it, don’t you just want to add to css_classes there! I feel this should be settable per-field though (and maybe via that per form) — I wonder what the right API is for that?

@smithdc1 likely has thoughts here.

Hi Both – Good discussion!

Here are some initial thoughts.

Now that we’ve got template based rendering as well as other improvements such as the addition of as_field_group() I’ve been thinking about what else would ease form rendering. css_classes is clearly one example but there are others too. One other example is the ability to ease rendering of classes on the widget especially when they depend on state. (e.g. something like form-invalid when there are errors), another is to allow a reference to the form from the widget.

There’s different ways to solve these. Template customisation has been suggested as one, and we can of course also add additional API for each of these needs. css_classes is one proposal, here was another. Template tags (such as what’s available in django-widget-tweaks) are also an option – crispy forms also makes use of this approach.

What these API proposals generally have in common is that they are customisations to the BoundField. Django allows custom BoundFields today, but I think they are a little onerous to use. You have to override get_bound_field() for each field in your form.

Maybe we should ease customisation of BoundFields at the field/form/project level. A PR along these lines was opened recently, but has no accepted ticket.

This would allow a general approach to customisation of BoundFields to allow folk to do what they want rather than add a specific API for each use case.

2 Likes

Thanks! That does indeed solve my issue nicely. I forgot about as_field_group and I only now remember the discussion about keeping the HTML the same in the default case.

The default field template still contains a lot of useful stuff such as the fieldset and the ARIA logic, but copying that seems doesn’t feel as bad since breakage would probably be more obvious.

Overriding the BoundField sounds interesting since it could also open the door for e.g. changing the order of inputs and labels in the HTML in the case of checkboxes or other, more involved changes to the output without having to use a third party form rendering package.

But, yes, looking at it, don’t you just want to add to css_classes there! I feel this should be settable per-field though (and maybe via that per form) — I wonder what the right API is for that?

My proposed field_css_class (analogous to required_css_class etc.) doesn’t feel flexible enough when we’re talking about adding new API; overriding the BoundField class would allow third party code to add to the CSS class quite easily without having to copy-paste many Django inetrnals.

1 Like

Just to follow up, I override this in my own projects using a template partial…

{% load partials %}
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
{% partialdef field-group inline %}
    Existing field template content here. 
{% endpartialdef %}
</div>

When I override this per-field then, it ends up looking like this:

<div data-my-customisations=true>
  {% include 'django/forms/field.html#field-group; %}
</div>

… where all the work is done in the opening <div> tag, and we don’t have to rewrite the field logic each time. (A quick check once per-major Django version is sufficient to make sure we didn’t get that out of sync.)

YMMV but I’m pretty happy with it ATM.

Hi! I’m the author of the aforementionned PR about customisation of BoundFields at the field/form level. So what could be the correct process to get this accepted?