-
-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Recommendations on how to handle dark mode #33
Comments
Does your first idea mean using a single style that looks good both in light and dark mode? Also, I'd like to help implementing this, is it something lower in complexity? |
I think of it as being:
|
It is not an elegant solution but code containers could be dark even in light theme. Check Dan Abramov's overreacted.io (https://overreacted.io/goodbye-clean-code/, for example). It looks good for me to read, both in light and dark theme. That seems the easiest possible solution. At least could bring some fresh to the eyes at night until the ideal solution could come up. |
Yep - that's a reasonable workaround to not have this problem. That's totally doable for people, but I'm not planning on making that design compromise |
Yes, I understand. I'm not proposing defining as design choice, but it is a better solution than the current one. I believe devs feel more comfortable reading dark code during the day than light code at night. |
I don't think it is practical enough to support CSS properties-backed dark and light theme, given the size of grammar and sheer number of color tokens. What I'd suggest is to customize some color tokens (like background-color and foreground-color) through CSS properties and toggle them through a feature query. You'll have to find a theme which has got tokens that work nice enough in light and dark mode (which is annoyingly hard). I was able to do that with Prism (an example here), given its very limited set of tokens but gave up on Shiki. |
@pveyes solved this by switching the output to use css variables: |
Hi, upon closer inspection I found out that we can get I'm not really familiar with how vscode textmate parses grammar and the resulting type so here's my assumption. We can find what scope matches inside type Scope = {
scopeName: string;
themeMatches: Array<ThemeMatch>
}
const token = highlighter.codeToThemedTokens(code, lang)
const matchingScope: Scope = token.explanation[0].scopes.find(scope => {
return scope.themeMatches.length > 0
}); If empty we can use if (!matchingScope) {
return "--fallback-color"
} Even though technically we can generate CSS variable using I found that 0 index is the one that's being used if there's more than 1 type Theme = {
name: string;
scope: Array<string>;
}
const matchingTheme: Theme = matchingScope.themeMatches[0]
// we can then generate CSS variable from either name or scope
// remove whitespace, convert invalid character, etc
const cssVar = convertToCSSVariable(matchingTheme.name)
// or use scope
// join all scope, compute hash
const cssVar = convertToCSSVariable(matchingTheme.scope);
return cssVar Then we can generate HTML tag and use CSS variable fallback. const cssVar = generateCSSVariable(token);
const html += `<span style="color: var(${cssVar}, ${token.color})">${token.content}</span>` |
So, dark mode alone wasn't quite sufficient for me. I wanted the color switching with themes that @pveyes built, but without being locked into a specific theme / set of themes. I'd love to have readable class names, but after messing around with scopes as suggested above, was unable to come up with a good solution and have settled for just generating Leaving this here as it might be useful to someone - https://gist.github.com/Gerrit0/275a4b8ffee4fa133fd075f5edeb3cda. TypeDoc will use a similar approach in the rebuilt themes. |
I need to read up dark mode a little bit first, so this won't cut it for 0.2.0. |
Currently renderers have no access to theme data. I plan to do an API change:
This way a renderer has all needed info to output HTML+CSS (with meaningful class names). HTMLCSSRenderer could generate a CSS mapping such as: .support-function {
color: #fff;
} And give each token its matching scope as Would this work for you? |
That would be awesome. For the CSS mapping - how static would these classes be across different themes? For my use case, I want to be able to generate links for some identifiers. Right now, to get highlighting I'm generating a string that looks like TypeScript, getting the tokens from that, and matching the text of tokens against the identifiers I expect. It would be neat to avoid the extra pass to Shiki. https://github.com/TypeStrong/typedoc/blob/library-mode/src/lib/renderer/default-templates.tsx#L539-L591 |
Each token can have multiple scopes, and it's up to the theme to decide which one it would colorize. .github-dark {
.a { }
}
.github-light {
.a { }
} Although that would take a larger refactor.
That's a separate feature, isn't it? |
I've been doing something like this on my new work-in-progress blog using What is done there - and what works really well, is @orta's second idea: Matching two seperate themes together. One is applied in light mode, the other in dark mode. |
This sounds like what I'm looking for.
Kind of - I already have it working by using the tokens myself, I'm just looking for a better way of choosing the CSS classes + CSS generation for each token. Really it's just a reason why I will still use tokens, not the |
https://github.com/anotherglitchinthematrix/monochrome could be a good test. I could generate 10 different variations and make a slider demo. |
We are currently inverting and colour hue shifting on the TypeScript website, and I think that's probably enough for us - microsoft/TypeScript-Website#1536 |
@orta's approach is an okay solution but I don't think it works for every theme. Works well on the TS website though. My ideal API would be the following: const highlighter = await shiki.getHighlighter({
theme: 'github-light',
darkTheme: 'github-dark',
langs: [...BUNDLED_LANGUAGES, ...langs]
}) As a user I want to be able to pick the theme that best fits my website for light/dark mode. |
I think I've found a pretty good solution. I'm editing Then you just add the following css to hide it based on a media query. You could also do class based dark theming, it's up to the user. .syntax-dark {
display: none;
}
@media (prefers-color-scheme: dark) {
.syntax-light {
display: none;
}
.syntax-dark {
display: block;
}
} |
Hah, nice idea I keep feeling like all of our answers live outside of 'shiki' and in whatever shiki -> x rendering tool you're using |
I built a playground with dark mode. You can give it a try at https://shiki-play.matsu.io. Source code is at https://github.com/shikijs/shiki-playground Here are my thoughts on different approaches:
Outputting semantic HTML (meaningful class names) with CSSGiven a source, a grammar and a theme, you can get the matching scope that makes a token a specific color. For example, here the token is So we need a renderer that does this:
Note to myself - on the conversion: If a theme colorizes API wise, something like this: interface SemanticHTMLCSSRenderer {
generateSemanticHTML(code: string, lang: Lang): string
generateCSSFromTheme(theme: Theme): string
} |
My gut says the simplest API is we allow theme to be either a |
I also joined the club of "render many times" with remark-shiki-typescript microsoft/TypeScript-Website#1831 It can render multiple copies of the code, and then it's on the user to write the CSS which removes the specific theme
|
For the record, I made https://github.com/antfu/markdown-it-shiki#dark-mode |
Yes, I think we should ship this theme in Shiki - perhaps simply called The CSS will need to go into shiki docs somewhere. I'm not sure where @octref feels WRT a user-facing docs site (e.g. like the shiki-twoslash one, but for now I think this can be put in the docs folder of this repo. |
Great, PR added: #212 |
#212 is great! Is is possible to get required CSS for specific theme? |
Hey @FredKSchott, great work! While I'm late to review the PR, since we are still pre 1.0 I'd like to get the API right. IMO hiding this functionality behind a special theme is not as good as having an explicit API. I think what you have is a good start. What I also want to see are:
|
I'm a bit wary about having this as a separate API codepath, interested to see how it turns out. Having a different API means that higher level abstractions like remark-shiki would need to be aware of this and expose new config vars for what is essentially a custom theme output |
@orta That's a valid concern. However I'm thinking about additional use cases like this. So far what we have are:
This assumes user want the tokens already colorized, and the only way for them to tweak the coloring pipeline is to write a theme. What we could also have is:
Essentially what highlight.js does. People who want to write their own colorizer can use this API. It's also not that much – just mapping I also feel |
I'm trying out the No clear API, functionality is triggered by a theme name ( Instead of the hacky and limited shiki/packages/shiki/src/highlighter.ts Line 67 in 4478d64
shiki/packages/shiki/src/highlighter.ts Line 99 in 4478d64
ShikiTheme like cssVariables: boolean ?
Seems to me that by doing this it would unlock full theming via CSS Variables and resolve all dark-mode issues, including my problems listed above. Users would be able to swap out any colors they'd like for a CSS variable instead. |
Also the shiki/packages/shiki/src/highlighter.ts Line 69 in 4478d64
|
As an innocent user, I thought I could do this on {
"tokenColors": [
{
"scope": ["comment"],
"settings": {
"foreground": "var(--color-muted)",
"fontStyle": "italic"
}
},
{
"scope": ["constant"],
"settings": {
"foreground": "var(--color-pine)"
}
},
{
"scope": [
"constant.numeric",
"constant.language",
"constant.charcter.escape"
],
"settings": {
"foreground": "var(--color-rose)"
}
},
]
} So when Shiki processing, it will inject that value to token style |
I think I've done it, not only dark mode but also multiple themes. Inspired by Screen.Recording.2022-05-13.at.19.09.29.mov |
I think maybe I found the most balanced way of doing so. You can learn more about the details at antfu/shikiji#5 Basically, it generated inline CSS variables like: <span style="color:#1976D2;--shiki-dark:#D8DEE9">console</span> That can be overridden with a short CSS snippet: @media (prefers-color-scheme: dark) {
.shiki span {
color: var(--shiki-dark) !important;
}
} This way we will have a perfectly working light mode output, while being able to switch to dark mode conditionally, without duplicating the content. Would be happy to port it back to Shiki if you think this approach makes sense. |
Yeah, I think this answer is the right one - and should probably be canonically the answer Something I'm not certain about (from a high level) is what might happen if the two themes have different support for coloring source attributes, e.g.
might generate something like
Is it possible that there could be inconsistencies in the theme in terms of the tokens <=> colors they support, making something like:
possibly happen? |
Yes, it's absolutely possible for that to happen. TypeDoc's implementation of this uses a single class for each color, and overrides what the class does, so I've seen this quite a lot when debugging, even when highlighting with "sister" themes like Light Plus/Dark Plus |
@orta I think <code>
<span class="line">
<span style="color:#1976D2;--shiki-dark:#D8DEE9">I</span>
<span style="color:#6F42C1;--shiki-dark:inherit"><</span>
<span style="color:#6F42C1;--shiki-dark:#88C0D0">3</span>
</span>
</code> Also it would be cases some theme output larger token, like: <code>
<span class="line">
<span style="color:#1976D2;">I < 3</span>
</span>
</code> In that case, So far I think it covers most of the edge cases. |
👍🏻 |
Since nobody mentioned this already: using inline html As far as I understand, the two approaches proposed in this thread rely on HTML classes and CSS variables, respectively. Since they both require the use of a separate CSS stylesheet, I'd argue that using HTML classes is better since it doesn't have the aforementioned CSP issue. Am I missing something? Please let me know! Thanks for your awesome work on Shiki :D |
👋 I'm thinking about how to handle dark and light themes, microsoft/TypeScript-Website#342 eventually I'm going to need to figure out how to make a dark mode which uses css classes because these poor dark mode folks have the same code color schema:
So, I'm wondering what you think might be the best way to do it (and whether it's something I should do upstream back here)
I have two ideas:
The text was updated successfully, but these errors were encountered: