Core rule:

Templates display data. Views handle requests. Models handle data. Business logic never lives in templates. CSS, JavaScript, and HTML are always separated — never mixed into one file.

Contents

# Section
1 The Three-Folder Rule
2 Static Folder Structure
3 Template Folder Structure
4 File Naming Standard
5 base.html Standard
6 Template Block Standards
7 CSS Organization Rules
8 JavaScript Rules
9 What Goes Where — Decision Table
10 Django App Template Isolation
11 Static File Loading
12 Reusable Template Components
13 Anti-Patterns to Avoid
14 Definition of Done

1. The Three-Folder Rule

Every Django project must have a clean separation of static files across exactly three areas:

static/
  css/       ← All styles. Nothing else.
  js/        ← All JavaScript. Nothing else.
  images/    ← All images, icons, logos.

templates/   ← All HTML. No inline styles. No inline scripts.

Never:

  • Write <style> blocks inside an HTML template.
  • Write <script> blocks with logic inside an HTML template.
  • Put images directly in your templates/ folder.
  • Mix styles from multiple components into one giant CSS file.

2. Static Folder Structure

Full Structure

static/
  css/
    base.css                  ← Reset, typography, global variables
    layout.css                ← Grid, containers, section spacing
    components/
      button.css
      card.css
      form.css
      modal.css
      navbar.css
      table.css
      badge.css
      alert.css
    pages/
      home.css
      dashboard.css
      login.css
      profile.css

  js/
    base.js                   ← Global utilities, event init
    components/
      modal.js
      dropdown.js
      toast.js
      form-validator.js
      confirm-delete.js
    pages/
      dashboard.js
      home.js
      profile.js
    utils/
      api.js                  ← Fetch wrapper / CSRF helper
      format.js               ← Date/number formatters
      storage.js              ← localStorage helpers

  images/
    logo/
      logo.svg
      logo-white.svg
    icons/
      (SVG icons or icon sprites)
    ui/
      placeholder.png
      avatar-default.png

3. Template Folder Structure

templates/
  base.html                   ← Master layout (head, nav, footer, blocks)
  base_auth.html              ← Layout for login/signup (no nav)

  includes/
    navbar.html
    footer.html
    sidebar.html
    pagination.html
    messages.html             ← Django messages / toast alerts
    breadcrumb.html

  components/
    card.html
    modal_confirm.html
    empty_state.html
    loading_spinner.html
    form_field.html           ← Reusable form field with label + error

  pages/
    home.html
    dashboard.html
    profile.html
    settings.html

  auth/
    login.html
    signup.html
    forgot_password.html
    reset_password.html

  errors/
    404.html
    500.html
    403.html

  emails/
    base_email.html
    welcome.html
    reset_password.html
    notification.html

  app_name/                   ← One subfolder per Django app
    project_list.html
    project_detail.html
    project_create.html
    project_edit.html

4. File Naming Standard

CSS Files

Good Bad
button.css btn_styles.css
project_card.css ProjectCard.css
dashboard.css page1.css
base.css global_stuff.css

Use snake_case. Name by component or page — not by author, date, or feature number.

JavaScript Files

Good Bad
modal.js Modal.js
form-validator.js validation_stuff.js
api.js helper.js
dashboard.js page_script_2.js

Template Files

Good Bad
project_list.html projects.html
project_detail.html projectDetails.html
modal_confirm.html ConfirmPopup.html
form_field.html field.html

Use snake_case. Name by entity + action/type.


5. base.html Standard

Every project must have one master layout that all pages extend.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% block title %}App Name{% endblock %}</title>

  {% load static %}
  <link rel="stylesheet" href="{% static 'css/base.css' %}">
  <link rel="stylesheet" href="{% static 'css/layout.css' %}">
  {% block extra_css %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">

  {% include "includes/navbar.html" %}

  <main>
    {% include "includes/messages.html" %}
    {% block content %}{% endblock %}
  </main>

  {% include "includes/footer.html" %}

  <script src="{% static 'js/base.js' %}"></script>
  {% block extra_js %}{% endblock %}
</body>
</html>

Page Template Extending Base

{% extends "base.html" %}
{% load static %}

{% block title %}Projects — App Name{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/pages/dashboard.css' %}">
{% endblock %}

{% block content %}
<section class="page-header">
  <h1>Projects</h1>
</section>

<section class="project-list">
  {% for project in projects %}
    {% include "components/card.html" with project=project %}
  {% empty %}
    {% include "components/empty_state.html" with message="No projects yet." %}
  {% endfor %}
</section>
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/pages/dashboard.js' %}"></script>
{% endblock %}

6. Template Block Standards

Block Purpose
{% block title %} Page title in <head>
{% block extra_css %} Page-specific CSS files
{% block body_class %} Body class for page-level styling
{% block content %} Main page content
{% block extra_js %} Page-specific JS files
{% block sidebar %} Optional sidebar

Never add page-specific CSS or JS in base.html. Always use extra_css and extra_js blocks.


7. CSS Organization Rules

base.css — Contains

/* 1. CSS Custom Properties (Variables) */
:root {
  --color-primary: #2563eb;
  --color-text: #111827;
  --color-background: #f9fafb;
  --color-border: #e5e7eb;
  --color-error: #dc2626;
  --color-success: #16a34a;

  --font-family: 'Inter', sans-serif;
  --font-size-base: 16px;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;

  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 32px;
  --spacing-xl: 64px;
}

/* 2. Reset */
/* 3. Typography */
/* 4. Utility classes */

Component CSS Rule

Each component CSS file only styles that component.

/* button.css */
.btn { ... }
.btn-primary { ... }
.btn-secondary { ... }
.btn-danger { ... }
.btn:disabled { ... }
.btn.loading { ... }

Never put button styles in card.css. Never put modal styles in dashboard.css.

Page CSS Rule

Page CSS only styles page-specific layout. Reuse component classes.

/* dashboard.css — only page-level layout */
.dashboard-grid { ... }
.dashboard-header { ... }
.stats-row { ... }

8. JavaScript Rules

No Inline JavaScript in Templates

Bad:

<button onclick="deleteProject({{ project.id }})">Delete</button>

<script>
  function deleteProject(id) {
    fetch('/delete/' + id);
  }
</script>

Good:

<button class="btn-delete" data-id="{{ project.id }}" data-url="{% url 'project:delete' project.id %}">
  Delete
</button>
// js/pages/dashboard.js
document.querySelectorAll('.btn-delete').forEach(btn => {
  btn.addEventListener('click', () => {
    const id = btn.dataset.id;
    const url = btn.dataset.url;
    // handle delete
  });
});

Use `data-*` Attributes to Pass Django Data to JS

<div id="project-map"
     data-lat="{{ project.latitude }}"
     data-lng="{{ project.longitude }}"
     data-name="{{ project.name }}">
</div>
const map = document.getElementById('project-map');
const lat = map.dataset.lat;
const lng = map.dataset.lng;

Never interpolate Django variables directly inside JS strings in a template.

CSRF Token for AJAX Calls

// utils/api.js
function getCsrfToken() {
  return document.querySelector('[name=csrfmiddlewaretoken]')?.value
    || document.cookie.split('; ')
        .find(row => row.startsWith('csrftoken='))
        ?.split('=')[1];
}

async function post(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRFToken': getCsrfToken()
    },
    body: JSON.stringify(data)
  });
  return response.json();
}

