Code.Movie

Light and dark mode

There are several ways to handle dark and light mode (or any other styling condition) with Code.Movie animations.

Setting variables

The long list of CSS variables can be set conditionally. Nothing stops you from changing certain settings dependent on media queries...

@media (prefers-color-scheme: light) {
  :root {
    --cm-scene-background: #fff;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --cm-scene-background: #000;
  }
}
1234567891011
@media (prefers-color-scheme: light) {
  :root {
    --cm-scene-background: #fff;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --cm-scene-background: #000;
  }
}

... or parent classes:

:root.isLight {
  --cm-scene-background: #fff;
}

:root.isDark {
  --cm-scene-background: #000;
}
1234567
:root.isLight {
  --cm-scene-background: #fff;
}

:root.isDark {
  --cm-scene-background: #000;
}

If you don't want to micro-manage your CSS variables, you can also alternatively use conditional themes.

Using conditional themes

Themes are objects that Code.Movie turns in CSS. This process supports more than one theme if the theme objects come with a condition. To make use of this feature, pass a list of themes with conditions instead of a single theme object to animateHTML() or toAnimationHTML():

import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a media query
const html = toAnimationHTML(sceneData, {
  // Pass a list of themes with conditions instead of a single theme object
  theme: [
    { theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
    { theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
  ],
});
1234567891011121314
import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a media query
const html = toAnimationHTML(sceneData, {
  // Pass a list of themes with conditions instead of a single theme object
  theme: [
    { theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
    { theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
  ],
});

condition can be a selector or the "header" of any relevant block at-rule, such as @media and @supports. The CSS generator will then wrap the CSS it creates for each theme in a new CSS block and prefix it with the string in condition. The CSS for the above example will therefore look something like this:

/* CSS that is independent of themes */
@media (prefers-color-scheme: light) {
  /* CSS generated from monokaiLight */
}
@media (prefers-color-scheme: dark) {
  /* CSS generated from monokaiDark */
}
1234567
/* CSS that is independent of themes */
@media (prefers-color-scheme: light) {
  /* CSS generated from monokaiLight */
}
@media (prefers-color-scheme: dark) {
  /* CSS generated from monokaiDark */
}

Because the CSS generator mindlessly wraps any theme with a condition into a new block and CSS nesting is an old hat by now, condition can just as well be a selector:

import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a parent selector
const html = toAnimationHTML(sceneData, {
  theme: [
    { theme: monokaiLight, condition: ":root.isLight" },
    { theme: monokaiDark, condition: ":root.isDark" },
  ],
});
12345678910111213
import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a parent selector
const html = toAnimationHTML(sceneData, {
  theme: [
    { theme: monokaiLight, condition: ":root.isLight" },
    { theme: monokaiDark, condition: ":root.isDark" },
  ],
});

Resulting CSS:

/* CSS that is independent of themes */
:root.isLight {
  /* CSS generated from monokaiLight */
}
:root.isDark {
  /* CSS generated from monokaiDark */
}
1234567
/* CSS that is independent of themes */
:root.isLight {
  /* CSS generated from monokaiLight */
}
:root.isDark {
  /* CSS generated from monokaiDark */
}

To support both media queries and classes, simply pass more items in the theme list with the relevant conditions:

import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a parent selector with media
// queries as fallbacks
const html = toAnimationHTML(sceneData, {
  theme: [
    // Fallback items in case neither "isLight" nor "isDark" are set
    { theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
    { theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
    // Overrides the media queries if either class is set
    { theme: monokaiLight, condition: ":root.isLight" },
    { theme: monokaiDark, condition: ":root.isDark" },
  ],
});
123456789101112131415161718
import {
  toAnimationHTML,
  monokaiLight,
  monokaiDark,
} from "@codemovie/code-movie";

// Example: use dark or light theme dependent on a parent selector with media
// queries as fallbacks
const html = toAnimationHTML(sceneData, {
  theme: [
    // Fallback items in case neither "isLight" nor "isDark" are set
    { theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
    { theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
    // Overrides the media queries if either class is set
    { theme: monokaiLight, condition: ":root.isLight" },
    { theme: monokaiDark, condition: ":root.isDark" },
  ],
});

This will duplicate the CSS generated for each theme, but this is only a small inefficiency - themes don't generate that much CSS compared to the rest of the animation.