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)

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

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael Yin

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

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

© 2024 SaaS Hammer