diff --git a/docs/configuration/theme.md b/docs/configuration/theme.md index a34a69067..07d42e8fc 100644 --- a/docs/configuration/theme.md +++ b/docs/configuration/theme.md @@ -31,3 +31,5 @@ There are currently 3 available themes for Authelia: * light (default) * dark * grey + +To enable automatic switching between themes, you can set `theme` to `auto`. The theme will be set to either `dark` or `light` depending on the user's system preference which is determined using media queries. To read more technical details about the media queries used, read the [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme). diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 74ae25dce..4aaf09f64 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -17,7 +17,7 @@ port: 9091 ## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem. # certificates_directory: /config/certificates -## The theme to display: light, dark, grey. +## The theme to display: light, dark, grey, auto. theme: light ## diff --git a/internal/configuration/validator/theme.go b/internal/configuration/validator/theme.go index d1b67929c..6fdaa1066 100644 --- a/internal/configuration/validator/theme.go +++ b/internal/configuration/validator/theme.go @@ -13,8 +13,9 @@ func ValidateTheme(configuration *schema.Configuration, validator *schema.Struct configuration.Theme = "light" } - validThemes := regexp.MustCompile("light|dark|grey") + validThemes := regexp.MustCompile("light|dark|grey|auto") + if !validThemes.MatchString(configuration.Theme) { - validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\" or \"grey\"", configuration.Theme)) + validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"", configuration.Theme)) } } diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go index 054f61247..1519b49d1 100644 --- a/internal/configuration/validator/theme_test.go +++ b/internal/configuration/validator/theme_test.go @@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() { suite.Assert().False(suite.validator.HasWarnings()) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "Theme: invalid is not valid, valid themes are: \"light\", \"dark\" or \"grey\"") + suite.Assert().EqualError(suite.validator.Errors()[0], "Theme: invalid is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"") } func TestThemes(t *testing.T) { diff --git a/web/src/App.tsx b/web/src/App.tsx index 48402a15b..a672fa843 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { config as faConfig } from "@fortawesome/fontawesome-svg-core"; import { CssBaseline, ThemeProvider } from "@material-ui/core"; @@ -37,6 +37,8 @@ function Theme() { return themes.Dark; case "grey": return themes.Grey; + case "auto": + return window.matchMedia("(prefers-color-scheme: dark)").matches ? themes.Dark : themes.Light; default: return themes.Light; } @@ -44,9 +46,20 @@ function Theme() { const App: React.FC = () => { const [notification, setNotification] = useState(null as Notification | null); - + const [theme, setTheme] = useState(Theme()); + useEffect(() => { + if (getTheme() === "auto") { + const query = window.matchMedia("(prefers-color-scheme: dark)"); + // MediaQueryLists does not inherit from EventTarget in Internet Explorer + if (query.addEventListener) { + query.addEventListener("change", (e) => { + setTheme(e.matches ? themes.Dark : themes.Light); + }); + } + } + }, []); return ( - +