A Step-by-Step Guide to implement light/dark modes in CSS

A Step-by-Step Guide to implement light/dark modes in CSS

In this tutorial, we'll take a look at how to create a dark theme for your web project, and how to switch from a default (light) theme to a dark one with the help of CSS Custom Properties.

The CSS Working Group is made up of members from all major browser vendors and other technology companies like Apple and Adobe. Apple, having recently launched its new version of MacOS, wanted a way to detect its snazzy new dark mode in the browser. In order to do this, Apple pushed a recommendation to the specification for a new level 5 media query.

@media (prefers-color-scheme: light | dark) 
{ … }

Using this media query, we can detect if the user is currently using light or dark mode in the OS. At the moment this is only supported by Safari Technology Preview 69 and above, but the other browsers shouldn't be far behind.

In order to test this you will need to be upgraded to Mojave 10.14 (MacOS) and have selected Dark appearance in System Preferences. There's a few ways we can use this new media query to implement different themes. We'll explore a few of them now in this tutorial.

1. Set up the page

To begin, we need to create some HTML elements to style, so we'll start by creating a new pen on CodePen and adding some elements. We'll add a container for our content, in order to centre it, and some headings and text. We'll set the CSS to use Sass in order to use nesting in CSS.

<div class="content-container">
    <h1>Heading One</h1>
    <h2>Heading Two</h2>
    <hr>
    <p>…</p>
    <p>…</p>
</div>

2. Style basic elements

Next we'll add some basic styles and include some fonts from Google in order to make our page look a bit nicer. We'll style all of our basic elements, applying new font sizes, colours and fonts.

body {
    font-family: 'Merriweather', serif;
    background-color: #ededed;
    color: #212121;
    padding: 1.618rem;
    line-height: 1.618;
    font-size: 16px;
}

3. Style container

This is image title Build a container with a comfortable line length

Next we'll style our container to make the content a comfortable line length for reading. We'll also add a background colour and drop shadow. In order to centre the content box in the page, we'll use the keyword 'auto' on the margin properties' left and right values.

.content-container {
    padding: 1.618rem 3.236rem;
    max-width: 48.54rem;
    margin: 3.236rem auto;
    background-color: #fff;
    box-shadow: 0 0 12px 6px rgba(0,0,0,0.05);
    border-radius: .269666667rem;
}

4. Add highlight colour

This is image title Pick a highlight colour and make a style for it

Most websites make use of colour somewhere, and at the moment we only have whites and greys, so now let's choose a highlight colour and create a style for applying this colour. We will apply the colour using a span tag, and will use it to highlight something in our content.

<span class="text--alpha">Lorem ipsum</span>
.text--alpha {
    color: #c3423f;
}

5. Implement the media query

This is image title Now you have some styles, you can implement the media query

Now we have a page with some basic styles, let's look at ways we can implement the media query. Let's include it and start overriding some of our styles. We'll start with the body styles.

@media (prefers-color-scheme: dark) 
{
    body {
        background-color: #111;
    }
}

6. Override the remaining styles

This is image title Now you can override the rest of the styles

Now that we can see the media query is working and our body background colour has changed, we need to override all of our remaining styles.

.content-container 
{
    color: white;
    background-color: #212121;
}
.text--alpha {
    color: #50a8d8;
}

7. Maintainability

While what we've just done works perfectly well for our demo and could be maintained on smaller websites, this method would be a nightmare to manage on a larger project, with lots of different elements that all need overriding. We're also making heavy use of the cascade in our example above, whereas a large system may require more specificity in order to target all elements.

8. Take another approach

This is image title For a quick and dirty dark mode, just use 'invert'...

So how else can we tackle the problem? Let's look at CSS filters. One of the values we can use on CSS filters is 'invert', so we could just apply this to the HTML and invert all of the colours, giving us a 'dark mode'

@media (prefers-color-scheme: dark) {
    html {
        filter: invert(100%);
    }
}

09. Add images

This is image title ...of course, your photos will look like this

While the filter method works with the content we have in our document it still doesn't look great – our box shadow, for example, has also inverted, which looks quite strange. We have lost control over the styles, which becomes an even bigger problem when you have coloured backgrounds. We also have a whole new problem to consider when images are involved. Let's see what happens when we add an image to our page.

10. Use custom properties

The methods we've explored so far either cause us to lose control over the styles or require a lot of maintenance in order to make sure everything is updated in dark mode. There's another way we can approach this: we can use custom properties in order to define our colours and then override them using the media query.

11. Create custom properties

In order to use custom properties, we define them at the top of our CSS inside the ':root' element. The root element has the same scope as HTML so will be available globally. We need to decide on the variable names and define their values.

