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¶
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¶
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¶
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:
Usage:
Publishing to PyPI¶
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