Skip to content
~/bermudev/blog
Go back

Making Theme-Aware Images Work in Astro

Table of contents

Open Table of contents

Why this change was needed

Since a while ago, even before the theme change, I noticed a small but common issue: a black logo looked perfect in light mode, but it almost disappeared in dark mode.

So now that I have a new theme, I had to do something about it. No better time than now to fix it once and for all!

The requirements were simple:

An example of the issue can be seen below, where the logo is barely visible in dark mode (toggle the dark mode if you are using white mode to see the difference):

Logo almost invisible in dark mode

The approach

This blog theme already uses a theme toggle that updates html[data-theme] in real time. This means we can rely on CSS selectors based on data-theme and avoid adding JavaScript listeners for every image.

The solution was to introduce a small opt-in attribute that applies a CSS filter only when the dark theme is active.

In global.css we added the following rules:

html[data-theme="dark"] img[data-invert-on-dark],
html[data-theme="dark"] [data-invert-on-dark] img {
  filter: invert(1) hue-rotate(180deg);
}

This filter inverts the colors of the image when the dark theme is active, which works well for logos that are primarily dark on transparent backgrounds.

Then in the article itself, the behavior can be enabled only where needed:

<Image
  src={picture}
  alt="Example picture"
  width="250px"
  data-invert-on-dark
/>

This keeps the behavior scoped to specific images instead of affecting every image on the site.

Example logo

Optional border control

While I was testing this, I also wanted a way to selectively hide image borders for some images, beacuse let’s be honest, sometimes it looks ugly.

For that, I added another optional attribute in global.css:

.app-prose img[data-transparent-border],
.app-prose [data-transparent-border] img {
  border-color: transparent;
}

Usage:

<Image
  src={picture}
  alt="Example picture"
  width="250px"
  data-transparent-border
/>

Final result with both attributes

Now with these changes we have two small reusable controls:

Both are completely opt-in and require no additional JavaScript.

And the fix is now reusable across the project and updates immediately when the theme changes, without requiring a page refresh.

Logo clearly visible after dark mode fix

This keeps logos readable in both themes while still allowing per-image control when needed — a small improvement that makes the blog feel much more polished!


Share this post on:

Next Post
Changing the Blog Theme: From Astro Cactus to AstroPaper