Recently I got an email thanking me for the work I have done for the Cypress community. The email also asked why I haven't added a dark color theme at my site cypress.tips where I keep my courses, my search, etc. Let's do this. I will define a second color theme using CSS variables, will use a React component to save and load the current color theme, and will write a Cypress end-to-end test to verify it all works together.
Previous work
I have already shown how to write Cypress tests for a React dark theme toggle component "DarkMode". You can see the app and the tests in the repo bahmutov/react-dark-mode. See the tests in the short video I have recorded below:
Let's apply this component to my Next.js React application.
CSS variables
The first thing we want to do is to make sure all colors are defined using CSS variables. In my case, the default is the light color theme, the alternative will be using attribute selector "data-theme=dark". We define the text color, the background color, the link colors, and maybe a few other colors.
1 | :root { |
Using these variable names, we define actual CSS properties. For example, the body of the document needs the text color and the background colors.
1 | html, |
I am using CSS modules to give styles to my React components. For example, the Card components need to change their color on hover. Again, we use the root CSS variable names to make the current theme apply:
1 | .card:hover, |
While creating the color, I set the data attribute on the custom Next Document component to see it immediately
1 | import Document, { Html } from 'next/document' |
The page looks nice.
I ran through the pages to make sure all components, like Footer use the color variables instead of local hard-coded colors.
Other CSS tweaks
I left the course thumbnail image as they were using the white backgrounds. The dark color theme showed the lack of margin between the course description and the thumbnail image.
I have added a right margin to the text paragraph.
The toggle component
Now let's copy the React DarkMode component from the repo alexeagleson/react-dark-mode. It checks the localStorage
to see if the user has previously selected a color them, with the fallback to the system theme preference.
1 | import React, { useEffect, useState } from 'react' |
We have a little bit of styling for the component, and then insert it on the top page
1 | import DarkMode from '../components/DarkMode' |
Now we have the component that flips the "data-" attribute on the document to change the CSS variables and toggle the colors
Adding Cypress tests
Since I have extensively tested this component in the repo bahmutov/react-dark-mode I will write just one test to make sure the toggle changes the document's data attribute
1 | describe('Dark mode toggle', { scrollBehavior: 'center' }, () => { |
You can find the final site at cypress.tips. I should probably add the Dark Mode toggle to all pages, not just the home page.
Client-side rendering
Next.js framework that I am using can render the pages on the server and then re-hydrate on the client (in the browser). Our DarkMode toggle component only makes sense when running in the browser. I looked up in this StackOverflow answer how to dynamically render a component on the client-side.
1 | // the actual DarkMode component |
1 | import { useEffect, useState } from 'react' |
1 | import React from 'react' |
1 | import DarkMode from '../components/DarkMode.js' |
Aria label
I have added the toggle Aria labels for a11y. I made them explain what the toggle will do when clicked.
1 | const label = theme === 'dark' ? 'Activate light mode' : 'Activate dark mode' |
The test uses the aria labels instead of input:checkbox
, for example
1 | cy.get('[data-cy=DarkMode]') |
See also
- You can emulate the system
prefers-color-scheme
when running Cypress tests, read the blog post Emulate Media In Cypress Tests. - Read the blog post How to Test an Application that Changes a CSS Variable
- How Cypress Freezes CSS Animations And You Can Too