:root {
    --background-color: #ededed;
    --page-background: #fff;
    --text-color: #212121;
    --color-alpha: #c3423f;
}

12. Apply our custom properties

Now we have some custom properties defined we can use them in our CSS. We'll start with the body and apply the background and text colours. In order to use a custom property we use the 'var(--custom-property-name)' syntax.

body {
    background-color: var(--background-color);
    color: var(--text-color);
}

13. Apply remaining properties

Using the same method, we can also update the 'background-color' of our container and the 'color' of our 'text—alpha' class to use our custom properties. All of the colours in our page are now controlled using custom properties.

.content-container {
    background-color: var(--page-background);
}
.text--alpha {
    color: var(--color-alpha);
}

14. Re-add the media query

Now we can re-add the media query, but this time we can override the custom property values that are inside of it. We will place this right after the original root definition, and inside the media query we can now simply choose new values for all of our colour custom properties.

@media (prefers-color-scheme: dark) {
    :root {
        --background-color: #111;
        --page-background: #212121;
        --text-color: #ededed;
        --color-alpha: #50a8d8;
    }
}

15. Take full control

Custom properties give us full control to choose what colours and other properties we change and use. Let's update the box shadow on our page container to make it less transparent when using dark mode. To do this we need to create a new custom property for the page shadow.

:root {
    …
    --page-shadow: 0 0 12px 6px rgba
    (0,0,0,0.05);
}

16. Apply the shadow

Now we've created another custom property we need to apply it to the correct element on the page. We can then override the value inside our root element in order to reduce the transparency.

@media (prefers-color-scheme: dark) {
    :root {
        …
        --page-shadow: 
            0 0 12px 6px rgba(0,0,0,0.33)
        ;
    }
}
.content-container {
    …
    box-shadow: var(--page-shadow);
}

17. Add an image

This is image title Add an image and float it next to the content

Now let's add an image back into our content, and then we can add some basic styles to float the image next to the content.

img {
    width: 100%;
    height: auto;
    float: left;
    max-width: 300px;
    margin-right: 1.618rem;
    margin-bottom: 1.618rem;
}

As we can see, since we're not using any filters the image is not altered between the two themes.

18. Add more components

Now we've got our custom properties we can keep adding elements to the page and styling them with our variables. Let's create a button class and add a button to our page.

.button {
    display: inline-flex;
    font-family: inherit;
    background-color: var(--color-alpha);
    color: var(--text-color);
    padding: 1.618rem 3.236rem;
    border: 0 none;
    border-radius: 0.25rem;
    text-decoration: none;
}

19. Create button hover styles

Using the same variables, we can also create a hover style that can be used for both themes. In order to achieve this, we will invert the colours when the user hovers over the button and transition those properties in order to make the experience less jarring.

.button {
    …
    transition: background-color 150ms, 
    color 150ms;
    &:hover {
        background-color: var(--text-color);
        color: var(--color-alpha);
    }
}

20. Make button custom properties

Custom properties have the same scope as regular CSS elements; this means we can override them using a more specific selector. We can take advantage of this and create some variables that are scoped to our button.

.button {
    --button-background: var(--color-alpha);
    --button-text: var(--background-color);
    background-color: var(--button-background);
    color: var(--button-text);
    …
}

21. Utilise scope

This is image title Use the scope to create different styles and interactions for your button

We can utilise this scope in order to create different styles and hover interactions for our button in dark and light themes. We can change the value of our variables based on the media query or the state of the element, instead of repeating the property with a new value as we normally would.

.button {
    …
    &:hover {
        --button-background: #ae3937;
        @media (prefers-color-scheme: dark) {
            --button-background: #2e98d1;
            --button-text: var(--background-
            color);
        }
    }
}

This article was originally published in issue 283 of creative web design magazine Web Designer.

Learn More

css

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire CSS Developer

Want to develop a website or re-design using CSS Development? We build a website and we implemented CSS successfully if you are planning to **[Hire CSS Developer](https://hourlydeveloper.io/hire-dedicated-css-developer/ "Hire CSS Developer")**...

7 Best Vue CSS Component for Your App

Vue CSS frameworks are great for many reasons; code is more universally understood, web applications are easier to maintain, and prototyping becomes less of an extra step.

Learn to use variables in CSS (CSS Tricks)

whats the variable in CSS, how to declare use them. Whats the benefit of variables in CSS. Lets learn all this. #Variables #CSS #HTML #CssVariables #ITArticles

CSS Flex Box: A Flexible Way To Layout

Every element of HTML is a rectangular box. Every Box has a defined height and width. This way you can increase or decrease its size.

Creative CSS Button Effect using CSS Keyframe - CSS Tricks

In this video, you'll look at Creative CSS Button Effect using CSS Keyframe - CSS Tricks