Skip to content

Creating a Custom Theme

This guide walks you through creating a custom theme for django-admin-deux.

Quick Start: CSS-Only Theme

The simplest way to create a theme is to provide custom CSS that overrides the default styles.

Step 1: Create a Django App

python manage.py startapp mytheme

Step 2: Add to INSTALLED_APPS

Critical: Add your theme BEFORE djadmin.plugins.theme and djadmin:

# settings.py
INSTALLED_APPS = [
    'mytheme',  # Your theme - highest priority
    'djadmin.plugins.theme',  # Default theme
    'djadmin',  # Core
    # ... other apps
]

Step 3: Create the Plugin Hook File

File: mytheme/djadmin_hooks.py

from djadmin.plugins import hookimpl
from djadmin.actions.base import BaseAction

@hookimpl
def djadmin_provides_features():
    """Advertise that this plugin provides a theme"""
    return ['theme']

@hookimpl
def djadmin_get_action_view_assets(action):
    """Provide custom CSS for all views"""
    from djadmin import CSSAsset
    return {
        BaseAction: {  # Apply to all actions/views
            'css': [CSSAsset(href='mytheme/custom.css')],
        }
    }

Step 4: Create Your CSS

File: mytheme/static/mytheme/custom.css

Start by overriding a few key colors:

/* Change primary color from Django Green to Blue */
.admin > header {
  background-color: #1e40af !important;
  border-bottom-color: #1e3a8a !important;
}

.admin > header > nav > a {
  color: white !important;
}

/* Update table headers */
.admin table thead {
  background-color: #dbeafe;
}

.admin table th {
  border-bottom-color: #93c5fd;
}

/* Update primary buttons */
.admin button.primary,
.admin a.primary {
  background-color: #1e40af;
}

.admin button.primary:hover,
.admin a.primary:hover {
  background-color: #1e3a8a;
}

/* Update pagination active page */
.admin nav[aria-label="Pagination"] span[aria-current] {
  background-color: #1e40af;
}

Step 5: Test Your Theme

python manage.py runserver

Visit the admin interface - you should see your blue theme instead of the default green!

Intermediate: Theme with Dark Mode

To add dark mode support, you need to override the navigation template and provide dark mode CSS variants.

Step 1-3: Same as CSS-Only Theme

Follow steps 1-3 above.

Step 4: Override Navigation Template

File: mytheme/templates/djadmin/_navigation.html

Copy from djadmin/plugins/theme/templates/djadmin/_navigation.html or create your own navigation with a dark mode toggle button.

The dark mode toggle needs id="dark-mode-toggle" for the JavaScript to work.

Step 5: Include Default Dark Mode JavaScript

Update your djadmin_hooks.py:

@hookimpl
def djadmin_get_action_view_assets(action):
    from djadmin import JSAsset, CSSAsset
    return {
        BaseAction: {
            'css': [CSSAsset(href='mytheme/custom.css')],
            'js': [JSAsset(src='djadmin/theme/js/admin.js', defer=True)],  # Default dark mode JS
        }
    }

Step 6: Create CSS with Dark Mode Variants

File: mytheme/static/mytheme/custom.css

/* Light mode styles */
.admin > header {
  background-color: #1e40af;
  border-bottom-color: #1e3a8a;
}

.admin main table {
  background-color: white;
  color: #111827;
}

/* Dark mode styles - prefix with .dark */
.dark .admin > header {
  background-color: #1e3a8a;
  border-bottom-color: #1e40af;
}

.dark .admin main table {
  background-color: #1f2937;
  color: #f3f4f6;
}

/* Continue with all your color overrides... */

See: Dark Mode Implementation Guide for complete details.

Advanced: Full Custom Theme with Tailwind

For complete control, you can build your own Tailwind-based theme.

Step 1: Create Theme App with Build Process

python manage.py startapp mytheme
cd mytheme
npm init -y
npm install -D tailwindcss@^3.4.1 @tailwindcss/forms@^0.5.7

Step 2: Create Tailwind Config

File: mytheme/static_src/tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    // Point to your project's templates
    '../templates/**/*.html',
    '../../djadmin/templates/**/*.html',
  ],
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          100: '#dbeafe',
          200: '#bfdbfe',
          300: '#93c5fd',
          400: '#60a5fa',
          500: '#3b82f6',  // Your primary color
          600: '#2563eb',
          700: '#1d4ed8',
          800: '#1e40af',
          900: '#1e3a8a',
        },
      },
    },
  },
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

Step 3: Create Source CSS

File: mytheme/static_src/input.css

