Render Django Form with Tailwind CSS Style

Table of Contents

Django Tailwind CSS, Alpine.js Tutorial Series:

  1. Introduction
  2. How to Setup Tailwind CSS with Django (Part 1)
  3. How to Setup Tailwind CSS with Django (Part 2)
  4. Optimize Tailwind CSS in Django
  5. Render Django Form with Tailwind CSS Style
  6. Integrate Alpine.js with Django (Part 1) (coming soon)
  7. Integrate Alpine.js with Django (Part 2) (coming soon)
  8. Build Task List with Tailwind CSS, Alpine.js and Django (coming soon)
  9. Django Form Validation in Tailwind Modal (Alpine.js) (coming soon)
  10. Django Form Validation in Tailwind Modal (Alpine.js + HTMX) (coming soon)
  11. How to deploy Django Tailwind CSS project with Docker (coming soon)

The source code is on Github/django-tailwind-alpine-htmx

Recommended Posts:

  1. Lightweight Javascript Framework Review (For Django Developers)

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:

  1. Render Django form with django-crispy-forms and crispy-tailwind
  2. Use tailwindcss/forms plugin 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:

  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 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:

  1. Introduction
  2. How to Setup Tailwind CSS with Django (Part 1)
  3. How to Setup Tailwind CSS with Django (Part 2)
  4. Optimize Tailwind CSS in Django
  5. Render Django Form with Tailwind CSS Style
  6. Integrate Alpine.js with Django (Part 1) (coming soon)
  7. Integrate Alpine.js with Django (Part 2) (coming soon)
  8. Build Task List with Tailwind CSS, Alpine.js and Django (coming soon)
  9. Django Form Validation in Tailwind Modal (Alpine.js) (coming soon)
  10. Django Form Validation in Tailwind Modal (Alpine.js + HTMX) (coming soon)
  11. How to deploy Django Tailwind CSS project with Docker (coming soon)

The source code is on Github/django-tailwind-alpine-htmx

Recommended Posts:

  1. Lightweight Javascript Framework Review (For Django Developers)
Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer