Skip to content

Bug: font-family-fallbacks reports an error for generic fonts written with uppercase letters #516

Description

@electrohyun

Environment

ESLint version: v10.8.0
@eslint/css version: v1.4.0
Node version: v22.22.3
npm version: v10.8.2
Operating System: StackBlitz WebContainer (linux)

Which language are you using?

stylesheet

What did you do?

// Configuration
// eslint.config.js
import css from "@eslint/css";

export default [
	{
		files: ["**/*.css"],
		plugins: { css },
		language: "css/css",
		rules: { "css/font-family-fallbacks": "error" },
	},
];
a { font-family: Arial, Sans-Serif; }
b { font-family: Arial, SERIF; }
c { font-family: Serif; }
d { font: 16px Arial, MONOSPACE; }

/* control cases, correctly not reported */
e { font-family: Arial, sans-serif; }
f { font-family: serif; }

What did you expect to happen?

I expected no errors to be reported for any of the six declarations above.

Generic family keywords are pre-defined CSS keywords, and the CSS specification states that keywords are matched ASCII case-insensitively. From CSS Values and Units Module Level 4, §4.1 Pre-defined Keywords:

(Keywords are identifiers and are interpreted ASCII case-insensitively (i.e., [a-z] and [A-Z] are equivalent))

Generic families are keywords only when they are unquoted — CSS Fonts Module Level 4 notes that <generic-font-family> keywords cannot be quoted, otherwise they are interpreted as a <font-family-name>. All four cases above are unquoted, so they are the keyword form.

So Sans-Serif, SERIF and MONOSPACE are valid generic font families and mean exactly the same thing as their lowercase forms. Only the lowercase forms are accepted right now.

What actually happened?

The four declarations that use uppercase letters are reported, while the two lowercase control cases are not:

❯ npx eslint .

/home/projects/stackblitz-starters-adhqdqr2/test.css
  1:18  error  Use a generic font last                     css/font-family-fallbacks
  2:18  error  Use a generic font last                     css/font-family-fallbacks
  3:18  error  Use fallback fonts and a generic font last  css/font-family-fallbacks
  4:11  error  Use a generic font last                     css/font-family-fallbacks

✖ 4 problems (4 errors, 0 warnings)

The fact that only the uppercase variants are reported shows that the cause is case-sensitive matching.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/stackblitz-starters-adhqdqr2

Participation

  • I am willing to submit a pull request for this issue.

AI acknowledgment

  • I did not use AI to generate this issue report.
  • (If the above is not checked) I have reviewed the AI-generated content before submitting.

Additional comments

Hello. I looked into where this comes from, and I would like to share what I found.

Where the bug is

In src/rules/font-family-fallbacks.js, the genericFonts set is defined entirely in lowercase (L20–L34), and the candidate tokens are compared against it without any case normalization:

Line Code
L101 !genericFonts.has(valueList.at(-1))
L183 / L189 genericFonts.has(variableList[...])
L198 genericFonts.has(valueArr[0].name)
L261 genericFonts.has(fontsList.at(-1))
L273 genericFonts.has(lastFont.name)
L356 / L389 / L400 / L444 genericFonts.has(...)

What makes me think this is an oversight rather than an intentional decision is that the same file already normalizes case for the other kind of keyword it handles. CSS-wide keywords are lowercased in two places:

// L43
return cssWideKeywords.has(value.trim().toLowerCase());

// L135, inside create()
sourceCode.lexer.cssWideKeywords.map(keyword => keyword.toLowerCase())

So CSS-wide keywords are treated case-insensitively, but generic font keywords in the very same rule are not.

Precedent

While searching for duplicates I found that this repository has fixed this same class of bug already:

font-family-fallbacks looks like the one rule that was left out. I could not find an existing issue or pull request covering this, so I am reporting it.

If this is accepted, I'd like to take it on, following the same approach as #222.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Status
Ready to Implement

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions