'Ionic Dark Mode - Force Light/Dark and System theme
I would like to add a toggle for my Ionic project that controls the theme of the entire application. The toggle can force a light mode/dark mode irrespective of what the system mode is (ie. stays dark mode even if the system turns from dark -> light) and also an option to toggle back to automatic.
Currently what I have done is follow the Ionic Conference App https://github.com/ionic-team/ionic-conference-app and what I've done to my base project is:
- Remove
@media (prefers-color-scheme: dark)
fromvariables.scss
and add.dark-theme
class to the media queries, ie.body
->.dark-theme
,.ios body
->.dark-theme.ios
- Add an
ion-segment
to my settings page:
<ion-label position="fixed">Mode</ion-label>
<ion-segment
[(ngModel)]="modeService.mode"
(ionChange)="modeService.setMode()"
>
<ion-segment-button value="light">
<ion-label>Light</ion-label>
</ion-segment-button>
<ion-segment-button value="dark">
<ion-label>Dark</ion-label>
</ion-segment-button>
<ion-segment-button value="auto">
<ion-label>Auto</ion-label>
</ion-segment-button>
</ion-segment>
- in my
modeService.ts
file (which is the service I use to save the mode to the storage and handle the mode change:
dark: boolean;
mode = 'auto';
prefDark = window.matchMedia('(prefers-color-scheme: dark)');
constructor() { }
setMode = async (): Promise<void> => {
const storeMode = this.mode;
await Storage.set({
key: 'mode',
value: storeMode
});
if (this.mode !== 'auto') {
this.dark = (this.mode === 'dark') ? true : false;
} else {
this.dark = this.prefDark.matches;
this.prefDark.addEventListener('change', e => {
this.dark = e.matches;
});
}
};
checkMode = async (): Promise<void> => {
const { value } = await Storage.get({ key: 'mode' });
if (value) {
this.mode = value;
}
};
}
- In
app.component.html
I added[class.dark-theme]="modeService.dark"
to<ion-app>
- In
app.component.ts
:
export class AppComponent implements OnInit{
prefDark = window.matchMedia('(prefers-color-scheme: dark)');
constructor(
private themeService: ThemesService,
public teamService: TeamService,
private helpService: HelpService,
public modeService: ModeService,
private router: Router,
public modalController: ModalController,
private menuController: MenuController,
) { }
async ngOnInit() {
await ... /// some other theme change
if (this.modeService.mode === 'auto') {
this.modeService.dark = this.prefDark.matches;
this.prefDark.addEventListener('change', e => {
this.modeService.dark = e.matches;
});
}
}
The problem is, the toggle work but say when I have the segment to dark
and the OS theme is changed to dark
, then I toggle the OS theme to light
the app turns back to the light theme and does not keep the dark theme.
Solution 1:[1]
I remade your question in my own project. There are a few changes that I made, most notably to the modeService.ts
. (I changed the name to ColorModeService)
export class ColorModeService {
dark: boolean;
mode = 'auto';
constructor() {}
setMode = (e: CustomEvent<SegmentChangeEventDetail>): void => {
const storeMode = this.mode;
const selectedMode = e.detail.value ?? 'auto';
localStorage.setItem('mode', storeMode);
switch (selectedMode) {
case 'auto':
this.dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
break;
case 'dark':
this.dark = true;
break;
case 'light':
this.dark = false;
break;
default:
break;
}
};
}
First I pass the $event
into the function, instead of using ngModel. The logic you had was not working for me, so I changed it to a simple switch case. I also removed the prefDark
variable, so the query gets run every time. These two changes solved the problem you describe.
Sidenote: to get this to work on Android devices, you'll need to change:
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
in android/app/main/red/values/styles.xml
to <style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Willem de Vries |