Theme Architecture¶
This document explains the architectural philosophy behind django-admin-deux theming.
Core Principles¶
1. Semantic HTML First¶
Philosophy: Let HTML structure drive styling, not classes.
Templates use pure HTML5 semantic elements without utility classes. Instead of:
<!-- ❌ Utility class approach (NOT used) -->
<div class="flex items-center justify-between px-6 py-4 bg-white border-b">
<h1 class="text-2xl font-bold">Title</h1>
</div>
We use:
Benefits: - ✅ Accessible by default (semantic elements have built-in roles) - ✅ Future-proof (HTML structure is stable) - ✅ Clean separation of concerns (structure vs presentation) - ✅ Easy to understand and maintain
2. Structural CSS Selectors¶
Philosophy: Target semantic structure via CSS selectors, not classes.
CSS targets the HTML structure instead of using utility classes in templates:
/* Target semantic structure */
.admin > header {
@apply bg-white border-b-4 border-primary-600 shadow-sm;
}
.admin header nav {
@apply px-6 flex items-center h-20 gap-4;
}
.admin header nav > a {
@apply text-gray-900 text-2xl font-bold no-underline;
}
.admin main table {
@apply w-full border-collapse bg-white border border-gray-300 rounded-lg;
}
Benefits: - ✅ No template changes needed for styling - ✅ Clear visual hierarchy via selector specificity - ✅ Easy to understand what you're styling - ✅ Reusable patterns across similar elements
3. Minimal Class Usage¶
Classes are used ONLY when semantic elements can't distinguish variants:
Root Wrapper (scoping):
Semantic Variants (when HTML can't distinguish):
<button class="primary">Save</button>
<button class="danger">Delete</button>
<button>Cancel</button> {# No class = default styling #}
Message Types (when role isn't enough):
<div role="alert" class="success">Success!</div>
<div role="alert" class="error">Error occurred</div>
Dark Mode (state class):
Template Structure¶
Finding Templates¶
Core Templates Location: djadmin/templates/djadmin/
Default Theme Templates: djadmin/plugins/theme/templates/djadmin/
Key templates to know about:
| Template | Location | Purpose |
|---|---|---|
admin_base.html |
Core | Main layout, includes _navigation.html |
_navigation.html |
Core | Base navigation (no dark mode) |
_navigation.html |
Theme Plugin | Navigation WITH dark mode toggle |
base_action.html |
Core | Wrapper for action pages |
{app}/{model}_list.html or actions/list.html |
Core | ListView with table |
actions/add.html |
Core | Create form |
actions/edit.html |
Core | Update form |
actions/delete.html |
Core | Delete confirmation |
actions/delete_bulk.html |
Core | Bulk delete confirmation |
project_dashboard.html |
Core | Project dashboard |
app_dashboard.html |
Core | App dashboard |
Base Template¶
File: djadmin/templates/djadmin/admin_base.html
The base template is named admin_base.html (not base.html) to avoid conflicts with user projects.
Key features:
- Loads CSS/JS from assets.css and assets.js context variables (populated by plugin hooks)
- Includes _navigation.html partial (can be overridden by themes)
- Provides blocks: title, breadcrumbs, content, footer, extra_css, extra_js
- Wraps everything in .admin scoping div
Navigation Partial - The One Override¶
Base Navigation: djadmin/templates/djadmin/_navigation.html
- Simple navigation without dark mode toggle
- Contains: brand link, optional menu, user info, mobile menu toggle
Theme Navigation: djadmin/plugins/theme/templates/djadmin/_navigation.html
- ONLY template override in the default theme
- Adds dark mode toggle button with sun/moon icons
- Otherwise identical to base navigation
This is the key pattern: Themes only need to override _navigation.html if they want to add dark mode or customize navigation.
Template Override Pattern¶
To override templates in your theme:
-
Create the template directory structure:
-
Place your app BEFORE others in
INSTALLED_APPS: -
Django's template loader will find
djadmin/_navigation.htmlinmytheme/templates/first and use it.
Important: You rarely need to override templates! Most themes only need CSS.
CSS Structure¶
Default Theme CSS Location¶
Source CSS: djadmin/plugins/theme/static_src/input.css (Tailwind source)
Compiled CSS: djadmin/plugins/theme/static/djadmin/theme/css/theme.css (generated)
The default theme uses Tailwind CSS with the @layer components approach.
CSS Architecture Pattern¶
@config "./tailwind.config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Global resets and base styles */
html { /* ... */ }
body { /* ... */ }
.dark body { /* Dark mode base */ }
}
@layer components {
/* Structural component selectors */
.admin { /* Root wrapper */ }
.admin > header { /* Top-level header */ }
.admin header nav { /* Navigation */ }
.dark .admin > header { /* Dark mode variants */ }
}
Key Patterns:
- Structural Selectors -
.admin > header,.admin header nav > a - Child Combinators (
>) - For direct children - Descendant Combinators (space) - For any descendant
- Pseudo-selectors -
:hover,:focus,:has(),:not() - Dark Mode Variants -
.dark .admin > header
Viewing the Complete CSS¶
To see all available selectors and styling patterns:
- Browse the source:
djadmin/plugins/theme/static_src/input.css - Use browser DevTools: Inspect elements to see applied selectors
Responsive Design¶
The theme is fully responsive using mobile-first CSS:
Breakpoint¶
Mobile: < 768px (max-width: 767px)
Desktop: ≥ 768px
Mobile Adaptations¶
On mobile screens (< 768px): - ✅ Navigation collapses to hamburger menu - ✅ Forms stack vertically (label above input) - ✅ Tables transform to labeled cards - ✅ Bulk actions stack vertically - ✅ Pagination wraps gracefully - ✅ Reduced padding/spacing
See the implementation: Browse the @media (max-width: 767px) section in djadmin/plugins/theme/static_src/input.css (~line 860+)
Asset Loading via Plugin Hooks¶
Themes provide assets using the plugin hook system:
# mytheme/djadmin_hooks.py
from djadmin.plugins import hookimpl
from djadmin import JSAsset, CSSAsset
from djadmin.actions.base import BaseAction
@hookimpl
def djadmin_get_action_view_assets(action):
"""Provide CSS/JS for all views"""
return {
BaseAction: { # Apply to all actions
'css': [CSSAsset(href='mytheme/custom.css')],
'js': [JSAsset(src='mytheme/custom.js', defer=True)],
}
}
How it works:
DjAdminViewMixincallsdjadmin_get_action_view_assets(action)hook- All plugins return asset dictionaries
- Assets are collected using
isinstance()matching on action type - Assets are merged and added to context as
assets.cssandassets.js - Base template loads them via
{% static %}
Load order: Plugins are processed in INSTALLED_APPS order, so list your theme first to ensure it loads before the default theme.
Theme Override Priority¶
Django's template loader respects INSTALLED_APPS order when APP_DIRS=True:
INSTALLED_APPS = [
'mytheme', # Priority 1 - checked first
'djadmin.plugins.theme', # Priority 2 - default theme
'djadmin', # Priority 3 - core templates
]
Template Resolution:
1. Django looks for template in mytheme/templates/
2. If not found, looks in djadmin/plugins/theme/templates/
3. If not found, looks in djadmin/templates/
CSS Cascade: - Multiple CSS files load in order (via plugin hooks) - CSS cascade rules apply (last wins for same specificity) - Your theme CSS overrides default theme CSS
Benefits of This Architecture¶
For End Users¶
✅ Fast loading - Pre-compiled CSS, no runtime bundler ✅ Accessible - Semantic HTML + proper ARIA labels ✅ Responsive - Mobile-first design ✅ Performant - Minimal CSS, efficient selectors
For Theme Developers¶
✅ Simple - Most themes = just CSS file ✅ Powerful - Full control via structural selectors ✅ Safe - No breaking changes to HTML structure ✅ Flexible - Override templates only if needed
For Project Maintainers¶
✅ Stable - HTML structure rarely changes ✅ Extensible - Plugin system for advanced features ✅ Compatible - Themes work across versions ✅ Debuggable - Clear separation of concerns
Inspecting the Default Theme¶
To understand the theme architecture better:
- Browse core templates:
djadmin/templates/djadmin/ - Compare theme override:
djadmin/plugins/theme/templates/djadmin/_navigation.html - Study the CSS:
djadmin/plugins/theme/static_src/input.css - Use DevTools: Inspect elements in browser to see selectors in action
- Check Tailwind config:
djadmin/plugins/theme/static_src/tailwind.config.js
Next Steps¶
- Learn about Dark Mode Implementation
- Follow the Custom Theme Guide
- Browse Example Themes