@config "./tailwind.config.js";

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  html {
    @apply h-full;
    font-family: Inter, system-ui, sans-serif;
  }

  body {
    @apply h-full bg-gray-100 text-gray-900 antialiased;
  }

  .dark body {
    @apply bg-gray-950 text-gray-100;
  }
}

@layer components {
  /* Copy and modify selectors from default theme */
  /* See: djadmin/plugins/theme/static_src/input.css */

  .admin {
    @apply min-h-full flex flex-col;
  }

  .admin > header {
    @apply bg-primary-500 border-b-4 border-primary-600;
  }

  .dark .admin > header {
    @apply bg-primary-900 border-b-4 border-primary-700;
  }

  /* ... continue with all components */
}

Step 4: Add Build Scripts

File: mytheme/package.json

{
  "name": "mytheme",
  "version": "0.1.0",
  "scripts": {
    "build": "tailwindcss -i ./static_src/input.css -o ./static/mytheme/theme.css --minify",
    "watch": "tailwindcss -i ./static_src/input.css -o ./static/mytheme/theme.css --watch"
  },
  "devDependencies": {
    "@tailwindcss/forms": "^0.5.7",
    "tailwindcss": "^3.4.1"
  }
}

Step 5: Build CSS

cd mytheme
npm run build

Step 6: Update Plugin Hook

File: mytheme/djadmin_hooks.py

from djadmin.plugins import hookimpl
from djadmin.actions.base import BaseAction

@hookimpl
def djadmin_provides_features():
    return ['theme']

@hookimpl
def djadmin_get_action_view_assets(action):
    from djadmin import JSAsset, CSSAsset
    return {
        BaseAction: {
            'css': [CSSAsset(href='mytheme/theme.css')],  # Your compiled CSS
            'js': [JSAsset(src='djadmin/theme/js/admin.js', defer=True)],  # Or your own JS
        }
    }

Theme Customization Tips

Finding Selectors to Override

Use Browser DevTools: 1. Right-click element → Inspect 2. See applied CSS selectors 3. Copy selector to your CSS file 4. Override with your styles

Reference the Default Theme: Browse djadmin/plugins/theme/static_src/input.css for all available selectors.

Color Strategy

Light Mode: - Backgrounds: Light grays (bg-gray-50, bg-gray-100, bg-white) - Text: Dark grays (text-gray-900, text-gray-700) - Borders: Medium grays (border-gray-300, border-gray-200)

Dark Mode: - Backgrounds: Dark grays (bg-gray-950, bg-gray-900, bg-gray-800) - Text: Light grays (text-gray-100, text-gray-200) - Borders: Dark grays (border-gray-800, border-gray-700)

Responsive Design

The default theme uses max-width: 767px for mobile breakpoint. Include mobile styles:

/* Desktop (default) */
.admin table {
  display: table;
}

/* Mobile */
@media (max-width: 767px) {
  .admin table {
    display: block;
  }

  .admin table tr {
    display: block;
    margin-bottom: 1rem;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
  }

  /* ... more mobile styles */
}

Distributing Your Theme

As a Reusable Django App

Structure:

django-admin-mytheme/
├── mytheme/
│   ├── __init__.py
│   ├── djadmin_hooks.py
│   ├── static/
│   │   └── mytheme/
│   │       └── theme.css
│   └── templates/  # Optional overrides
│       └── djadmin/
│           └── _navigation.html
├── setup.py
└── README.md

Installation:

pip install django-admin-mytheme

Usage:

INSTALLED_APPS = [
    'mytheme',  # Just add to INSTALLED_APPS
    'djadmin',
    # ...
]

Publishing to PyPI

python setup.py sdist bdist_wheel
twine upload dist/*

Troubleshooting

Styles not applying

Check: 1. Is app in INSTALLED_APPS before djadmin.plugins.theme? 2. Is static file collected? Run python manage.py collectstatic 3. Is CSS selector specific enough? Use !important if needed 4. Check browser console for 404 errors

Dark mode not working

Check: 1. Is _navigation.html overridden with dark mode toggle? 2. Is dark mode JS loaded? Check assets.js in context 3. Are .dark CSS variants defined? 4. Is .dark class on <html> element? (Check with DevTools)

Template override not working

Check: 1. Is app listed BEFORE djadmin.plugins.theme in INSTALLED_APPS? 2. Is template path correct? Must be templates/djadmin/_navigation.html 3. Restart Django server after adding templates

Examples

See the examples directory for complete working themes: - Blue Theme (CSS-only) - Dark Theme (with dark mode) - Material Theme (full Tailwind rebuild)

Next Steps

  • Review the Architecture Guide to understand the design philosophy
  • Check the Dark Mode Guide for dark mode implementation details
  • Browse the default theme source for complete reference