Adding a high-contrast theme toggle to a pastel website
Tags: webdev, javascript, css, a11y
Published: 2023-12-04 19:08:43

How can we make a legacy website more accessible? A common problem is the low color contrast of pastel designs, making text hard or impossible to read for a user with visual impairments.

![Screenshot: colors inspired by a real WordPress theme and a WAVE result showing 164 contrast errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6oi9d4frtnzgw5x56rqt.png)

## How to provide an accessible high contrast alternative to a pastel color scheme?

I used to experiment with different strategies, but they all had disadvantages. A fixed color palette that meets the minimum contrast requirements reduces the artistic design possibilities. It might still not be contrasting enough for some individuals, and it might even distract other users who expect a pastel scheme for some reason. We can add another color scheme, much like a "reader mode", a print stylesheet or an alternative dark mode vs. light mode. Then we can query the current high-contrast preference and switch the theme accordingly. Even when it works, automatic accessibility audits might fail, and "tricking" them by delaying the switch for several seconds before fading out high contrast into a more pastel version is no best practice for various reasons.

{% stackoverflow https://stackoverflow.com/questions/72547565/how-to-provide-an-accessible-high-contrast-alternative-to-a-pastel-color-scheme %} 

Automatically switching themes based on `prefers-high-contrast` system settings might not be enough. Our users might not know where to change the settings, they might have a browser or operating system that does not support it, or they may want change their preference temporarily based on ambient light settings. Applying one of two or more alternative themes automatically might result in an unhelpful outcome, much like an automated language selection that cannot be changed.

## WCAG 2.1 - high contrast toggle button

Although it has been criticized as "poor usability", a button to toggle high-contrast color schemes is perfectly valid to conform to WCAG criteria even without querying `prefers-high-contrast`, and it can be a helpful option to revise an unhelpful automatic preference.

{% stackoverflow https://stackoverflow.com/a/57033448 %}

When we decide how to switch themes, all we have to do is code them. The effort depends on how well our existing CSS and HTML markup is suited to be themed. Modern content management system themes often rely on variables, while legacy code will introduce custom and inline styles that will be hard to override, even if we use the combined force of specificity and `!important`.

Here is a (not so) "minimal" reproducible example inspired by an actual Elementor site. It contains tricky markup and unintuitive custom property variable names, like this

```html
<div class="elementor-element elementor-element-895b404 elementor-widget elementor-widget-text-editor" data-id="895b404" data-element_type="widget" data-widget_type="text-editor.default">
  <div class="elementor-widget-container">
    <style>.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:#69727d;color:#fff}</style><p>
```

```css
.elementor-kit-9 {
    --e-global-color-edb7346: #A76CAA;
    --e-global-color-9399b1e: #000000; /* black */
    --e-global-color-4a62416: #8FCBB0; /* pastel green */
    --e-global-color-a30acc7: #FFFFFF; /* white */
```

As you can see, the global color suffixes, have absolutely nothing to do with hex colors, but they look similar enough to confuse my mind and affect readability, so I added some perceived color names in the code comments.

## Toggle and override

To toggle themes, I decided to add another class name to the document body (or to an addition content wrapper in my codepen example) and replace the system color palette before adding element-specific styles only where needed.

```css
/* high contrast theme */
.elementor-kit-9.has-high-contrast-theme,
body.has-high-contrast-theme,
body.elementor-kit-9.has-high-contrast-theme {
  --e-global-color-4a62416: #e2fff2 !important; /* light greenish white */
  --e-global-color-a30acc7: #000000 !important; /* 100% black */
  --e-global-color-ec37af6: #191970; /* dark midnight blue */
  --e-global-color-9b687dd: #421504; /* dark orange brown */
  --e-global-color-dbe950d: #fffbea; /* light yellow-ish white */
}
```

The button must have a high color contrast from the start, but we can use JavaScript to toggle our themes. This can be done with some lines of vanilla JS that should run in every recent major browser and a lot of discontinued clients like Internet Explorer or older Safari browsers, using `var` variables, a `for` loop and the oldest and most compatible state engine: the global `window` object.

```javascript
document.addEventListener("DOMContentLoaded", function(event) { 
  window.hasHighContrastTheme = window.hasHighContrastTheme || false;
  window.elementorThemeRoot = document.querySelector('.elementor-kit-9') || document.body;
  var contrastThemeToggleButtons = document.querySelectorAll('.toggle-contrast');
  for (var i=0; i<contrastThemeToggleButtons.length; i++ ) {
    contrastThemeToggleButtons[i].addEventListener('click', function(e){
      window.hasHighContrastTheme = !window.hasHighContrastTheme;
      if (window.hasHighContrastTheme) {
        window.elementorThemeRoot.classList.add('has-high-contrast-theme');
      } else {
        window.elementorThemeRoot.classList.remove('has-high-contrast-theme');
      }
    })
  }
});
```

We can add some CSS to indicate the state of the toggle button. Here is a very simple style to demonstrate the syntax.

```css
.toggle-contrast {
  border: solid 1px silver;
}
body.has-high-contrast-theme .toggle-contrast {
  border: solid 2px black;
}
```

## Proof of concept: a CodePen

{% codepen https://codepen.io/openmindculture/pen/RwvEbqP %}