Skip to main content

Render Django Form with Tailwind CSS Styles

Objectives​

  1. Render Django form with Tailwind CSS

Django App​

Let's create a turbo_drive app, we will learn how Turbo Drive works with this Django app.

(venv)$ mkdir -p ./hotwire_django_app/turbo_drive
(venv)$ python manage.py startapp turbo_drive ./hotwire_django_app/turbo_drive

We will have structure like this:

├── hotwire_django_app
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── tasks
│   ├── templates
│   ├── turbo_drive # new
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── urls.py
│   └── wsgi.py

Update hotwire_django_app/turbo_drive/apps.py to change the name to hotwire_django_app.turbo_drive

from django.apps import AppConfig


class TurboDriveConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hotwire_django_app.turbo_drive' # update

Add hotwire_django_app.turbo_drive to the INSTALLED_APPS in hotwire_django_app/settings.py

INSTALLED_APPS = [
...
'hotwire_django_app.turbo_drive', # new
]
# check if there is any error
(venv)$ ./manage.py check

System check identified no issues (0 silenced).

View​

Create hotwire_django_app/turbo_drive/views.py

from django.shortcuts import render, redirect

from hotwire_django_app.tasks.forms import TaskForm


def create_view(request):
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()

return redirect('/')
else:
form = TaskForm()

return render(request, 'turbo_drive/create.html', {'form': form})

Here we create a simple Django FBV (function-based view), user can create tasks on the form page.

Template​

Create hotwire_django_app/templates/turbo_drive/base.html

{% load webpack_loader static %}

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">

{% stylesheet_pack 'turbo_drive' %}
{% javascript_pack 'turbo_drive' attrs='defer' %}
</head>
<body>

{% block content %}
{% endblock content %}

</body>
</html>

Notes:

  1. We already created turbo_drive JS application file and CSS file in the previous chapter, we import them to the Django template here.

Create hotwire_django_app/templates/turbo_drive/create.html

{% extends "turbo_drive/base.html" %}

{% block content %}

<div class="w-full max-w-7xl mx-auto px-4">

<form method="post">
{% csrf_token %}

{{ form.as_p }}

<button type="submit" class="btn-blue">Submit</button>
</form>

</div>

{% endblock %}

URL​

Create hotwire_django_app/turbo_drive/urls.py

from django.urls import path
from .views import create_view

app_name = 'turbo-drive'

urlpatterns = [
path('create/', create_view, name='task-create'),
]

Notes:

  1. Here we use the app_name to set the namespace of the Django app.

Update hotwire_django_app/urls.py

from django.contrib import admin
from django.urls import path, include # update
from django.views.generic import TemplateView

urlpatterns = [
path('', TemplateView.as_view(template_name="index.html")),
path('turbo-drive/', include('hotwire_django_app.turbo_drive.urls')), # new
path('admin/', admin.site.urls),
]

Manual test​

# make sure 'npm run start' is running
(venv)$ python manage.py runserver

If we check on http://127.0.0.1:8000/turbo-drive/create/

As you can see, even if we import Tailwind to our Django project, the default form style still look not good.

Next, let's start improving the form style.

tailwindcss-forms​

tailwindcss/forms is a plugin that provides a basic reset for form styles that makes form elements easy to override with utilities.

$ npm install @tailwindcss/forms

In the package.json, we can see

"@tailwindcss/forms": "^0.5.3",

Update tailwind.config.js to use the plugin.

module.exports = {
//
plugins: [
require('@tailwindcss/forms'), // new
],
}

Hmm, the form style looks much better, let's keep improving it.

crispy-tailwind​

crispy-tailwind is a Tailwind template pack for django-crispy-forms

UPDATE: crispy-tailwind is not recommended anymore, please check django-formify, which seamlessly integrates Tailwind CSS styles into your Django forms for a modern look.

Update requirements.txt

django-crispy-forms==2.0        # new
crispy-tailwind==0.5.0 # new
(venv)$ pip install -r requirements.txt

Update hotwire_django_app/settings.py

INSTALLED_APPS = [
...
'crispy_forms', # new
'crispy_tailwind', # new
]

CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" # new

CRISPY_TEMPLATE_PACK = "tailwind" # new

Update hotwire_django_app/templates/turbo_drive/create.html

{% extends "turbo_drive/base.html" %}
{% load crispy_forms_tags %}

{% block content %}

<div class="w-full max-w-7xl mx-auto px-4">

<form method="post">
{% csrf_token %}

{{ form|crispy }}

<button type="submit" class="btn-blue">Submit</button>
</form>

</div>

{% endblock %}

Notes:

  1. We load crispy_forms_tags at the top
  2. {{ form|crispy }} will render the form using Tailwind template pack. (set by CRISPY_TEMPLATE_PACK)

JIT​

If we check the form style.

We notice some tailwind css such as mb-2 is still not working.

Why did it happen?

Because we did not tell Tailwind CSS which css classes are used by crispy-tailwind

If you use other 3-party Python packages to manipulate tailwind css classnames, you might also meet this problem

Update tailwind.config.js

const Path = require("path");
const pwd = process.env.PWD;

// To make tailwind can scan code in Python packages:
// export pySitePackages=$(python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))")
const pySitePackages = process.env.pySitePackages;

// We can add current project paths here
const projectPaths = [
Path.join(pwd, "./hotwire_django_app/templates/**/*.html"),
// add js file paths if you need
];

// We can add 3-party python packages here
let pyPackagesPaths = []
if (pySitePackages){
pyPackagesPaths = [
Path.join(pySitePackages, "./crispy_tailwind/**/*.html"),
Path.join(pySitePackages, "./crispy_tailwind/**/*.py"),
Path.join(pySitePackages, "./crispy_tailwind/**/*.js"),
];
}

const contentPaths = [...projectPaths, ...pyPackagesPaths];
console.log(`tailwindcss will scan ${contentPaths}`);

module.exports = {
content: contentPaths,
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
};

If we set pySitePackages env variable, the Tailwind can know the path of the crispy_tailwind package, and will scan the code to detect what css class names are used.

(venv)$ python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))"
hotwire_django_project/env/lib/python3.10/site-packages

# set it to pySitePackages ENV variable
(venv)$ export pySitePackages=$(python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))")

# check
(venv)$ env | grep pySitePackages
# restart webpack
(venv)$ npm run start

As you can see, now the form style works with Tailwind smoothly

Test Again​

Now, please try to create some tasks on the form page, and you will be redirected to the home page.

If the title is very short, you might see Error: Form responses must redirect to another location in the console of the web devtool, do not worry, and I will talk about it in the next chapter.