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

  1. Switch to a style that declares a light or dark mode (e.g. "github", "github-dark").
  2. Pass an explicit `--mode=light` or `--mode=dark` so a mode-specific variant is selected up front.
  3. 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

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


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/da949606902d31ef.json. Report an issue: GitHub ↗.