Pikchr

proposal: CSS ”currentColor” as alternative for dark mode
Login

proposal: CSS "currentColor" as alternative for dark mode

(1) By Michael Thornburgh (zenomt) on 2023-08-19 01:46:21 [link] [source]

i'm not personally a fan of "the dark mode web" or UIs, and never intentionally select to look at web pages that way, so i hadn't given much thought until recently about Pikchr diagrams and dark mode (other than the examples on this site). however, a friend's inability to see one of my diagrams on GitHub (which shows for him, along with most of the rest of the web, in dark mode because of his system settings) made me think about it more.

i see that Pikchr's "dark mode" implementation involves recompiling the diagram and inverting the colors emitted into the SVG. however, this feels not-quite-right to me for two reasons:

  • things like the font-family are inherited from the parent page (which i think is good and proper), but the page's foreground color is not, which feels philosophically incongruous
  • dark mode inverts all colors, which erases the author's intent when they say, for example, "make this box red"

CSS has the color keyword currentColor that can be used for strokes and fills (and anything else where you specify a color), which inherits the color style of the parent element. there is no currentBackgroundColor (though there probably doesn't need to be, since the background should usually be transparent anyway, and background isn't inherited in CSS if i understand correctly). using currentColor to color a diagram would make the diagram fit in with the rest of the page's content & color scheme without recompilation, no matter what it is (and allow the diagram's main color to be trivially styled statically or dynamically).

i made a proof-of-concept in pikchr.y by defining a new color keyword Inherit with a positive but >0xffffff value (0x01000000; i considered -2, but "any value < 0 is invisible/none" is baked into too many places), and modified pik_append_clr() to recognize this special value and emit "currentColor" instead of "rgb(R,G,B)" for it. this should be fine for any "marks in the page's color on a transparent background" diagrams (which is probably most of them), and reacts instantly if you change the foreground color of the page without recompiling the Pikchr to a differently colored SVG. you just need to say color = Inherit at the top of the diagram.

i don't think the way i've done this POC is the right way though. i have a few different ideas but i don't have a strong sense that any of these is best:

  1. Inherit (or another name) magic color (in the sense that None is magic) that you explicitly set in order to paint with the parent's color (the current POC); requires a modification to the diagram's source; not recommended for the real solution
  2. the Inherit magic color, but it starts as the default color instead of 0.0/black (perhaps with a new flag to pikchr()); probably not the greatest solution since it would change the initial value of the color variable, and would be erased or ineffective if "black" is ever used explicitly with the assumption it's the normal painting color
  3. a mode (set with a new flag to pikchr()) to change the string emitted for 0.0/black (exactly and only) to be currentColor instead of rgb(0,0,0); an arbitrarily-small non-zero value could still be indistinguishable from black for those cases where you actually mean "black" instead of "the parent element's foreground color" -- this would make the default paint to "parent element's foreground color" unless explicitly changed to some other color, which for (i suspect) most diagrams is what's desired, while still leaving the value of the "color" variable 0 as expected.

i think choice 3 is the closest to a good solution, since today's "dark mode" already changes what "0.0/black" means, and the behavior would be enabled with a flag (though i think "paint with the page's current color" should be the default behavior for most diagrams, but wrappers/drivers/integrations can always default it to on with a switch to off if desired).

regardless, none of this actually fixes the problem for GitHub specifically, since embedded SVGs aren't rendered when looking at a Markdown page in the repo (but they are in GH Pages), SVGs included as <img> or <object> can't be styled externally, and even if they could be, GitHub strips style= attributes from inline HTML elements. but GitHub-specific problems were only why i got to thinking about the general dark-mode problem; there isn't a satisfactory solution for GitHub as far as i can tell (currently i'm inserting an extra style attribute into the external SVG image file to give it a white background instead of the default transparent, so it's at least visible at all in my repo's main page when viewed in dark mode, even though it's ugly).

(2) By Stephan Beal (stephan) on 2023-08-19 07:29:37 in reply to 1 [link] [source]

i don't think the way i've done this POC is the right way though.

A simple alternative, which we used in /pikchrshow before pikchr had dark-mode support, is to apply a CSS filter to pikchrs to invert their colors. The results aren't perfect, but they're typically legible. That mode can be seen in operation at:

Toggle the "dark mode" button on the bottom to (un)apply the filter.

Of the example pikchr scripts (see the drop-down list on those pages), "swimlanes" demonstrates the effect most clearly. The others are monochrome, which makes them invisible when inverted.

The corresponding CSS:

filter: invert(1) hue-rotate(180deg);

Where it's applied depends on how the page is flagged as being in dark mode. e.g.:

body.dark-mode .pikchr {
  filter: invert(1) hue-rotate(180deg);
}

(3) By Michael Thornburgh (zenomt) on 2023-08-19 17:04:24 in reply to 2 [source]

i don't think the way i've done this POC is the right way though.

A simple alternative, which we used in /pikchrshow before pikchr had dark-mode support ...

your response makes me concerned that i might've buried the lede or otherwise been unclear. i definitely think that it should be possible to paint with the SVG element's computed current color (that is, currentColor inside the SVG), no matter what that happens to be at the time. when i said "i don't think the way i've done this POC is the right way though", i meant that i don't think having another magic color (similar to how None and Off are magic) is the right way.

on further reflection, i'm leaning toward my idea #3, which is a new flag for pikchr() that, when set, emits currentColor instead of rgb(0,0,0) only when the (floating point) color value is exactly 0.0 (so that "actually i mean black no matter what" can still be achieved with something like color 0.1 which would emit rgb(0,0,0) after the rounding that already happens). i think this is consistent with the current ethos of the PIKCHR_DARK_MODE flag, which changes all of the colors if set. i already changed my POC to work like this (no more magic Inherit color).

the rationale for "Pikchr diagrams should be painted in the current color by default" is consistency with Markdown, where your text and other things are painted in the document's current foreground color and font, not explicitly "black without serifs on a transparent background just because it's Markdown". dark mode was the impulse to realizing the current incongruity, but i feel it stands independent of dark mode.