TL;DR
This article describes three things:
- How I’ve created a custom dark mode theme of Google Maps to be embedded in a web app, by using Google Maps Platform style editor (Section 2)
- JavaScript coding techniques to switch on the dark mode map style after 6pm, and off after 6am, of the user’s local time. (Section 3)
- An example of the user experience to be achieved with this feature of the app that I’m making, for someone living in Kyoto, that is, me myself :-). (Section 5)
1. Motivation
I’m creating My Ideal Map App, a web app that improves the UX of Google Maps for saving the places of the user’s interest (for detail, see Day 1 of this blog series).
One UX improvement is to switch the map style into the dark mode in the evening automatically. I want the app to reflect the world outside the screen.
The Google Maps app allows us to turn on the dark mode, but it won’t switch between light and dark modes depending on the time of the day. I find a map in the dark mode appears awkward at daytime. It looks incongruent to the outside world when the sun is out. It is difficult to see the map clearly as the entire screen is dark while the rest of the world is bright. However, it’s tedious to switch between light and dark modes manually, depending on the time of the day.
Switching on the dark mode in the evening also makes sense because the user's mobile device is likely to be near out of battery after spending a day outside. With OLED screens of the mobile devices released recently (e.g., iPhone X, 11, 12), the user interface in the dark mode reduces battery consumption (for why, see Purdy 2019; for evidence, see PhoneBuff 2019).
Plus, a dark mode UI reduces the amount of "blue light" that reaches to our eyes. Blue light awakens us, suppressing our inner body to release melatonin, a hormone needed for falling asleep (Harvard Health Publishing 2020). Switching on the dark mode in the evening, only a few hours before going to bed, will make sense to ensure good sleep at night.
(On a different note, there seems no evidence that the dark mode reduces eye strains; see Clarke 2019 and Laderer 2021).
Of course, the best UX design is to let the user choose whether to set the dark mode or not (Babich 2019). But the default setting should be what the app designer/developer believes is the best for the user.
The rest of this article explains how, from both design and coding perspectives, I implement this feature in My Ideal Map App, which embeds Google Maps in its user interface.
2. Designing the dark mode style of Google Maps
The first step is to design a custom dark theme for the Google Maps to be embedded in My Ideal Map App.
To customize the color scheme of Google Maps to be embedded in an app, we need to use Google Maps Platform's style editor (for more detail, see Section 2 of Day 4 of this blog series.
Defining the style of each map element in Google Maps always takes three steps: choose (1) Feature type (roads, water, shops, etc.); (2) Element type (shape or text label); and (3) Stylers (color and whether to be visible or hidden):
A screenshot of Google Maps Platform's cloud-based style editor
And I do this in seven steps.
2.1 Desaturation
First of all, I set the entire map to be rendered in black-and-white.
To do so with the style editor, I desaturate the default style of all map elements of Google Maps:
- Feature type:
All
- Element type:
All
- Stylers > Saturation:
-100
Which renders a map like this:
A map of central Kyoto, with saturation set to be -100 (screenshot by the author)
Fully desaturating all the map elements makes it easy to style the map: if I add color to a particular set of map elements, they will be visually noticeable so I can check whether they are rendered in an intended way. Also, I don’t have to set the color of those map elements that I don’t really care about: they will be in black-and-white, less noticeable but still visible for those users who need them.
Now, I’m going to set the color of map elements in the order of darkness, not brightness. When I set the daytime color scheme, I started with the brightest element (see Section 4 of Day 4 of this blog series). For the nighttime color scheme, however, starting with the brightest may end up with the need for a color darker than pure black. :-)
For daytime, I set the luminance level to be the highest for streets, the second highest for green spaces, the third for waterways, and the fourth for city blocks (see, again, Section 4 of Day 4 of this blog series for why). I want to keep this order of luminance, to minimize the difference in the user's perception of map color schemes by day and night. This way, the integrity of an app will be maintained.
So I start with the darkest among the four: city blocks.
2.2 City blocks
By city blocks, I mean the area surrounded by city streets. Google Maps Platform’s style editor doesn’t allow us to directly set the color code for city blocks. A workaround that I have found is to set the lightness
value for all features:
- Feature type:
All
- Element type:
All
- Stylers > Lightness:
-77
which renders the map of central Kyoto like this:
A map of central Kyoto, with lightness set to be -77 (screenshot by the author)
Together with the saturation of -100, the lightness of -77 will render city blocks in #2b2b2b
.
2.2.1 Why not pure black?
I could use pure black instead of #2b2b2b, but it would give an unsophisticated feel to the user interface, simply because anyone can pick pure black without thinking much. Pure black would also remind us (or at least me) of the web design before 2010 such as MySpace. Such a retro feel is not appropriate for My Ideal Map App, which aims to be an app that no one has created.
Furthermore, with OLED screens of the recent mobile devices such as iPhone X, 11, 12, pure black in user interface is known to cause "black smearing" when the screen is scrolled (see, for example, Edwards 2018).
When a UI component in pure black is scrolled on an OLED screen, the speed with which an LED light turns on (to change from pure black to another color) is slower than the scroll speed. As a result, the black part of the UI gets blurred, causing an unpleasant visual effect. (Special thanks goes to Atharva Kulkarni whose article and reply to my comment enlightened me about the “black smearing” problem.)
The problem is also mentioned in Material Design, Google’s UI design guideline, cautioning the use of pure black due to the following reason:
On OLED screens, turning pixels on and off can cause a delay when the screen is scrolled, making the pixels blur.
So pure black is recommended only if the web page doesn't scroll at all (like google.com). Which is clearly not applicable to any map application in which the user swipes to move the map around.
2.2.2 Which shade of dark gray, then?
Then the issue is how much dark the darkest shade of gray should be in the dark mode color scheme. Material Design recommends #121212, but it doesn’t give any rationale.
I want to pick the darkest shade of gray to be noticeably different from pure black, to avoid any connotation with pre-2010 web design. My rule-of-thumb for a color to be perceptually different from another is to change the level of luminance by 1.5 times. #2b2b2b
is the shade of gray whose luminance is about 1.5 times brighter than pure black:
A screenshot of contrast-ratio.com, showing the ratio of contrast in luminance between pure black (left) and the dark gray for city blocks (right) (screenshot by the author)
2.3 Waterways
For styling waterways:
- Feature type:
Water
- Element type:
Geometry
- Stylers > Color:
#344d4d
which renders the map as follows:
A map of central Kyoto, with rivers and canals in dark cyan (#344d4d
) (screenshot by the author)
We now see Kamo River, on the bank of which young couples always sit at an equal distance from each other in the evening. :-)
As discussed in Section 4.4 of Day 4 of this blog series, the color of water is cyan. So the hue for waterways should be cyan, that is, the H value of 180 in the HSL color space.
For luminance, I want waterways to be 1.5 times brighter than city blocks so they are perceptually different. This means that the luminance contrast ratio against pure black is at least 2.25 to 1, where 2.25 is derived as the square of 1.5.
For saturation, I pick the shade of cyan obtained by mixing 10% pure cyan with 90% gray. (See Section 4.3 of Day 4 of this blog series for why I set saturation this way.)
Using Triangulum Color Picker, a free web app that I've built, I find #344d4d
to satisfy these three requirements for the color of waterways in the dark mode.
Triangulum Color Picker's user interface, showing the color code of #344d4d (screenshot by the author)
2.4 Green spaces
I set the color of green spaces (mountains and city parks) for night mode as follows:
For mountains:
- Feature type:
Landscape > Natural
- Element type:
Geometry
- Stylers > Color:
#4c664c
For city parks:
- Feature type:
Points of interest > Park
- Element type:
Geometry
- Stylers > Color:
#4c664c
Which renders the map as follows:
A map of central Kyoto, with green spaces in dark green (#4c664c
) (screenshot by the author)
Pontocho Park, a little city park in the middle of an endless sequence of dinner restaurants and drinking bars in Pontocho District, is now rendered in a dark shade of green.
As discussed in Section 4.3 of Day 4 of this blog series, green spaces should be rendered in a shade of green, to be in line with the user's expectation for a map. The hue is therefore the H value of 120 in the HSL color space.
For luminance, green spaces should be at least 1.5 times brighter than waterways. As waterways themselves are 1.5 times brighter than city blocks, this level of luminance ensures the perceptual difference of green spaces from both waterways and city blocks. This means that the shade of green for green spaces should have a luminance contrast ratio of 3.375 to 1 against pure black, where 3.375 is derived as 1.5 to the power of 3.
For saturation, I pick the shade of green that is a mixture of 10% pure green with 90% gray so that color harmony is achieved with the shade of cyan for waterways. As discussed in Section 4.4 of Day 4 of this blog series, it was Wilhelm Ostwald who advocated for equal shares of pure hue to achieve color harmony. I don't know why he reached this conclusion (let me know if you're aware of his reasoning). A possible reason is that such a color palette resembles what we see in nature, where the uneven surface of an object in nature reflects different amounts of sunlight that reach our eyes, causing the object’s own color to be mixed with gray of a wide range of luminance.
With Triangulum Color Picker, I find #4c664c
to satisfy these three requirements for the color of waterways in the night mode.
Triangulum Color Picker's user interface, showing the color code of #4c664c
(screenshot by the author)
2.5 Streets
I set streets to be rendered in a shade of orange as follows:
- Feature type:
Road
- Element type:
Geometry > Fill
- Stylers > Color:
#ae6f2f
I also remove the outline of streets (see Section 4.2 of Day 4 of this blog series for why).
- Feature type:
Road
- Element type:
Geometry > Stroke
- Stylers > Visibility:
Off
Which renders the map like this:
A map of central Kyoto, with streets in dark orange (#ae6f2f
) (screenshot by the author)
The crossing at the bottom-left is Shijo-Kawaramachi, where street musicians such as Kocchi and Juuichi would start performing in the evening.
Here's my thought process that led to the choice of a shade of orange for streets:
- When I analyzed the 33 map themes in Place.Guru to learn about the do's and don'ts for designing a map color scheme (see Section 3 of Day 4 of this blog series), the map theme called Limos Night caught my attention, rendering streets in dark brown against the background of dark blue gray. For some reason, it appeared natural to me as a map at night. It was like seeing a city in the evening from an observatory tower.
- I noticed why, when I found the following image of Paris in the evening for making a mood board to guide the dark mode UI design of My Ideal Map App (see Day 3 of this blog series for detail). Streets in a shade of brown reminded me of the night view of a city, because brown is a dark shade of brown:
Aerial view of the Arc de Triomphe in Paris in the evening (image source: unknown)
The design concept of My Ideal Map App is "Dye Me In Your Hue From The Sky" (see Day 2 of this blog series for detail), which means that the user, by saving the places of their interest, dyes the map—a landscape view from the sky—in their own "color". Consequently, the color scheme of the map needs to be similar to the aerial view of a city.
And this photo of Paris in the evening, with streets illuminated in orange light, beautifully visualizes this design concept. I use this image to guide the color choice of streets in the night mode.
Shades of orange have the H value of 30 in the HSL color space. A value lower than this value (i.e., towards more reddish orange) will look too bright. A value higher than this value (i.e., towards more yellowish orange) will look ugly: dark shades of yellow resemble, well, what comes out of our body...
For luminance, I want streets to be 1.5 times brighter than green spaces (which was set as 1.5 times brighter than waterways, which in turn was set as 1.5 times brighter than city blocks, as described above).
Consequently, the luminance contrast between streets and city blocks is 3.375 to 1, where 3.375 is 1.5 to the power of 3. This ratio satisfies the Web Content Accessibility Guideline's recommendation of 3 to 1 for UI components (§1.4.11 of WCAG 2.1).
Streets are UI components on a digital map. When we swipe the screen to move the map, one possibility is to swipe along a street as if we were moving along that street. Consequently, unlike other map elements such as green spaces, streets should clearly be distinguishable from their surroundings. The 3-to-1 contrast ratio rule ensures it.
Since city blocks were set to be 1.5 times brighter than pure black, streets will be 5.0625 times brighter than pure black, with 5.0625 derived as 1.5 to the power of 4.
For saturation, I pick a shade of orange as a mixture of 50% pure orange with 50% gray. If the ratio of pure orange is lower, the color becomes purely brown. If it's higher than 50%, the color will be too bright. I find the value of 50% striking the right balance to be bright enough to convey an impression of street illumination in Paris while not being too bright to be glaring in the otherwise dark color scheme.
With Triangulum Color Picker, I find #ae6f2f
to satisfy these three requirements for the color of streets in the night mode:
Triangulum Color Picker's user interface, showing the color code of #ae6f2f
(screenshot by the author)
2.6 Buildings
I set the style of buildings as follows:
For disabling its default 3D rendering (see Section 4.6 of Day 4 of this blog series for why):
- Feature type:
Lanscape > Human-made > Buildings
- Element type > Building style:
Footprints
For setting the color of outlines:
- Feature type:
Landscape > Human-made > Buildings
- Element type:
Geometry > Stroke
- Stylers > Color:
#757575
Which renders the map like this:
A map of central Kyoto, with building outlines in medium gray (#757575
) (screenshot by the author)
We now see the densely clustered buildings in Kyoto's night-life districts shown in the map.
I set the color for outlines only. Coloring the entire rectangles for buildings will change the overall impression of the map. I do not want buildings to take that crucial role in UI design: they are not the most important map elements.
Why gray for the outlines, then?
For the outlines of buildings, I initially picked a shade of orange because buildings would emit light through windows. But when I tried it, the buildings looked too noticeable, competing with streets for the user's attention. It was also difficult to distinguish it from narrow alleyways due to similar shades of orange. By choosing a shade of gray, the buildings remain part of the background, with streets the most noticeable map elements.
Which shade of gray, then?
Initially I tried #494949
because its luminance level is 1.5 times brighter than city blocks. This strategy worked for daytime mode (see Section 4.6 of Day 4 of this blog series), but with much darker gray of city blocks, the 1.5-to-1 ratio of contrast in luminance is not enough to make buildings perceptually different enough from city blocks.
So I go for a shade of gray whose luminance is 3 times brighter than city blocks: #757575
, referring to the 3-to-1 ratio of contrast in luminance for UI components (§1.4.11 of WCAG 2.1).
A screenshot of contrast-ratio.com, showing the ratio of contrast in luminance between the dark gray for city blocks (left) and the medium gray for building outlines (right) (screenshot by the author)
It's interesting that the same ratio of contrast in luminance doesn't give the same impression, depending on the absolute level of luminance. This is what makes it difficult to convert the light mode color palette into the dark mode one.
2.7 Place labels
For place labels, I set their fill color to be light gray:
- Feature type:
All
- Element type:
Labels > Text > Text fill
- Stylers > Color:
#929292
Then, the outline of text is set to be white:
- Feature type:
All
- Element type:
Labels > Text > Text outline
- Stylers > Color:
#2b2b2b
Which renders the map as follows:
A map of central Kyoto, with place labels in light gray (#929292
) outlined in dark gray (#2b2b2b
) (screenshot by the author)
Finally, place labels become visible. And this is the image featured at the top of this article.
Section 1.4.3 of the Web Content Accessibility Guideline (WCAG) states that the ratio of contrast in luminance must be at least 4.5 to 1 so that small text will be legible to those with visual acuity typical of the age 80 (Accessibility Guidelines Working Group 2021).
For place labels to be legible against the background of city blocks, therefore, its luminance level needs to be 4.5 times brighter than city blocks. #929292
satisfies this requirement:
A screenshot of contrast-ratio.com, showing the ratio of contrast in luminance between the dark gray for city blocks (left) and the light gray for place labels (right) (screenshot by the author)
This shade of gray, however, is not bright enough against the orange shade of streets. To make place labels legible against the background of streets in dark orange, I outline them in the same shade of dark gray as city blocks. This way, the adjacent area of place labels will always satisfy the 4.5-to-1 contrast ratio requirement against the fill color of place labels. It is a standard technique for graphic designers. It is also recommended by Lee (undated), a map design guide by Mapbox (see page 29).
That's all for My Ideal Map App's color scheme for the nighttime map.
For how I've set the visibility of place labels by zoom level, see Section 5 of Day 4 of this blog series (it's the same as for the daytime map).
It's time to take off my hat as a web designer and put on the one as a web developer. :-)
3. Switching to Dark Mode at Nighttime
Google Maps API documentation doesn't tell you how to switch the map style programatically. Here’s an approach I’ve figured out.
3.1 Basics
To apply a custom map style to a Google Maps embedded in a webpage, we need to assign a Map ID to the custom style on the Google Maps Platform website. Then, refer to the Map ID in JavaScript code.
I'm using React, rather than vanilla JavaScript, to build My Ideal Map App. So the JavaScript code to set up embedded Google Maps is something like this:
map = new google.maps.Map(googlemap.current, {
center: {lat: 35.011636, lng: 135.768029}, // Kyoto City Hall
zoom: 17,
mapId: '4hde5345723cdegs'
});
where googlemap.current
refers to the <div>
element in which Google Maps will be embedded:
const Map = () => {
const googlemap = useRef(null);
...
return <div ref={googlemap} />;
};
Have a look at React documentation on useRef()
if you're confused with the above code.
To switch the map style automatically, therefore, we need to programatically replace the Map ID in the above code.
3.2 Detecting the user's local time
To switch the map style into a nighttime mode after 6pm (and to switch back to a daytime mode after 6am), we need to know the user's local time. This is done with Date()
:
const currentTime = new Date();
The Date()
function returns the current time expressed in milliseconds since January 1, 1970 (known as Unix time). We assign this number to a new variable called currentTime
.
Of course, the current time expressed in milliseconds since January 1, 1970, is difficult to work with. What matters for us is whether the hour of the current time is between 18 and 23 or between 0 and 5. So we just need to convert the Unix time into the current hour:
const currentHour = currentTime.getHours();
The .getHours()
method converts the Unix time into the 24-hour clock and extracts the number of hours. We assign it to a new variable currentHour
.
We then check if the current time is between 6pm and 5:59am, with the following code:
let nightMode;
if (currentHour < 6 || currentHour >= 18) {
nightMode = true;
} else {
nightMode = false;
}
If that is the case, we assign true
to the variable nightMode
. Otherwise we assign false
.
I could, of course, go more precise by calculating the sunrise and sunset time based on the user's location. This is important for high-latitude places like Stockholm, where I used to live: the sun rises around 9am and sets around 3pm in winter. For the moment, however, I'll focus on building the entire web app to release its first version. Once that's done, I'll work on the enhancement of this feature.
3.3 Switching the map style by time of the day
Now let's get back to the initial piece of code where we set which map style to use by specifying a Map ID. We use the ternary operator to switch the Map ID, depending on the value of nightMode
:
map = new google.maps.Map(googlemap.current, {
center: {lat: 35.011636, lng: 135.768029}, // Kyoto City Hall
zoom: 17,
mapId: nightMode ? '4hde5345723cdegs' : 'fw2s9334gso24ckk'
});
where the Map IDs for nighttime and daytime modes are assumed to be 4hde5345723cdegs
and fw2s9334gso24ckk
, respectively. (These are fake Map IDs, by the way.)
3.4 Self-documentation, instead of comments
To make the code more readable, we're better off by assigning these Map IDs to variables:
const mapIdDaytime = 'fw2s9334gso24ckk';
const mapIdNighttime = '4hde5345723cdegs';
map = new google.maps.Map(googlemap.current, {
center: {lat: 35.011636, lng: 135.768029}, // Kyoto City Hall
zoom: 17,
mapId: nightMode ? mapIdNighttime : mapIdDaytime
});
This kind of "self-documentation" of programming code is very important. It is better than adding a comment like this:
map = new google.maps.Map(googlemap.current, {
center: {lat: 35.011636, lng: 135.768029}, // Kyoto City
zoom: 17,
mapId: nightMode ? '4hde5345723cdegs' : 'fw2s9334gso24ckk'
// '4hde5345723cdegs' is map ID for nighttime mode
// 'fw2s9334gso24ckk' for daytime mode
});
We will for sure forget updating this kind of comments when we have to change the map IDs in the future, because outdated comments won’t break the code.
A solution is to embed "documentation" into the code so that the failure to update "documentation" will break the code, which makes us notice it.
By using the variable names as "documentation":
const mapIdDaytime = 'fw2s9334gso24ckk';
const mapIdNighttime = '4hde5345723cdegs';
we will for sure update these lines of code when we need to change the map IDs.
This coding practice is something I learned in my previous job as an academic data scientist for social sciences. Statistical analysis of data requires computer programming skills. Back in 2014, two leading data scientists in economics, Matthew Gentzkow and Jesse M. Shapiro, wrote a manuscript that lists up the best coding practices they had learned from their computer science colleagues at University of Chicago (Gentzkow and Shapiro 2014). One of such is:
(A) Don't write documentation you will not maintain.
(B) Code should be self-documenting.
– Gentzkow and Shapiro (2014, Chapter 7)
I’m now programming for web apps, not for data science. But I still benefit from what I learned from this manuscript.
4. Demo
I've deployed the code described in this article with Cloudflare Pages: https://69be04bf.mima.pages.dev/
Have a look. If your local time is between 6am and 5:59pm, you will see a map in the light mode. But if it’s between 6pm and 5:59am, you’ll see a map in the dark mode. If that’s not the case, let me know by posting a comment as a bug report. ;-)
With this feature, My Ideal Map App will give the user experience of the following kind:
5. User experience #4: Navigation at night
It was before the pandemic broke out. With a Polish friend of mine visiting from Sweden, I was having dinner at Osen, my favorite restaurant in Kyoto. It serves delicious obanzai, the type of cuisine that grandmas in Kyoto would cook at home.
Interior of Osen (image source: &Travel)
We sat down at the counter, which is the best seating position in this restaurant because we can enjoy talking to the chef and the chief waitress. Indeed, with my translation, my friend enjoyed the communication with them as well as the dishes like this one:
Tanuki gohan (a bowl of steamed rice topped with fried tofu and kudzu starch), Osen's signature dish (image source: &Travel)
After finishing dinner, we decided to go out for a drink. I picked up my smartphone and launched My Ideal Map App. I tapped the bottom right button and checked the box for “bars” among the list of tags.
The app showed the bars that I had saved as my favorite around our current location. I found Gion Niti, a cocktail and whiskey bar housed in a former ochaya.
Interior of Gion Niti (image source: Gion Niti official website)
But I knew its location was hard to find. I needed to navigate myself by constantly watching a street map on the mobile phone. It was the late evening. A smartphone screen could be glaring to my eyes.
But My Ideal Map App is great because at nighttime it renders a map in the dark mode. I love its visual consistency with the world outside the screen. If it were the Google Maps app, I would have to tap a few times to set the dark mode on.
And we had a good time at the bar.
On the following morning, I launched My Ideal Map App for deciding where to take my friend next. As the app turns off the dark mode automatically in the morning, I saw a map in the light mode. This feature is helpful. I would feel uncomfortable with the dark mode map during daytime.
Plus, I feel a certain kind of warmth in the user interface that changes in response to whether the sun is in the sky or under the horizon. That's an unusual feeling for an app. I cannot really explain why, but it's probably because such a user interface doesn't try to control the nature but follow the nature.
... I really want to start using My Ideal Map App as soon as possible. :-) I have to work hard to release it very soon.
References
Accessibility Guidelines Working Group (2021) “Understanding Success Criterion 1.4.3: Contrast (Minimum)”, Understanding WCAG 2.1, Jul 27, 2021 (last updated).
Babich, Nick (2019) "8 Tips for Dark Theme Design", UX Planet, Jul 30, 2019.
Clarke, Laurie (2019) "Dark mode isn't as good for your eyes as you believe", Wired, Jul 30, 2019.
Edwards, Marc (2018) "I'm not a fan of pure black in UI...", Twitter, Oct 20, 2018.
Gentzkow, Matthew and Jesse M. Shapiro (2014) "Code and Data for the Social Sciences: A Practitioner's Guide", mimeograph.
Google (undated) "Dark theme", Material Design, undated.
Harvard Health Publishing (2020) "Blue light has a dark side", the website of Harvard Medical School, Jul 7, 2020.
Laderer, Ashley (2021) "Why dark mode isn't actually better for your eyes", Insider, Jan 23, 2021.
Lee, Amy (undated) "The Guide to Map Design", the website of Mapbox.
PhoneBuff (2019) "Dark Mode vs. Light Mode Battery Test", YouTube, Oct 20, 2019.
Purdy, Kevin (2019) "Does Dark Mode Really Save Battery on Your Phone?", Ifixit, Jun 3, 2019.
Top comments (0)