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.