When working with Django forms, one of the first things you'll want to do is customize how your form fields look and behave. Whether it's adding CSS classes, placeholders, or changing the type of input, Django gives you multiple ways to tweak widgets.

In this article, we'll walk through how to customize widgets in two scenarios:

  • A plain vanilla form (based on forms.Form)
  • A model form (based on forms.ModelForm)

Let’s dive in. 🏊‍♂️

1. Customizing Widgets in Vanilla Forms (forms.Form)

We have this contact form.

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField()

Options:

  • Option 1: define the Widget Inline. You set the widget right inside the field definition.
  • Option 2: modify a widget in the form definition.
  • Option 3: modify Widgets in __init__. If you need conditional logic (based on a user or some external flag), you can update the widget after instantiation.

Let's see all options in one form example:

# forms.py

class ContactForm(forms.Form):
    subject = forms.CharField(
        max_length=100,
        # *** CUSTOMIZE WIDGET: option 1 ***
        widget=forms.TextInput(
            attrs={
                "class": "form-control",
                "placeholder": "Subject",
            }
        ),
    )
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

    # *** CUSTOMIZE WIDGET: option 2 ***
    message.widget.attrs.update(
        {"class": "form-control", "placeholder": "Write your message here"}
    )

    # *** CUSTOMIZE WIDGET: option 3 ***
    def __init__(self, *args, user=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["sender"].widget.attrs["class"] = "form-control"

        # Conditional logic based on user
        if user and user.is_authenticated:
            self.fields["sender"].initial = user.email
        else:
            print(user)
            self.fields["sender"].widget.attrs["readonly"] = True
            self.fields["sender"].widget.attrs[
                "placeholder"
            ] = "Login to fill your email"

The user is passed into the view when instantiating the form:

#views.py

def form3(request):
    if request.method == "POST":
        form = ContactForm(request.POST)
        if form.is_valid():
            context = {"form": form}
            messages.success(request, "Your message has been sent!")
            return redirect("form")
    else:
        form = ContactForm(user=request.user)
    context = {"form": form}
    return render(request, "form.html", context=context)

2. Customizing Widgets in Model Forms (forms.ModelForm)

We have this form and model.

#forms.py
class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = "__all__"


# models.py
class Post(models.Model):
    title = models.CharField()
    title_tag = models.CharField()
    body = models.TextField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)

You can customize the widget like this:

Options:

  • Option 1: override fields manually
  • Option 2: use widgets inside the Meta class
  • Option 3: modify Widgets in __init__. If you need conditional logic (based on a user or some external flag), you can update the widget after instantiation.

Let's see all options in one form example:

class PostForm(ModelForm):
    # *** CUSTOMIZE WIDGET: option 1 ***
    title = forms.CharField(
        widget=forms.TextInput(
            attrs={"class": "form-control", "placeholder": "Here goes the title"}
        )
    )

    class Meta:
        model = Post
        fields = "__all__"
        # *** CUSTOMIZE WIDGET: option 2 ***
        widgets = {
            "body": forms.Textarea(attrs={"class": "form-control", "rows": 5}),
        }

    # *** CUSTOMIZE WIDGET: option 3 ***
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["title_tag"].widget.attrs.update(
            {"class": "form-control", "placeholder": "Title tag"}
        )

Now you are free to customize the widgets in your project!