gohugoio/hugo · error
style %q does not have a light or dark mode
Error message
style %q does not have a light or dark mode
What it means
When `ModeSelector` is enabled, Hugo scopes every generated CSS selector under a top-level `.light` or `.dark` class so both stylesheets can coexist and be toggled by a parent class. That prefix is derived from the built style's `Mode()`; if the style reports neither `chroma.Light` nor `chroma.Dark` (mode unknown/unset), Hugo cannot pick a prefix and errors instead of emitting unscoped CSS.
Source
Thrown at markup/highlight/chromastyles.go:119
var buf bytes.Buffer
if err := html.New(options...).WriteCSS(&buf, style); err != nil {
return "", err
}
css := buf.String()
if opts.ModeSelector {
// Scope every selector under a top level mode class, e.g. ".dark .chroma".
// This allows generating both light and dark stylesheets and toggling
// them with a parent class on the page.
// There's no upstream option for this (I think), so do string replacements for now.
// TODO(bep) upstream option for this.
var prefix string
switch style.Mode() {
case chroma.Light:
prefix = ".light "
case chroma.Dark:
prefix = ".dark "
default:
return "", fmt.Errorf("style %q does not have a light or dark mode", style.Name)
}
replacer := strings.NewReplacer(
".bg {", prefix+".bg {",
".chroma ", prefix+".chroma ",
)
css = replacer.Replace(css)
}
return css, nil
}
// chromaCSSOverrides re-emits leaf token colors that Chroma's minifier drops
// because they equal the style's default foreground (the .chroma color). Without
// modeSelector scoping, a paired light/dark stylesheet shares the same .chroma
// scope, and the light sheet's explicit rule (e.g. .chroma .nx) would otherwise
// leak into dark mode, since an explicit declaration beats inheritance.
func chromaCSSOverrides(style *chroma.Style) map[chroma.TokenType]string {
bg := style.Get(chroma.Background)View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Switch to a style that declares a light or dark mode (e.g. "github", "github-dark").
- Pass an explicit `--mode=light` or `--mode=dark` so a mode-specific variant is selected up front.
- Disable the mode-selector option and generate a single unscoped stylesheet if you don't need class-based theme toggling.
Example fix
# before hugo gen chromastyles --style=some-legacy-style --modeSelector # after hugo gen chromastyles --style=github --mode=light --modeSelector
Defensive patterns
Strategy: fallback
Validate before calling
// pre-check: confirm the chosen style has at least one of light/dark variants
names := styles.Names()
if !slices.Contains(names, styleName) { /* reject */ } Try / catch
styles, err := highlight.GenerateStyles(cfg)
if err != nil {
log.Printf("style %q lacks light/dark modes, using default: %v", styleName, err)
cfg.Style = "github" // known dual-mode style chosen explicitly by the user
styles, err = highlight.GenerateStyles(cfg)
} Prevention
- Pick styles known to provide both light and dark variants when using auto mode switching
- Pin the style name in config and test `hugo` locally after changing it
- Treat this as a config error to fix at build time, not to paper over at runtime
When it happens
Trigger: Generating chromastyles CSS with the mode-selector option on, using a Chroma style whose registered mode is unspecified — typically an older or third-party style that never declared light/dark metadata. Reached only through the `opts.ModeSelector` code path in `ChromaStylesCSS`.
Common situations: Adopting Hugo's class-toggled light/dark highlighting scheme with a legacy style name carried over from an old config; custom or vendored Chroma styles without mode metadata; Chroma version bumps changing which styles carry mode info.
Related errors
- targetPath cannot be empty
- style %q does not have a %q mode
- invalid style: %s
- invalid mode: %s
- must not provide more arguments than resource object and opt
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/da949606902d31ef.json.
Report an issue: GitHub ↗.