Tech Brief django CMS dark mode
=============================================================================
###### tags: `workgroup`
Since Django 3.2 the built-in admin app comes with built-in support of dark mode css styles. Many developer use it - also when evaluating software. django CMS uses the admin for front-end editing, page admin and so forth.
## Workgroup Members
Add names here
## Workroup Leader
Fabian
## Workgroup Goals
- Make **django CMS compatible with dark-mode of Django**
- Support **dark mode with djangocms-admin-style**
- Develop **guidlines on how django cms apps and third party apps** should use color styling so that the whole system consistently displays light and dark content
- Check that all solutions are independent of djangocms-admin-style and also work with plain Django.
- **Have fun** with dark mode!
## Links
- [Django 3.2 theming](https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#admin-theming)
- [Django 3.2 css color variables in admin.css](https://github.com/django/django/blob/main/django/contrib/admin/static/admin/css/base.css)
## Milestones
✅ Done, PR merged and released.
☑️ Done, PR created and reviewed.
⚙️ Work in progress
| status | date | description |
| ------ | ---------- | ----------- |
| ✅ | 27.03.2022 | Dark mode for djangocms-admin-style |
| ✅ | 04.04.2022 | Dark mode for djangocms-text-ckeditor |
| ☑️ | 12.03.2022 | Dark mode for django CMS v3 |
| ⚙️ | xx.xx.xxxx | Guidelines for admin styles in CMS apps |
| ⚙️ | xx.xx.xxxx | Dark mode for djangocms-alias |
| ⚙️ | xx.xx.xxxx | Dark mode for djangocms-versioning |
| ⚙️ | xx.xx.xxxx | Dark mode for django CMS v4 |
## Tentative Roadmap
- Q1 - Implement dark mode for django CMS v3 core and djangocms-admin-style
- Q2 - Develop and decide on guidelines
- Q3 - Adapt core apps for dark mode
- Q3 - Improvement of colors
## Approximate specification
Main points:
- Replace fixed color values in css by css variables
- Replace scss functions like `lighten`, `darken`, and `rgba` which do not work with css variables by css filters (e.g., `brightness()`)
- Define CMS color variables on existing django CMS styles for both django CMS core and djangocms-admin-style
- Use CMS color variables with (the smaller set of) django variables as fall back (in case djangocms-admin-style is not installed).
### CMS colors
#### CMS color list
The django CMS core styling uses a set of grayscale colors. See [`cms/static/cms/sass/settings/_cms.scss`](https://github.com/django-cms/django-cms/blob/develop/cms/static/cms/sass/settings/_cms.scss)
These are the colors designed for light mode:
| Name | Color | Variable name |
| ----------------- | ------- | ------------------------- |
| `$white` | #ffffff | `--dca-white` |
| `$black` | #000000 | `--dca-black` |
| `$color-primary` | #0bf | - |
| `$color-danger` | #669933 | - |
| `$color-warning` | #ff0000 | - |
| `$gray` | #666 | `--dca-gray` |
| `$gray-lightest` | #f2f2f2 | `--dca-gray-lightest` |
| `$gray-lighter` | #ddd | `--dca-gray-lighter` |
| `$gray-light` | #999 | `--dca-gray-light` |
| `$gray-darker` | #454545 | `--dca-gray-darker` |
| `$gray-darkest` | #333 | `--dca-gray-darkest` |
| `$gray-super-lightest`|#f7f7f7 | `--dca-gray-super-lightest` |
The `dca-` prefix is used to avoid conflicts with other css variables and stands for "django CMS association".
### Fallback colors
The admin frontend pulls the style from django admin styles and - if present - from djangocms-admin-style. Django itself also uses css variables to implement admin mode, these can be used as dark mode-aware fall-back colors.
| Variable name | Color | Fallback | Color |
| ----------------- | ------- | ----------- | ----------- |
| `--dca-white` | #ffffff | `--body-bg` | #ffffff |
| `--dca-black` | #000000 | `--body-fg` | #303030 |
| `--dca-gray` | #666 | `--body-quiet-color` | #666 |
| `--dca-gray-lightest` | #f2f2f2 | `--darkened-bg`| #f8f8f8 |
| `--dca-gray-lighter` | #ddd | `--border-color` | #ccc |
| `--dca-gray-light` | #999 | `--close-button-bg` | #888 |
| `--dca-gray-darker` | #454545 | | |
| `--dca-gray-darkest` | #333 | | |
| `--dca-gray-super-lightest` | #f7f7f7 | | |
| `--dca-primary` | #00bbff | `--primary` | #79aec8 |
If at all posible **only the first four fallbacks should be used**. Other django admin css variables might be overwritten by a theme and might not represent a level of gray.
### Mode selection
Three modes may be selected:
auto
: The displayed color scheme is set by the browser/OS. This is the default behavior.
light
: Light mode is enforced (e.g., to support plugins that are not compatible with dark mode). Add `data-color-scheme="light"` to the document's `html` tag, i.e., `<html data-color-scheme="light">`.
dark
: Dark mode is enforced. Add `data-color-scheme="dark"` to the document's `html` tag.
### Recommendation draft for cms apps (django cms association and third party)
- Try avoid using `color`, `background` etc. styles where possible and meaningful
- If necessary use as few as possible standard django CMS colors (preferably from see above list with fallback colors)
- Usage: ``var(--dca-color-var, var(--fallback-color-var, #xxxxxx))`` where `#xxxxxx` represents the light version of the color.
- Avoid media queries like `@media (prefers-color-scheme: dark)` since they would ignore forced settings to light or dark.
### Edge case 3rd party libraries
Third party libraries might present an edge case: CKEditor 4, for example, does not offer dark mode compatible skins for djangocms-text-ckeditor. A similar situation exists for the code editor Ace which is used by djangocms-frontend or djangocms-snippet. Two options exist:
1. For **CKEditor 4** we patch the standard skin during the build process of the editor. The patch replaces color literals by ``var(--dca-color-var, var(--fallback-color-var), #color)`` in the already minified css. See [this python scrippt.](https://github.com/django-cms/djangocms-text-ckeditor/blob/master/private/patch_moono_lisa.py)
3. For **ACE** dark color designs exist. When invoking the editor in javascript we check for the preferred mode (with the drawback that after invoking the editor a change in preference by the user is only reflected after a reload of the page):
// init editor with settings
var editor = ace.edit(div[0]);
var darkMode = False;
if (window.parent.CMS.API.Toolbar.get_color_scheme) {
darkMode = window.parent.CMS.API.Toolbar.get_color_scheme() === 'dark';
} else {
// django CMS pre-3.11
darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
if (darkMode) {
editor.setTheme('ace/theme/tomorrow_night');
} else {
editor.setTheme('ace/theme/github');
}
Of course, an improved implementation will not inline the dark and light themes as literals but revert to settings to allow for better configurability.