How to Implement Efficient Dark Mode in Mobile Apps Using CSS Custom Variables
Implementing dark mode in the Qidian Reading app, this guide explains using CSS custom variables and a custom SCSS loader to achieve low‑maintenance theme switching across native and embedded web pages, handling low‑end devices, code examples, and best‑practice considerations.
Background
With the release of dark appearance in macOS and iOS, dark mode for mobile apps has become visible to the public, and many native apps are adapting to enhance user experience.
The Qidian Reading app recently adapted both native pages and embedded web pages. How can we adapt quickly while reducing development cost?
Solution Choice - CSS Custom Variables
Why use this solution?
Designers provide a "Color Mapping Specification" for front‑end developers.
We build a basic theme using CSS custom properties and switch classes to change all mapped colors, achieving a site‑wide skin change. Adding new skins later only requires new mapping rules.
The main advantages are:
Low maintenance cost: modify only color variables instead of each page.
Non‑destructive: adding or changing mappings does not affect existing styles or logic.
Visual consistency: pages using the theme system match design specifications.
Fast integration: adding a new theme mainly involves designers, especially for embedded H5 pages.
Core Implementation Process
1. Use the JSSDK to obtain the current color mode.
2. Add a mode class to the HTML element, e.g., dark-mode for dark mode.
<code>// Extracted from utils.js – get system color mode
utils.checkThemeMode().then(theme => {
const localTheme = window.localStorage.getItem('local_theme');
if (localTheme) theme = parseInt(localTheme);
this.$store.commit('setThemeModel', theme);
document.getElementsByTagName('html')[0].className = this.$store.state.global.themeClass;
console.log('系统的颜色模式:' + theme);
});
// Extracted from global.js – set system color mode
setThemeModel(state, value) {
state.themeMode = value;
switch (value) {
case 0:
state.themeClass = '';
break;
case 1:
state.themeClass = 'dark-mode';
break;
}
}</code>3. In dark-mode , replace all CSS variable colors according to the designer's mapping.
<code>/* Extracted from themeVar.scss and global.scss */
$--Primary_500: #E5353E;
:root {
--Primary_500: #{$--Primary_500};
}
.dark-mode {
--Primary_500: #FF4D55;
}</code>4. Store the selected theme in local storage ( local_theme ) to align with the JSSDK and provide fallback handling.
Problems Encountered
Handling Low‑End Devices
Older iOS (≤9.2) and Android (≤4.4) devices do not support CSS custom properties, but they account for less than 0.3% of users, allowing us to adopt the new approach with fallback handling.
We add a SASS fallback variable, e.g., $--Primary_500: #E5353E , and write styles like:
<code>color: $--Primary_500;
color: var(--Primary_500);</code>Eliminating Duplicate Declarations
To improve coding efficiency, we created a custom loader reset-scss-loader that automatically generates fallback CSS.
Original SCSS:
<code>.bottom-wrap {
color: $--Surface_500;
border: 1px solid $--Background_BW_White;
background: linear-gradient(270deg, $--Gradient_Red_500_end 0%, $--Gradient_Red_500_start 100%);
}</code>After loader processing:
<code>color: #808080;
color: var(--Surface_500);</code>New devices use the var(--Surface_500) line, while old devices ignore it, achieving graceful degradation. The loader is added to webpack:
<code>module: {
rules: [
{
test: /\.scss$/,
use: ['reset-scss-loader'],
exclude: path.resolve(__dirname, './src/assets/css/global.scss')
}
]
}</code>The loader works by regex‑matching SCSS declarations and appending a fallback line that replaces $-- variables with var(--...) :
<code>module.exports = str => {
return str.replace(/([a-z-]+:[^;]*\$--[^;]+;)/gu, $1 => $1 + $1.replace(/\$(--[^\s;]*)(\s|;)/g, 'var($1)$2'));
};</code>Scope Considerations
Since the $store state is attached to the top‑level div , we also need to add the dark-mode class to the html element before rendering special components like loading layers, ensuring global skin control.
Overall Adaptation Progress
Modules already adapted to dark mode include the new account page, sign‑in page, and membership card page.
Conclusion and Reflection
CSS custom variables are now supported by all major browsers, with compatibility solutions for older versions. Theme switching can be achieved more efficiently than traditional methods that required separate style files or per‑page updates, reducing maintenance cost and risk. Future enhancements may allow server‑side configuration to dynamically change CSS custom properties.
Qidian Reading App Experience
Authors: Liu Wentao, Tang Peiliang, Yang Ye
Yuewen Frontend Team
Click follow to learn the latest frontend insights in the cultural content industry. We welcome you to join us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.