Skip to content

Conversation

@develar
Copy link

@develar develar commented Jan 5, 2024

  • introduce --dark-theme-class: the CSS class to enable dark mode. When left unset prefers-color-scheme media query is used;
  • add test for combined light and dark themes (original test uses light theme in dark theme test, not clear why);

When D2-produced SVG file is embedded into some documentation/site framework where dark mode is controlled by CSS class, using prefers-color-scheme media query leads to inability to switch dark theme if needed. Writerside issue. Same for mkdocs and so on. IntelliJ D2 plugin also will use a new flag.

Media Query Level 5, where it is possible to use some class as a condition, is not supported by any modern browser.

dark-dynamic.mov

Copy link
Collaborator

@alixander alixander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR. Let me understand the context a bit more.

  1. Certain websites add a CSS class to all images when dark mode is turned on.
  2. This flag makes it so that this CSS class flows down into D2's CSS to control dark mode.

The downside is obviously that the image is no longer portable -- it's generated to be on a certain website.

It seems that apps/sites that do this type of dark mode configuration know that this is a limitation on most images and just have you specify two images, e.g.:

  1. https://docusaurus.io/docs/markdown-features/assets#github-style-themed-images
  2. https://github.blog/changelog/2021-11-24-specify-theme-context-for-images-in-markdown/

The benefit of using D2 is that you can have all your diagrams adapt with just 1 image.

I guess rendering an SVG for a particular website and thus having it not be portable for others is fine. I can't think of anything better and recognize this is a benefit for these use cases.


I think this flag should be either class or ID. If it starts with .something, it's a class. If it's #something, it's ID.

edit: nvm, IDs don't make sense here.

@develar
Copy link
Author

develar commented Jan 11, 2024

  1. Certain websites add a CSS class to all images when dark mode is turned on.

The "dark" class is technically applied to the html or other top-level elements. See, for example, the Tailwind Documentation template — it applies class dark to html. (<html lang="en" class="h-full antialiased __variable_e66fe9 __variable_b436a8 dark" style="color-scheme: dark;">)

The downside is obviously that the image is no longer portable -- it's generated to be on a certain website.
just have you specify two images

Writerside also supports this, enabling us to generate or provide two different static images, but I wouldn't say it is a downside.

When rendering SVGs into bitmap format, the user loses the ability to copy text from the diagrams. To me, this is a valuable feature. Additionally, we should also consider scaling — Retina screens, for example, require 2x resolution images to provide crisp, detailed visuals.

@develar develar requested a review from alixander January 21, 2024 09:19
@alixander
Copy link
Collaborator

Cool and lastly, can you sign your PR please @develar ? We have it turned on as an organization-wide setting.

@develar develar force-pushed the dynamic-dark-theme branch from 52d4880 to c2e881e Compare January 22, 2024 06:38
@develar
Copy link
Author

develar commented Jan 22, 2024

Cool and lastly, can you sign your PR please

Sign Git commits? Done.

Copy link
Collaborator

@alixander alixander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive the delay here. This feature looks valuable, if you're still interested in pursuing it.

@yinhx3
Copy link

yinhx3 commented Aug 6, 2025

I'm not sure if it's too late to bring this up now: I suggest using light-dark instead of a class

Reasons:

  • Simplicity (no need to duplicate styles), easy to configure, and available in all three major browser engines.
  • Both class and attribute naming conventions exist; in Tailwind CSS, .dark and [data-theme=dark] are the recommended approaches. However, regardless of how dark: is defined, you still need to apply dark mode to native controls via dark:scheme-dark as documented
  • Based on the embedded SVG prefer-color-scheme behavior, our current implementation already allows an external color-scheme to control the color scheme of embedded SVGs (details). Using light-dark effectively extends this behavior to inline SVGs

Behavior Changes

Current behavior:

  • For inline SVG, the color scheme depends on the browser's current prefers-color-scheme and cannot be controlled in code.
  • For embedded SVG:
    • If color-scheme: light or color-scheme: dark is set, that scheme is honored.
    • If color-scheme: light dark or no color-scheme is set, prefers-color-scheme is honored.

Using light-dark:

For both inline and embedded SVG:

  • If color-scheme: light or color-scheme: dark is set, that scheme is honored.
  • If color-scheme: light dark is set, prefers-color-scheme is honored.
  • If no color-scheme is set, always use light mode.

A breaking change here is that inline SVG without a color-scheme no longer follow prefers-color-scheme.

The proposed behavior actually aligns with how native web controls (e.g. <button>, <input>) handle color schemes. As the light-dark function gains awareness, it should be widely accepted.

If this proposal is adopted, issue #831 can be safely implemented, because authors will have direct page-level control without worrying about their SVGs unexpectedly switching to auto-applied dark mode. Just like native controls, every component supports dark mode—but only when color-scheme is explicitly set, not merely via prefers-color-scheme.

Implementation:

  • Use light-dark to define color values
  • Add
    svg:root {
      color-scheme: light dark;
    }

Further explanation of point 2:

Because:

  • Embedded SVG's prefer-color-scheme depends on the external document's color-scheme
  • light-dark only responds to prefer-color-scheme when color-scheme: light dark is set

So, if you remove an SVG's prefer-color-scheme and use light-dark:

  • Inline SVG will respond to the page's color-scheme
  • Embedded SVG will lose its ability to follow the external color-scheme

By adding:

svg:root {
  color-scheme: light dark;
}
  • Inline SVG remains unaffected, because svg:root only matches when the SVG is the root node (i.e., opened directly or embedded via <img src>)
  • Embedded SVG will "reactivate" its response to prefer-color-scheme thanks to the color-scheme: light dark declaration, thus correctly following the external color-scheme

Example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body style="color-scheme: dark;">
  <div style="display: flex; gap: 20px;">
    <!-- inline SVG -->
    <svg xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 100 100"
         width="100"
         height="100">
      <style>
        svg:root {
          color-scheme: light dark;
        }

        @scope {
          --fill-color: light-dark(red, green);

          circle {
            fill: var(--fill-color);
          }
        }
      </style>
      <circle cx="50" cy="50" r="40" />
    </svg>

    <!-- embedded SVG -->
    <img src="./light-dark-circle.svg" alt="SVG Image" />
  </div>
</body>
</html>

light-dark-circle.svg (same content as the inline SVG above):

<svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 100 100"
     width="100"
     height="100">
  <style>
    svg:root {
      color-scheme: light dark;
    }

    @scope {
      --fill-color: light-dark(red, green);

      circle {
        fill: var(--fill-color);
      }
    }
  </style>
  <circle cx="50" cy="50" r="40" />
</svg>

For brevity, this example uses @scope for style isolation, but Firefox support is lacking. In practice, you can continue to isolate styles with something like class="d2-1286588776 d2-svg".

@develar
Copy link
Author

develar commented Oct 1, 2025

I’ve started working on this PR again. I reviewed the comment above, and it does look like a better approach. It’s not exactly what I’d like to see in the long run, since ideally we should be able to use external CSS to reduce SVG size—but that’s a separate topic, as it involves different scenarios with their own trade-offs.

@develar develar marked this pull request as draft October 1, 2025 08:25
@develar develar force-pushed the dynamic-dark-theme branch from c2e881e to f7ce68f Compare October 1, 2025 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants