Django Tailwind CSS, Alpine.js Tutorial Series:
- Introduction
 - How to Setup Tailwind CSS with Django (Part 1)
 - How to Setup Tailwind CSS with Django (Part 2)
 - Optimize Tailwind CSS in Django
 - Render Django Form with Tailwind CSS Style
 - Integrate Alpine.js with Django (Part 1) (coming soon)
 - Integrate Alpine.js with Django (Part 2) (coming soon)
 - Build Task List with Tailwind CSS, Alpine.js and Django (coming soon)
 - Django Form Validation in Tailwind Modal (Alpine.js) (coming soon)
 - Django Form Validation in Tailwind Modal (Alpine.js + HTMX) (coming soon)
 - How to deploy Django Tailwind CSS project with Docker (coming soon)
 The source code is on Github/django-tailwind-alpine-htmx
Recommended Posts:
** 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. **
Objectives
By the end of this chapter, you should be able to:
- Render Django form with 
django-crispy-formsandcrispy-tailwind - Use 
tailwindcss/formsplugin to reset form style. 
Tasks App
Let's first create django app tasks
(env)$ python manage.py startapp tasks
.
├── db.sqlite3
├── django_tailwind_app
├── env
├── frontend
├── manage.py
├── requirements.txt
└── tasks                   # new
Add tasks to the INSTALLED_APPS in django_tailwind_app/settings.py
INSTALLED_APPS = [
    ...
    'tasks',                  # new
]
Model
Update tasks/models.py
from django.db import models
from django.utils import timezone
class Task(models.Model):
    title = models.CharField(max_length=250)
    due_date = models.DateField(default=timezone.now)
Migrate the db
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Form
Create tasks/forms.py
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        fields = ("title", "due_date")
View
Update tasks/views.py
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from .forms import TaskForm
def create_task(request):
    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponse('ok')
    else:
        form = TaskForm()
    return render(request, 'tasks/task_create.html', {'form': form})
Template
Create django_tailwind_app/templates/base.html
{% load webpack_loader %}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  {% stylesheet_pack 'app' %}
</head>
<body>
{% block content %}
{% endblock content %}
{% javascript_pack 'app' %}
</body>
</html>
Create django_tailwind_app/templates/tasks/task_create.html
{% extends "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"  value="Submit">Submit</button>
    </form>
  </div>
{% endblock %}
URL
Create tasks/urls.py
from django.urls import path
from .views import (
    create_task
)
urlpatterns = [
    path("create/", create_task, name="task-create"),
]
Create django_tailwind_app/urls.py
from django.contrib import admin
from django.urls import include, path
from django.views.generic import TemplateView
urlpatterns = [
    path('', TemplateView.as_view(template_name="index.html")),
    path("tasks/", include("tasks.urls")),
    path('admin/', admin.site.urls),
]
Manual Test
(env)$ python manage.py runserver
If we check http://127.0.0.1:8000/tasks/create/

As you can see, even we import Tailwind to our Django project, the default form style still look ugly.
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.
$ cd frontend
$ npm install @tailwindcss/forms
Update frontend/tailwind.config.js
module.exports = {
  //
  plugins: [
    require('@tailwindcss/forms'),                // new
  ],
}

crispy-tailwind
crispy-tailwind is a Tailwind template pack for django-crispy-forms
Update requirements.txt
django-crispy-forms==1.13.0        # new
crispy-tailwind==0.5.0             # new
$ pip install -r requirements.txt
Update django_tailwind_app/settings.py
INSTALLED_APPS = [
    "crispy_forms",                     # new
    "crispy_tailwind",                  # new
]
CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind"
CRISPY_TEMPLATE_PACK = "tailwind"
Update django_tailwind_app/templates/tasks/task_create.html
{% extends "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"  value="Submit">Submit</button>
    </form>
  </div>
{% endblock %}
Notes:
- We load 
crispy_forms_tagsat the top {{ form|crispy }}will render the form usingTailwindtemplate pack. (set byCRISPY_TEMPLATE_PACK)
JIT

If we check the elements in the devtools, we notice some tailwind css such as mb-2 is not working.
Why does that happen?
Because we did not tell Tailwind CSS which css classes are used by crispy-tailwind
If you use other 3-party packages to manipulate tailwind css classnames, you should also do it.
Update frontend/tailwind.config.js
const Path = require("path");
const pwd = process.env.PWD;
const pySitePackages = process.env.pySitePackages;
// We can add current project paths here
const projectPaths = [
  Path.join(pwd, "../django_tailwind_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: [],
}
If we set pySitePackages when running npm run start, it should work as
(env)$ python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))"
/Users/michaelyin/django_tailwind_project/env/lib/python3.9/site-packages
# set it to pySitePackages ENV variable
(env)$ export pySitePackages=$(python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))")
# check
(env)$ env | grep pySitePackages
(env)$ cd frontend
(env)$ npm run start

As you can see, now the margin under the input element is working!
Alternative Solution
If you don't want to use crispy-tailwind, you can also use django-widget-tweaks to add Tailwind CSS classes to the form elements.
You can check this blog post
Django Tailwind CSS, Alpine.js Tutorial Series:
- Introduction
 - How to Setup Tailwind CSS with Django (Part 1)
 - How to Setup Tailwind CSS with Django (Part 2)
 - Optimize Tailwind CSS in Django
 - Render Django Form with Tailwind CSS Style
 - Integrate Alpine.js with Django (Part 1) (coming soon)
 - Integrate Alpine.js with Django (Part 2) (coming soon)
 - Build Task List with Tailwind CSS, Alpine.js and Django (coming soon)
 - Django Form Validation in Tailwind Modal (Alpine.js) (coming soon)
 - Django Form Validation in Tailwind Modal (Alpine.js + HTMX) (coming soon)
 - How to deploy Django Tailwind CSS project with Docker (coming soon)
 The source code is on Github/django-tailwind-alpine-htmx
Recommended Posts: