This document outlines the conventions and best practices for templates in this Django project.
TIP: Visit the UI Component Examples page at
/ui/examples/to see a live demonstration of all available components.
The project uses two primary base templates:
apps/public/templates/base.html- Main layout template for full pagesapps/public/templates/partial.html- Base template for HTMX partial updates
Additionally, the project uses specialized base templates for components:
apps/public/templates/components/_component_base.html- Base template for UI components
This template provides the complete HTML structure with:
- HTML5 doctype and responsive viewport
- Metadata for SEO and social sharing
- CSS imports (Tailwind CSS v4)
- JS dependencies (HTMX, HyperScript)
- Common layout elements (navbar, footer)
- Toast notification system
- Modal dialog system
{% extends "base.html" %}
{% block title %}Page Title{% endblock %}
{% block content %}
<!-- Your main content here -->
{% endblock %}This template is designed for HTMX partial page updates without the full HTML structure:
{% extends "partial.html" %}
{% block content %}
<!-- Partial content here -->
{% endblock %}For out-of-band updates, set is_oob=True in the view context:
{% if is_oob %}
<div id="notification-count" hx-swap-oob="true">{{ count }}</div>
{% endif %}The templates/layout/ directory contains layout elements that make up the page structure:
footer.html- Site footermodals.html- Modal containersnavbar.html- Main navigation barmessages/- Notification/toast templatesnav/- Navigation components like navbar parts and account menu
The project uses a minimalist design system with a focused color palette:
- Primary: Deep navy blue from the theme palette:
navy-900: #0a192f- Primary buttons, footer backgroundnavy-800: #112240- Button hover statesnavy-700: #1d3557- Borders, focus rings, secondary accents
- Accent: Yellow (
accent: #ffd404) used sparingly for important highlights and decorative elements - Grayscale: Consistent gray shades for UI elements and text:
gray-900throughgray-700: Primary and secondary textgray-600throughgray-400: Tertiary text, icons, and disabled statesgray-300throughgray-100: Borders, dividers, and background variationsgray-50: Very light background for secondary content areas
- Feedback: Standard semantic colors for system feedback:
- Red: Errors and destructive actions
- Green: Success and completion states
- Yellow: Warnings and attention states
-
Primary Buttons:
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-700"
-
Secondary/Gray Buttons:
class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
- Standard Links:
class="text-slate-700 hover:text-slate-900" - Subtle Links:
class="text-gray-600 hover:text-slate-700 hover:underline"
- Standard Card:
class="bg-white shadow rounded-lg p-6" - Bordered Card:
class="bg-white shadow rounded-lg overflow-hidden"with inner containerclass="border-l-4 border-slate-700 p-6"
- Form Layout: Use
space-y-6for vertical spacing andgridfor multi-column layouts - Form Groups: Apply consistent spacing with
class="mb-4"orclass="space-y-2" - Form Labels:
class="block text-sm font-medium text-gray-700 mb-2" - Form Inputs:
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-slate-700 focus:ring-slate-700 sm:text-sm"
- Status Badges:
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-slate-100 text-slate-800" - Alternative Badges: Use gray-scale for neutral states and standard semantic colors for others
- Minimalist: Clean layouts with ample white space
- Focused: Clear hierarchy and limited color usage
- Consistent: Uniform spacing, typography, and interaction patterns
- Accessible: High contrast text and clear interactive elements
- Font Awesome is used for all icons throughout the application
- Use
fasprefix for solid icons (e.g.<i class="fas fa-user"></i>) - Use
farprefix for regular icons (e.g.<i class="far fa-circle"></i>) - Use
fabprefix for brand icons (e.g.<i class="fab fa-github"></i>) - Add
fa-fwclass for fixed-width icons to ensure proper alignment - Use
fa-lg,fa-2x, etc. for larger icons when needed
- Use
container mx-auto px-4 py-8for main page containers - Page titles with
text-2xl font-bold text-gray-900andmb-6 - Section headers with
text-xl font-semibold text-gray-900 mb-4 - Component headers with
text-lg font-medium text-gray-900 mb-4
Reusable UI components are organized in templates/components/:
forms/- Form input componentsmodals/- Modal dialog templatesbuttons/- Button componentscards/- Card componentslists/- List components
Components extend from components/_component_base.html which provides basic structure and utility blocks.
A live, interactive showcase of all available UI components is available at /ui/examples/ or by clicking the "UI Components" link in the footer. This page demonstrates:
- Form components (text inputs, textareas, checkboxes, selects, radio buttons, buttons)
- Card layouts
- Modal dialogs
- List displays
- Common UI elements (notifications, error messages)
Use this page as a reference when implementing interfaces to ensure consistent design across the application.
The examples page demonstrates the following standards:
- Proper page container and layout structure
- Standard headings and typography hierarchy
- Button styles for primary and secondary actions
- Card/panel layouts with consistent styling
- Form element styling and organization
- Consistent use of navy and grayscale color palette
- Modal component usage and styling
- Status badges and indicators
Access this resource at /ui/examples/ to see live implementations of all styled components.
Form components provide consistent styling and error handling:
<!-- Basic Input -->
<div class="sm:col-span-3">
<label for="id_first_name" class="block text-sm font-medium text-gray-700">
First Name
</label>
<div class="mt-1">
<input type="text" name="first_name" id="id_first_name" value="{{ value|default:'' }}"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-slate-700 focus:ring-slate-700 sm:text-sm" />
</div>
{% if errors %}
<p class="mt-2 text-sm text-red-600">{{ errors.0 }}</p>
{% endif %}
</div>
<!-- Form Buttons -->
{% include "components/forms/form_buttons.html" with submit_text="Save Changes" %}The project uses consistent button styling:
<!-- Primary Button -->
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-700">
Primary Action
</button>
<!-- Secondary Button -->
<button type="button" class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500">
Secondary Action
</button>Toast notifications use color-coded borders and icons:
<div class="rounded-lg shadow-lg overflow-hidden bg-white border-l-4 border-green-500">
<div class="p-4 flex items-start">
<!-- Icon -->
<div class="flex-shrink-0 mr-3">
<svg class="h-5 w-5 text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<!-- Content -->
<div class="flex-1">
<p class="text-sm text-gray-800">Operation successful!</p>
</div>
<!-- Close Button -->
<button class="ml-4 text-gray-400 hover:text-gray-500 focus:outline-none">
<svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>The project uses a CSS-based modal system with minimal JavaScript:
- Modal containers are included in
base.htmlautomatically - Modal templates extend from
components/modals/modal_base.html - Specialized modal types:
modal_content.html- For displaying contentmodal_form.html- For formsmodal_confirm.html- For confirmations
Key features:
- CSS animations for smooth transitions
- Keyboard accessibility (Escape to close)
- Backdrop click to dismiss
- No JavaScript functions, only inline handlers
- HTMX integration for dynamic content loading
Opening a modal with HTMX:
<button
hx-get="{% url 'load_modal_content' %}"
hx-target="#primary-modal-container"
hx-swap="innerHTML"
>
Open Modal
</button>Use the MainContentView mixin for views that handle both full page and HTMX partial requests:
class ProfileView(MainContentView):
template_name = "pages/profile.html"
partial_template_name = "partials/profile_content.html"- Use Tailwind responsive prefixes (
sm:,md:,lg:) - Mobile-first approach (default styles are for mobile)
- Test all templates on multiple screen sizes
- IMPORTANT: Tailwind cannot recognize dynamic class names created with Django template syntax
- ❌ Avoid:
md:col-span-{% block content_width %}8{% endblock %} - ✅ Instead: Include all potential variants:
md:col-span-8 md:col-span-9 md:col-span-10 - ✅ Alternative: Use CSS custom properties or JavaScript for dynamic values
- This is because Tailwind performs static analysis at build time and cannot process Django template variables or blocks
Note: We've migrated from django-tailwind to django-tailwind-cli and upgraded to Tailwind CSS v4. This version uses CSS variables for theme customization in static/css/source.css instead of a separate tailwind.config.js file.
- Styling with utility classes
- Hover, focus, and other states
- Responsive design
- Dark mode
- Theme variables
- Colors
- Adding custom styles
- Detecting classes in source files
- Functions and directives
-
Template Names:
- Use lowercase, underscore-separated names
- No leading underscore in filenames (e.g.,
base.htmlnot_base.html) - Group related templates in subdirectories
-
Blocks:
- Use descriptive block names
- Provide default content for optional blocks
- Keep blocks focused on a single responsibility
-
Context Variables:
- Document required and optional context variables using Django comment tags
{% comment %}...{% endcomment %} - Use descriptive variable names
- Provide sensible defaults with the
|defaultfilter
- Document required and optional context variables using Django comment tags
-
Accessibility:
- Include proper ARIA attributes
- Maintain proper heading hierarchy
- Ensure sufficient color contrast
- Support keyboard navigation
-
Performance:
- Minimize template inheritance depth
- Use template fragments for expensive operations
- Leverage template caching for slow-changing content
Custom template tags are defined in app-specific templatetags modules:
{% load common_tags %}
{% user_display user %}Prefer Django's built-in filters when possible:
{{ value|default:"empty" }}
{{ text|truncatewords:30 }}- Use
hx-boostfor page transitions - Target specific elements with
hx-target - Use
hx-swapto control the swap behavior - Use
hx-triggerto specify when the request occurs - Add CSRF protection with the
{% django_htmx_script %}tag
The project follows a "minimal JavaScript" approach:
- Use modern HTML5 and CSS3: Leverage CSS animations, transitions, and modern HTML features
- Use inline handlers: When JavaScript is necessary, use minimal inline event handlers
- Prefer HTMX: Use HTMX for interactive behaviors instead of custom JavaScript
- Use CSS animations: Implement animations with CSS keyframes and transitions
- NO script tags: Avoid using
<script>tags with function definitions - Accessibility: Ensure all interactive elements work with keyboard navigation
/* Define animations with keyframes */
@keyframes toast-fade {
0%, 80% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-10px); }
}
/* Apply animation to element */
.animate-toast {
animation: toast-fade 5s ease-in-out forwards;
}<button onclick="this.closest('.modal-container').innerHTML = ''">
Close
</button><button hx-get="/path/to/content" hx-target="#content-area" hx-swap="innerHTML">
Load Content
</button>