Easy page transitions with the magic of CSS

code

Using a fade-in transition to smooth things out and prevent a flash of the opposite theme colors

first up:

Effective Communication (by JORDANN)

so let's start with the facts

i added a theme switcher to my site so that you can switch between light or dark themes freely.

however, as a result of this change: sometimes visitors would see a flash of the opposite theme colors if they switched to a theme on this site that was different than the theme they had set at their OS level.

in those cases, depending on load times and so forth, the wrong css theme would load (because the theme media query would pick up the visitor's OS level theme) and then the correct one would load a moment later (once the class name override from my site's theme switcher was applied). even if the correct theme came a fraction of a second late, that was just enough time to see the flash of the wrong theme.

let's move on to the subjective

to be honest, the flash of opposite theme colors was jarring and sometimes disorienting. it was aesthetically unpleasing and gross. i had to be rid of it.

i wanted to add page transitions to mitigate this theme flash issue. i didn't want to add complex page transitions, sacrifice accessibility for the sake of aesthetics, or do anything that would turn this site into a single-page application.

for me, a pure CSS solution was the answer. the title jokingly calls this a solution for "lazy people" but

now validate my feelings (CSS is great)

i wanted a simple fade-in with CSS, so i added the following above my theme styles:

@keyframes fadeIn {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

body {
opacity: 0;
visibility: hidden;
animation: fadeIn 250ms ease-in;
transition: background-color 250ms ease-in, background-image 350ms ease-in;
}

these styles load right away, followed by...

@mixin light-theme() {
// light theme colors

// body content visible:
body {
opacity: 1;
visibility: visible;
}
}

@mixin dark-theme() {
// dark theme colors here

// body content visible:
body {
opacity: 1;
visibility: visible;
}
}

html,
.light-theme
{
@include light-theme();
}

html.dark-theme {
@include dark-theme();
}

@media screen and (prefers-color-scheme: dark) {
html,
.dark-theme
{
@include dark-theme();
}

html.light-theme {
@include light-theme();
}
}

my thinking here is that putting opacity: 1 & visibility: visible inside the theme mixins (so they are applied when the theme override is correctly applied) with the transitions will help prevent the disorienting & annoying color shift.

visibility (not display) is intentional

i purposely used visibility instead of display to keep page content in the accessibility tree. (and to be clear, by "content" i am referring to the page's semantic HTML, which includes all of the text and the page hierarchy information.)

using display: none will hide everything in that DOM tree from the screen reader, which would make the page unreadable for a screen reader during the duration of the CSS transition. i want the content to be available to read in the accessibility tree immediately, even during the very brief visual fade-in.

also, because the fade-in is so brief, i don't want to use aria-live attributes to report a "loading state" to a screen reader because (1) the "content" itself is not actually loading, (it's just a CSS style fade-in), and (2) by the time the loading state is read by the screen reader, the fade-in would be over anyway.

the end?

i'm still experimenting with what works best, so i may end up updating this post with new ideas. for now, the transitions are working pretty well. ✌🏻