d2lang/d2 · error

invalid color "%s"

Error message

invalid color "%s"

What it means

Darken reduces a color's luminance by 10%. If the input is one of d2's theme color tokens (checked via IsThemeColor, e.g. B1-B6, AA2-AA5, N1-N7), it maps through a hardcoded switch; any theme-style token not covered by that switch is rejected with this error instead of falling through to CSS parsing.

Source

Thrown at lib/color/color.go:72

		case colorString[0] == 'N':
			switch colorString[1] {
			case '1', '2':
				return N1, nil
			case '3':
				return N2, nil
			case '4':
				return N3, nil
			case '5':
				return N4, nil
			case '6':
				return N5, nil
			case '7':
				return N6, nil
			}
		}

		return "", fmt.Errorf("invalid color \"%s\"", colorString)
	}

	return darkenCSS(colorString)
}

func darkenCSS(colorString string) (string, error) {
	c, err := csscolorparser.Parse(colorString)
	if err != nil {
		return "", err
	}
	h, s, l := colorful.Color{R: c.R, G: c.G, B: c.B}.Hsl()
	// decrease luminance by 10%
	return colorful.Hsl(h, s, l-.1).Clamped().Hex(), nil
}

func LuminanceCategory(colorString string) (string, error) {
	// check if colorString matches the `url('#grad-<sha1-hash>')` format
	// which is used to refer to a <linearGradient> or <radialGradient> element.

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Use a valid theme token: B1–B6, AA2/AA4/AA5, or N1–N7
  2. If passing a regular color, use CSS syntax (hex, rgb(), named) so Darken routes to darkenCSS
  3. Check IsThemeColor first and validate the token against the supported set
  4. If you added a new theme constant, extend Darken's switch

Example fix

// before
c, err := color.Darken("B9") // invalid token
// after
c, err := color.Darken("B3") // or color.Darken("#4A6FA5")
Defensive patterns

Strategy: validation

Validate before calling

var themeToken = regexp.MustCompile(`^(B[1-6]|AA[245]|N[1-7])$`)
func canDarken(s string) bool {
    if themeToken.MatchString(s) {
        return true
    }
    _, err := csscolorparser.Parse(s)
    return err == nil
}

Type guard

func isThemeColor(s string) bool {
    switch {
    case strings.HasPrefix(s, "B") && len(s) == 2 && s[1] >= '1' && s[1] <= '6':
        return true
    case strings.HasPrefix(s, "AA") && (s[2] == '2' || s[2] == '4' || s[2] == '5'):
        return true
    case strings.HasPrefix(s, "N") && s[1] >= '1' && s[1] <= '7':
        return true
    }
    return false
}

Try / catch

dark, err := color.Darken(input)
if err != nil {
    log.Printf("cannot darken %q, using original: %v", input, err)
    dark = input
}

Prevention

When it happens

Trigger: Calling lib/color.Darken with a theme color token whose prefix/number is not in the switch (e.g. B7, AA1, N8, or a malformed token like 'X2'), or a non-theme string shorter than the checks.

Common situations: Typoed theme color names in diagrams/styles, new theme colors added upstream but not to Darken's switch, or users passing arbitrary color strings they assumed were theme tokens.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/ac2973c589f28ff9. Report an issue: GitHub.