Fix: detect placeholder contrast (#790) (#799)

* Fix: detect placeholder contrast (#790)

`detect` never read `::placeholder` color, so pale placeholders passed. Score them with the same WCAG math as body text, without host class/clip heuristics.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: match descendant ::placeholder hosts (#790)

`.form ::placeholder` kept the ancestor as the host. Reuse the hover combinator star-fill so the color lands on the inputs inside.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: placeholder-shown and gradient alpha (#790)

Browser scans skip when :placeholder-shown is false, so a live filled field does not keep the HTML value attribute's empty state. Translucent placeholders flatten over each gradient stop before scoring.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: trailing combinator only for ::placeholder hosts (#790)

`star_empty_compounds` turned `.label + ::placeholder` into `.label *+*`. Fill only a trailing empty compound so adjacent-sibling hosts still match.

Prepared with AI assistance.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-09-10 13:49:59 -07:00
committed by GitHub
co-authored by Cursor
parent 3e1f67c52c
commit cb56ed6c19
17 changed files with 688 additions and 137 deletions
+92
View File
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Placeholder Contrast — Should Flag vs Should Pass</title>
<style>
body { font-family: system-ui, sans-serif; background: #fafafa; padding: 24px; margin: 0; color: #1a1a1a; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 1200px; margin: 0 auto; }
.col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
.col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 24px 0 8px; color: #64748b; }
.field {
width: 280px;
height: 40px;
padding: 8px 12px;
font-size: 16px;
border: 1px solid #cbd5e1;
border-radius: 6px;
box-sizing: border-box;
display: block;
margin-bottom: 8px;
}
.field-white { background: #ffffff; }
.field-light { background: #f5f5f5; }
.field-dark { background: #1a1a1a; border-color: #333; }
.flag-pale-white::placeholder { color: #bbbbbb; }
.flag-pale-textarea::placeholder { color: #bbbbbb; }
.flag-translucent-light::placeholder { color: rgba(255, 255, 255, 0.4); }
.dark-wrap { background: #0f0f11; padding: 20px; width: 320px; }
.frosted-panel {
background: rgba(255, 255, 255, 0.15);
padding: 12px;
}
.frosted-panel .field {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.25);
}
.frosted-panel .field::placeholder { color: #bbbbbb; }
.pass-ink-white::placeholder { color: #1a1a1a; }
.pass-light-dark::placeholder { color: #e8e8e8; }
.pass-filled-pale::placeholder { color: #bbbbbb; }
.pass-unstyled { /* no ::placeholder rule — UA color only */ }
</style>
</head>
<body>
<div class="grid">
<div class="col" data-col="flag">
<h2>Should flag</h2>
<h3>Pale placeholder on white input</h3>
<input class="field field-white flag-pale-white" type="text" placeholder="Pale Placeholder On White Field">
<h3>Pale placeholder on white textarea</h3>
<textarea class="field field-white flag-pale-textarea" rows="2" placeholder="Pale Placeholder On White Textarea"></textarea>
<h3>Translucent placeholder on light field</h3>
<input class="field field-light flag-translucent-light" type="text" placeholder="Translucent Placeholder On Light Field">
<h3>Pale placeholder on frosted panel</h3>
<div class="dark-wrap">
<div class="frosted-panel">
<input class="field" type="text" placeholder="Pale Placeholder On Frosted Panel">
</div>
</div>
</div>
<div class="col" data-col="pass">
<h2>Should pass</h2>
<h3>Ink placeholder on white field</h3>
<input class="field field-white pass-ink-white" type="text" placeholder="Ink Placeholder On White Field">
<h3>Light placeholder on dark field</h3>
<input class="field field-dark pass-light-dark" type="text" placeholder="Light Placeholder On Dark Field">
<h3>Input with no placeholder attribute</h3>
<input class="field field-white" type="text" value="">
<h3>Filled field hides placeholder</h3>
<input class="field field-white pass-filled-pale" type="text" value="Already filled" placeholder="Filled Field Hides Placeholder">
<h3>Unstyled placeholder uses UA color</h3>
<input class="field field-white pass-unstyled" type="text" placeholder="Unstyled Placeholder Uses UA Color">
<h3>Empty placeholder attribute</h3>
<input class="field field-white flag-pale-white" type="text" placeholder="">
</div>
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
{
"stdout": "",
"stderr": "415 anti-patterns found.\n17 advisory notes (not counted).\n",
"stderr": "419 anti-patterns found.\n17 advisory notes (not counted).\n",
"exit": 2,
"signal": null,
"files": {}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
{
"stdout": "[\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"<REPO>/tests/fixtures/antipatterns/placeholder-contrast.html\",\n \"line\": 0,\n \"snippet\": \"placeholder \\\"Pale Placeholder On White Field\\\" 1.9:1 (need 4.5:1) — text #bbbbbb on #ffffff\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"<REPO>/tests/fixtures/antipatterns/placeholder-contrast.html\",\n \"line\": 0,\n \"snippet\": \"placeholder \\\"Pale Placeholder On White Textarea\\\" 1.9:1 (need 4.5:1) — text #bbbbbb on #ffffff\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"<REPO>/tests/fixtures/antipatterns/placeholder-contrast.html\",\n \"line\": 0,\n \"snippet\": \"placeholder \\\"Translucent Placeholder On Light Field\\\" 1.0:1 (need 4.5:1) — text #f9f9f9 on #f5f5f5\"\n },\n {\n \"antipattern\": \"low-contrast\",\n \"name\": \"Low contrast text\",\n \"description\": \"Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\",\n \"severity\": \"warning\",\n \"category\": \"quality\",\n \"file\": \"<REPO>/tests/fixtures/antipatterns/placeholder-contrast.html\",\n \"line\": 0,\n \"snippet\": \"placeholder \\\"Pale Placeholder On Frosted Panel\\\" 3.5:1 (need 4.5:1) — text #bbbbbb on #5c5c5d\"\n }\n]\n",
"stderr": "",
"exit": 2,
"signal": null,
"files": {}
}
@@ -0,0 +1,7 @@
{
"stdout": "",
"stderr": "\n<REPO>/tests/fixtures/antipatterns/placeholder-contrast.html\n [low-contrast] placeholder \"Pale Placeholder On White Field\" 1.9:1 (need 4.5:1) — text #bbbbbb on #ffffff\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [low-contrast] placeholder \"Pale Placeholder On White Textarea\" 1.9:1 (need 4.5:1) — text #bbbbbb on #ffffff\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [low-contrast] placeholder \"Translucent Placeholder On Light Field\" 1.0:1 (need 4.5:1) — text #f9f9f9 on #f5f5f5\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n [low-contrast] placeholder \"Pale Placeholder On Frosted Panel\" 3.5:1 (need 4.5:1) — text #bbbbbb on #5c5c5d\n → Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.\n\n4 anti-patterns found.\n",
"exit": 2,
"signal": null,
"files": {}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long