Django Templates and Static Files
Django templates let you show dynamic data (data that changes based on users or database content) inside HTML pages. This chapter covers templates, static files, app setup, and how to add Tailwind CSS for styling. By the end, you will know how to build reusable page layouts, serve CSS and image files, and style your project with Tailwind.
Django Template Language (DTL) vs Jinja2
Section titled “Django Template Language (DTL) vs Jinja2”Django comes with its own way to write templates called Django Template Language (DTL). It looks very similar to Jinja2 (another popular template tool), so if you’ve seen one, the other will feel familiar. DTL is the default and the one you should use for most Django projects.
Stick with DTL unless you have a strong reason to switch. If you prefer Jinja2, Django does support it as an option, but for beginners, DTL is the way to go.
Template Settings
Section titled “Template Settings”By default, Django looks for template files in two places:
- A
templates/folder inside each app (whenAPP_DIRS=True) - Any extra folders you list in
settings.pyunderTEMPLATES[0]["DIRS"]
To add a project-level templates/ folder (one that’s shared across all apps), update settings.py like this:
TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], # project-level templates "APP_DIRS": True, # include app-level templates "OPTIONS": { # context processors, etc. }, },]Basic Template Usage
Section titled “Basic Template Usage”In a Django template, you use:
{{ variable }}- to display a value{% tag %}- to run logic like loops or conditions
Hello {{ name }}!If the view sends name = "Kumar Shail", the page will show: Hello Kumar Shail!
Common Template Tags
Section titled “Common Template Tags”if - show something only when a condition is true:
Section titled “if - show something only when a condition is true:”{% if name %} Hello, {{ name }}!{% endif %}for - loop over a list and show each item:
Section titled “for - loop over a list and show each item:”{% for item in items %} {{ item }}{% endfor %}block / endblock - mark a section in a base template that child templates can replace:
Section titled “block / endblock - mark a section in a base template that child templates can replace:”<title>{% block title %}My Website{% endblock title %}</title>extends - use a base template as the starting point for a page:
Section titled “extends - use a base template as the starting point for a page:”{% extends "base.html" %}include - pull in another template file, great for things like headers or footers:
Section titled “include - pull in another template file, great for things like headers or footers:”{% include "partials/header.html" %}load static / static - link to static files like CSS, JS, or images:
Section titled “load static / static - link to static files like CSS, JS, or images:”{% load static %}<link rel="stylesheet" href="{% static 'css/style.css' %}">url - generate a link to a named URL without hardcoding the path:
Section titled “url - generate a link to a named URL without hardcoding the path:”<a href="{% url 'all_items' %}">All Items</a>Template Inheritance
Section titled “Template Inheritance”Template inheritance lets you write the common layout (header, footer, navigation) once in a base template, and then have other pages just fill in their own content. This way you don’t repeat the same HTML on every page.
templates/base.html (example):
{% load static %}<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="stylesheet" href="{% static 'css/style.css' %}"> <title>{% block title %}My Project{% endblock title %}</title></head><body> <nav><!-- common navbar --></nav> <main>{% block content %}{% endblock content %}</main> <footer><!-- common footer --></footer> </body></html>The {% block %} sections are the parts that child templates can replace. Everything else (like the <nav> and <footer>) stays the same on every page.
Child template (core/templates/core/all_items.html):
{% extends "base.html" %}
{% block title %}All Items{% endblock title %}
{% block content %} <h1>All Items</h1> <p>Welcome {{ name }}</p> {% for item in names %} <p>{{ item }}</p> {% endfor %}{% endblock content %}This child template starts with {% extends "base.html" %} which means “use base.html as the shell, and fill in my blocks”. It only defines what goes inside title and content - the rest comes from the base.
Serving Templates from Views
Section titled “Serving Templates from Views”To show a template to a user, you use render() in your view. You pass it the request, the template file name, and any data (called context) you want to display in the template.
core/views.py:
from django.shortcuts import render
def all_items(request): context = { "name": "Kumar Shail", "names": ["Tea", "Coffee", "Juice"], } return render(request, "core/all_items.html", context)core/urls.py:
from django.urls import pathfrom . import views
urlpatterns = [ path("", views.all_items, name="all_items"),]Include the app’s URLs in the main project urls.py:
from django.contrib import adminfrom django.urls import include, path
urlpatterns = [ path("admin/", admin.site.urls), path("items/", include("core.urls")),]Now visit http://localhost:8000/items/ and you’ll see the page rendered with your data.
Serving a Template Directly from a URL (No View Needed)
Section titled “Serving a Template Directly from a URL (No View Needed)”For simple pages that don’t need any data from the database, you can skip writing a view entirely and use TemplateView directly in your URLs:
app/urls.py:
from django.urls import pathfrom django.views.generic import TemplateView
urlpatterns = [ path("", TemplateView.as_view(template_name="core/index.html"), name="index"),]This is handy for things like a plain “About” or “Coming Soon” page.
Static Files
Section titled “Static Files”Static files are files that don’t change based on user data - things like CSS stylesheets, JavaScript files, images, and fonts. Django’s built-in development server handles these automatically when DEBUG=True. For production, you need a proper setup.
The easiest way to serve static files in production is WhiteNoise. Install it:
uv add whitenoiseAdd WhiteNoise to MIDDLEWARE in settings.py (it must go right after SecurityMiddleware):
MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", # other middleware...]
STATIC_URL = "/static/"STATICFILES_DIRS = [BASE_DIR / "static"]STATIC_ROOT = BASE_DIR / "staticfiles"STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"Create a sample stylesheet at core/static/core/css/style.css or static/css/style.css:
body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding: 1rem;}Before deploying, run this command to copy all static files into one folder (STATIC_ROOT) so your web server can find them:
python manage.py collectstaticWhiteNoise then serves those files efficiently without needing a separate server.
Tailwind CSS Integration
Section titled “Tailwind CSS Integration”Tailwind CSS is a popular styling tool that lets you style your HTML using small, ready-made class names instead of writing CSS from scratch. This project uses django-tailwind, which connects Tailwind into your Django project smoothly.
The commands below use uv to run things. If you don’t have uv, you can replace uv add with pip install and uv run python manage.py with python manage.py.
Install the required packages:
uv add django-tailwind django-browser-reloaddjango-browser-reload automatically refreshes your browser when you change a template - no need to manually reload the page while developing.
Add the apps to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [ # other apps... "tailwind", "theme", # the Tailwind theme app created by tailwind init]
# development-only toolsif ENVIRONMENT != PRODUCTION: INSTALLED_APPS += ["django_browser_reload"]
TAILWIND_APP_NAME = "theme"INTERNAL_IPS = ["127.0.0.1", "localhost"]NPM_BIN_PATH = "npm" if os.name != "nt" else r"C:\Program Files\nodejs\npm.cmd"TAILWIND_APP_NAME = "theme"- tells Django which app holds your Tailwind setup.INTERNAL_IPS- tells Django which IP addresses count as “local” (needed for dev tools to work).NPM_BIN_PATH- points Django to thenpmtool. Thentcheck handles Windows vs Mac/Linux.
Add django_browser_reload middleware in development (so auto-reload works):
if ENVIRONMENT != PRODUCTION: MIDDLEWARE += ["django_browser_reload.middleware.BrowserReloadMiddleware"]Add the reload URL in development:
from django.conf import settingsfrom django.urls import include, path
urlpatterns = [ path("admin/", admin.site.urls), path("items/", include("core.urls")),]
if settings.ENVIRONMENT != settings.PRODUCTION: urlpatterns += [path("__reload__/,", include("django_browser_reload.urls"))]Now run these two commands to set up Tailwind. You only need to do this once:
uv run python manage.py tailwind inituv run python manage.py tailwind installtailwind init- creates thethemeapp inside your project.tailwind install- downloads the Tailwind CSS tools via npm.
Use Tailwind in your base template like this:
{% load static %}{% load static tailwind_tags %}<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> {% tailwind_css %} <title>{% block title %}My Project{% endblock title %}</title></head><body class="bg-slate-100 text-slate-900"> {% block content %}{% endblock content %}</body></html>{% tailwind_css %} automatically injects the Tailwind stylesheet. Then you can use Tailwind classes like bg-slate-100 directly on any HTML element.
When developing, you need to run two terminal windows at the same time:
uv run python manage.py runserveruv run python manage.py tailwind startThe first starts Django. The second watches your templates for changes and rebuilds the Tailwind CSS file automatically.
When you’re ready to deploy, build the final optimized Tailwind CSS file:
uv run python manage.py tailwind build