In Django, forms play a vital role in capturing and processing user input. Whether it’s a registration form, contact form, or feedback page, well-designed forms are critical to both user experience and data integrity. While Django’s default form rendering works fine functionally, the default appearance is often too plain for production use.
In this post, we’ll explore in detail how to style and customize Django forms to make them more visually appealing, user-friendly, and consistent with modern web design standards. We’ll discuss how to use widgets, custom attributes, and third-party libraries like Django Crispy Forms to achieve beautiful, responsive form layouts.
By the end of this tutorial, you’ll be able to style your Django forms professionally while keeping your code clean, maintainable, and efficient.
1. Why Form Styling Matters
A well-styled form is more than just aesthetics—it’s about improving user interaction, readability, and accessibility.
Here are a few reasons why form styling is essential:
- User Experience: Attractive, consistent form design encourages users to fill out fields correctly.
- Clarity: Proper spacing, labels, and placeholder text help users understand what’s required.
- Accessibility: Screen readers and keyboard users benefit from properly styled and labeled input fields.
- Consistency: Styled forms integrate seamlessly with the rest of your website design.
By default, Django’s forms output minimal HTML with no classes or styling. It’s up to you to add CSS, JavaScript, and widgets to enhance the look and feel.
2. Setting Up the Django Project
Let’s create a small Django project to demonstrate different styling methods.
Step 1: Create a Project
django-admin startproject formstyleproject
cd formstyleproject
Step 2: Create an App
python manage.py startapp users
Step 3: Register the App
In formstyleproject/settings.py
, add 'users'
to the INSTALLED_APPS
list:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users',
]
Now we can define our form.
3. Creating a Basic Form
Let’s create a simple user registration form using Django’s forms
module.
In users/forms.py
, write:
from django import forms
class RegistrationForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
This creates a simple form with three fields: name, email, and password.
By default, Django renders it in plain HTML when used in a template, like this:
<p>
<label for="id_name">Name:</label>
<input type="text" name="name" maxlength="100" required id="id_name">
</p>
It works, but it’s not styled and doesn’t look modern. Let’s fix that.
4. Using Widgets to Customize Form Fields
Django provides widgets — Python classes that define how form fields are rendered into HTML. You can customize widgets using the widget
parameter.
For example:
name = forms.CharField(
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter your name'})
)
The attrs
dictionary allows you to add HTML attributes such as:
class
(for CSS frameworks like Bootstrap)placeholder
id
style
readonly
ordisabled
Let’s update our registration form with styled widgets.
5. Adding Bootstrap Styles Using Widgets
Bootstrap is a popular CSS framework for building responsive websites. It provides pre-styled components like buttons, inputs, and alerts.
To include Bootstrap, first add its CDN link in your base HTML template:
<!-- base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Django Form Styling{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
{% block content %}{% endblock %}
</div>
</body>
</html>
Now modify your form in forms.py
:
class RegistrationForm(forms.Form):
name = forms.CharField(
label='Full Name',
max_length=100,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your full name'
})
)
email = forms.EmailField(
label='Email Address',
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your email address'
})
)
password = forms.CharField(
label='Password',
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your password'
})
)
Each field now has Bootstrap’s form-control
class, which automatically adds padding, border radius, and spacing for a clean design.
6. Creating the View
In users/views.py
, create a view to render the form:
from django.shortcuts import render
from .forms import RegistrationForm
def register(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
email = form.cleaned_data['email']
password = form.cleaned_data['password']
print(f"Registered user: {name} ({email})")
return render(request, 'users/success.html', {'name': name})
else:
form = RegistrationForm()
return render(request, 'users/register.html', {'form': form})
This view handles both GET and POST requests — displaying the form and processing the submission.
7. Defining URLs
Create users/urls.py
:
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register, name='register'),
]
And include it in the project’s main urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('users.urls')),
]
8. Creating Templates
Create a folder templates/users/
and add register.html
:
{% extends 'base.html' %}
{% block title %}Register{% endblock %}
{% block content %}
<h2>User Registration</h2>
<form method="post" class="mt-3">
{% csrf_token %}
<div class="mb-3">
{{ form.name.label_tag }}
{{ form.name }}
</div>
<div class="mb-3">
{{ form.email.label_tag }}
{{ form.email }}
</div>
<div class="mb-3">
{{ form.password.label_tag }}
{{ form.password }}
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
{% endblock %}
The form will now render beautifully using Bootstrap’s styling.
9. Rendering Forms Automatically with {{ form.as_p }}
For quick prototyping, you can render the entire form with:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-success">Submit</button>
</form>
The as_p
method wraps each field in a paragraph tag.
Other rendering methods include:
{{ form.as_table }}
— renders as table rows{{ form.as_ul }}
— renders as list items
However, for production, manual control using Bootstrap div
containers provides better customization.
10. Adding Help Text and Error Messages
You can guide users by providing help text in your form definition:
email = forms.EmailField(
help_text='We will never share your email with anyone else.',
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
In your template, display help text:
<div class="form-text">{{ form.email.help_text }}</div>
For displaying errors, Django automatically passes them in form.errors
. Example:
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">{{ error }}</div>
{% endfor %}
{% endfor %}
This ensures users know what needs correction.
11. Using Custom CSS for Fine-Tuned Styling
If you don’t want to rely entirely on Bootstrap, you can write your own CSS.
In your static directory, create a css/style.css
file:
input, select, textarea {
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
width: 100%;
}
label {
font-weight: bold;
margin-top: 10px;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
Link it in base.html
:
<link rel="stylesheet" href="{% static 'css/style.css' %}">
This gives you complete control over your design.
12. Adding Placeholders, IDs, and Custom Attributes
The attrs
argument in widgets allows you to enhance input behavior.
Example:
phone = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., +1 123 456 7890',
'id': 'phone-input',
'maxlength': '15'
})
)
You can even add JavaScript hooks for custom validation or interactivity.
13. Customizing Labels and Field Order
You can manually specify labels:
name = forms.CharField(label='Full Name')
And control field order by listing them in Meta.fields
or in form definition order.
If you want to dynamically reorder fields:
class RegistrationForm(forms.Form):
...
field_order = ['email', 'name', 'password']
14. Adding Required and Optional Fields
By default, all fields are required. You can make them optional by setting required=False
:
middle_name = forms.CharField(required=False)
In templates, you can visually indicate required fields using CSS or HTML attributes.
Example:
<label class="required">{{ field.label }}</label>
15. Styling Checkboxes, Radio Buttons, and Select Fields
Django widgets can render various input types.
Example of a dropdown:
GENDER_CHOICES = [
('M', 'Male'),
('F', 'Female'),
]
gender = forms.ChoiceField(
choices=GENDER_CHOICES,
widget=forms.Select(attrs={'class': 'form-select'})
)
Example of radio buttons:
course_level = forms.ChoiceField(
choices=[('beginner', 'Beginner'), ('advanced', 'Advanced')],
widget=forms.RadioSelect
)
Example of a checkbox:
agree_terms = forms.BooleanField(
label='I agree to the terms and conditions',
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
)
In your template:
<div class="form-check">
{{ form.agree_terms }}
{{ form.agree_terms.label_tag }}
</div>
This integrates naturally with Bootstrap’s form-check styling.
16. Grouping Fields with Fieldsets
You can use <fieldset>
and <legend>
in HTML to group related fields for clarity.
Example:
<form method="post">
{% csrf_token %}
<fieldset class="border p-3">
<legend>Personal Information</legend>
{{ form.name.label_tag }}
{{ form.name }}
{{ form.email.label_tag }}
{{ form.email }}
</fieldset>
<fieldset class="border p-3 mt-3">
<legend>Security</legend>
{{ form.password.label_tag }}
{{ form.password }}
</fieldset>
<button type="submit" class="btn btn-success mt-3">Submit</button>
</form>
This improves readability, especially for long forms.
17. Using Django Crispy Forms
What Is Django Crispy Forms?
Django Crispy Forms is a third-party library that makes it easy to style Django forms using CSS frameworks such as Bootstrap, Tailwind, or Foundation.
Installing Django Crispy Forms
Install via pip:
pip install django-crispy-forms
pip install crispy-bootstrap5
Then add it to your INSTALLED_APPS
in settings.py
:
INSTALLED_APPS = [
...
'crispy_forms',
'crispy_bootstrap5',
]
And set the template pack:
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
Updating the Template
Load Crispy Forms in your template:
{% load crispy_forms_tags %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
</form>
Crispy Forms automatically applies Bootstrap classes, spacing, and labels without requiring you to modify each field manually.
Example with Bootstrap 5
Your forms.py
remains unchanged:
class RegistrationForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
But in the template, using {{ form|crispy }}
will instantly render a professional-looking form.
18. Customizing Crispy Forms Layout
You can customize form layout with Crispy’s Layout
and Field
helpers.
Example:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Submit
class RegistrationForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Field('name', css_class='form-control'),
Field('email', css_class='form-control'),
Field('password', css_class='form-control'),
Submit('submit', 'Register', css_class='btn btn-primary')
)
This allows total control over the structure, styling, and button placement within your form.
19. Adding JavaScript Enhancements
Once your form is styled, you can enhance interactivity using JavaScript or libraries like jQuery.
Examples:
- Real-time validation
- Dynamic field display (e.g., showing additional fields when a user selects an option)
- Character counters for text areas
Example snippet:
document.getElementById("id_name").addEventListener("input", function() {
const length = this.value.length;
document.getElementById("nameCounter").textContent = ${length}/100 characters
;
});
In template:
<p id="nameCounter" class="text-muted">0/100 characters</p>
20. Testing and Debugging Styled Forms
When testing styled forms:
- Verify field alignment on different screen sizes.
- Ensure accessibility with screen readers.
- Check that validation messages are clearly visible.
- Confirm that required attributes appear as expected.
You can use Django’s built-in test client to verify form behavior.
Example:
from django.test import TestCase
from .forms import RegistrationForm
class RegistrationFormTest(TestCase):
def test_form_fields_have_bootstrap_class(self):
form = RegistrationForm()
self.assertIn('form-control', form.fields['name'].widget.attrs['class'])
21. Accessibility Considerations
Accessibility is crucial in modern web design. Make sure:
- Each input has a label.
- Error messages are descriptive.
- Form controls are navigable via keyboard.
- Color contrast meets accessibility standards.
Bootstrap already handles most accessibility concerns, but custom designs require testing with screen reader tools.
22. Combining Multiple Styling Techniques
You can mix several approaches:
- Use widgets for individual field customization.
- Use Crispy Forms for overall layout and consistency.
- Add your own CSS for brand-specific styling.
This hybrid approach gives both control and simplicity.
23. Final Thoughts
Styling forms in Django may initially seem like a purely visual task, but it significantly affects usability, accessibility, and user satisfaction. Django’s flexible form system, combined with widgets, attributes, and third-party libraries, makes it easy to create forms that not only work perfectly but also look professional.
In this tutorial, we covered:
- How to use widgets to add classes and placeholders.
- How to integrate Bootstrap for responsive design.
- How to add help text, error handling, and custom CSS.
- How to use Django Crispy Forms for advanced styling.
- Accessibility and testing tips for production-ready forms.
Leave a Reply