Skip to content

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:

<!-- ✅ Semantic approach (what we use) -->
<header>
  <h1>Title</h1>
</header>

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):

<div class="admin">
  <!-- All styles scoped to .admin -->
</div>

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):

<html class="dark">
  <!-- Dark mode styles applied -->
</html>

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

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:

  1. Create the template directory structure:

    mytheme/
    └── templates/
        └── djadmin/
            └── _navigation.html  # Your override
    

  2. Place your app BEFORE others in INSTALLED_APPS:

    INSTALLED_APPS = [
        'mytheme',  # Your theme - loads first
        'djadmin.plugins.theme',  # Default theme
        'djadmin',  # Core
    ]
    

  3. Django's template loader will find djadmin/_navigation.html in mytheme/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:

  1. Structural Selectors - .admin > header, .admin header nav > a
  2. Child Combinators (>) - For direct children
  3. Descendant Combinators (space) - For any descendant
  4. Pseudo-selectors - :hover, :focus, :has(), :not()
  5. Dark Mode Variants - .dark .admin > header

Viewing the Complete CSS

To see all available selectors and styling patterns:

  1. Browse the source: djadmin/plugins/theme/static_src/input.css
  2. 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:

  1. DjAdminViewMixin calls djadmin_get_action_view_assets(action) hook
  2. All plugins return asset dictionaries
  3. Assets are collected using isinstance() matching on action type
  4. Assets are merged and added to context as assets.css and assets.js
  5. 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:

  1. Browse core templates: djadmin/templates/djadmin/
  2. Compare theme override: djadmin/plugins/theme/templates/djadmin/_navigation.html
  3. Study the CSS: djadmin/plugins/theme/static_src/input.css
  4. Use DevTools: Inspect elements in browser to see selectors in action
  5. Check Tailwind config: djadmin/plugins/theme/static_src/tailwind.config.js

Next Steps