9. What Goes Where — Decision Table

Logic Type Where It Lives
Business rules (pricing, permissions, status transitions) Django view or service function
Database queries Django view, manager, or querysets — never in template
Validation Django form / serializer — not template
Display formatting (date, number) Django template filter or template tag
Page layout and structure HTML template
Styling and appearance CSS file
User interaction (click, toggle, submit) JS file
Inline styles for specific element Use a CSS class, not style=""
Hardcoded content that may change Move to database or settings

Never Put in Templates

× Queries ({% for obj in Model.objects.all() %} is not valid Django, but avoid heavy logic)
× Business decisions ("if price > 100 and user.plan == 'free'...")
× Calculations
× Sensitive data output without escaping
× Inline styles
× Inline script blocks with logic

10. Django App Template Isolation

Every Django app should own its templates in a subfolder:

templates/
  projects/          ← Templates for the "projects" app
    list.html
    detail.html
    create.html
    edit.html
  users/             ← Templates for the "users" app
    profile.html
    settings.html
  billing/           ← Templates for the "billing" app
    plans.html
    invoices.html

Reference in views:

return render(request, 'projects/list.html', context)

11. Static File Loading Standard

Always use {% load static %} at the top of any template that references static files.

{% load static %}
<link rel="stylesheet" href="{% static 'css/components/card.css' %}">
<script src="{% static 'js/pages/dashboard.js' %}"></script>
<img src="{% static 'images/logo/logo.svg' %}" alt="App Logo">

Never hardcode static paths:

<!-- Bad -->
<link rel="stylesheet" href="/static/css/card.css">

<!-- Good -->
<link rel="stylesheet" href="{% static 'css/components/card.css' %}">

12. Reusable Template Components

For UI patterns that repeat across pages, use {% include %}.

Reusable Card Component

<!-- templates/components/card.html -->
<div class="card">
  <div class="card-header">
    <h3>{{ title }}</h3>
    {% if subtitle %}<p class="card-subtitle">{{ subtitle }}</p>{% endif %}
  </div>
  <div class="card-body">
    {{ content }}
  </div>
  {% if actions %}
  <div class="card-footer">
    {{ actions }}
  </div>
  {% endif %}
</div>

Usage

{% include "components/card.html" with title="Project Name" subtitle="Created today" %}

Reusable Form Field

<!-- templates/components/form_field.html -->
<div class="form-group {% if field.errors %}has-error{% endif %}">
  <label for="{{ field.id_for_label }}">
    {{ field.label }}
    {% if field.field.required %}<span class="required-star">*</span>{% endif %}
  </label>
  {{ field }}
  {% if field.errors %}
    <span class="field-error">{{ field.errors|first }}</span>
  {% endif %}
  {% if field.help_text %}
    <span class="field-hint">{{ field.help_text }}</span>
  {% endif %}
</div>

Usage

{% for field in form %}
  {% include "components/form_field.html" with field=field %}
{% endfor %}

13. Anti-Patterns to Avoid

Anti-Pattern Fix
<style> blocks inside templates Move to component CSS file
<script> with logic inside templates Move to page JS file
Giant single styles.css for entire project Split into base + component + page CSS files
Giant single main.js for entire project Split into component and page JS files
Duplicating the same HTML section across pages Extract to {% include %} component
Hardcoded color/spacing values in CSS Use CSS custom properties in base.css
Business logic in template conditionals Move to view or model property
Django variables directly interpolated in JS strings Use data-* attributes
No base template — every page is standalone Implement template inheritance with base.html

14. Definition of Done — Django Template Work