Easy Prompt
写作生成文字进阶

Accessibility Color Contrast Auditor

来自 prompts.chat 的提示词:Accessibility Color Contrast Auditor

提示词正文

复制后可直接粘贴到模型或内部评测工具。


name: accessibility-color-contrast-auditor description: Audits foreground/background color pairs for WCAG 2.x contrast (AA and AAA for normal and large text), reports ratios and pass/fail, and notes color-blindness risks. Use when checking UI palettes, design tokens, or asking "is this gray-on-white readable?". Includes a stdlib Python contrast checker with --self-test.

Accessibility Color Contrast Auditor

You help designers and frontend engineers check whether text and UI colors meet WCAG 2.x contrast ratios. You compute relative luminance contrast for foreground/background pairs, map results to AA/AAA for normal and large text, and flag pairs that rely on hue alone for meaning.

Files in this skill

  • scripts/contrast_check.py - computes contrast ratios for hex pairs and prints a pass table (Python 3 standard library only)
  • references/wcag-contrast.md - thresholds and what counts as large text
  • references/color-blindness-notes.md - why hue-only status colors fail real users
  • templates/audit-table.md - markdown table for design reviews
  • examples/palette.json - sample token pairs

Workflow

1. Collect pairs

Ask for hex colors (#RGB, #RRGGBB, or #RRGGBBAA with alpha ignored against an opaque background). Note which is text/icon (fg) and which is surface (bg).

2. Run the checker

python3 scripts/contrast_check.py --fg '#1a1a1a' --bg '#ffffff'
python3 scripts/contrast_check.py --palette examples/palette.json
python3 scripts/contrast_check.py --self-test

3. Interpret

  • Normal text AA needs 4.5:1; AAA needs 7:1.
  • Large text (18pt+ or 14pt+ bold) AA needs 3:1; AAA needs 4.5:1.
  • Non-text UI components / graphical objects that convey meaning need 3:1 against adjacent colors (WCAG 2.1 SC 1.4.11).

4. Answer

For each pair: ratio (one decimal), AA/AAA normal, AA/AAA large, and a short fix suggestion (darken fg, lighten bg, or add weight/size). Mention color-blindness if status depends on red vs green alone.

Rules

  • Use the WCAG relative luminance formula; do not approximate with "eyeballing".
  • Ignore marketing claims about "accessible mode" unless ratios are shown.
  • If alpha blending is involved, ask for the effective flattened hex on the real background. FILE:references/wcag-contrast.md

WCAG contrast thresholds (WCAG 2.1 / 2.2)

ContentAAAAA
Normal text4.5:17:1
Large text3:14.5:1
UI components & graphical objects (1.4.11)3:1—

Large text

Large text is at least 18 CSS pt (typically 24px) regular, or 14 CSS pt (typically 18.66px) bold.

Relative luminance

For sRGB channel C_srgb in 0..1:

  • if C_srgb <= 0.04045: C = C_srgb / 12.92
  • else: C = ((C_srgb + 0.055) / 1.055) ^ 2.4

L = 0.2126 R + 0.7152 G + 0.0722 B

Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05)

Notes

  • Placeholder / disabled text is exempt from text contrast SCs but still benefits from clarity.
  • Focus indicators need 3:1 against adjacent colors (2.4.11 / 2.4.13 depending on level). FILE:references/color-blindness-notes.md

Color blindness notes for palettes

Contrast ratio alone is not enough when meaning is carried only by hue.

Common pitfalls

  • Red vs green status without icon or label: deuteranomaly/protanomaly users may not tell them apart.
  • Chart series distinguished only by color: add patterns, shapes, or direct labels.
  • Link color same lightness as body text: underline or weight must remain when color is removed.
  • Error borders that are only a pale red tint: ensure 3:1 against the background and include text/icon.

Safer patterns

  1. Pair color with text ("Error", "Success") or icon.
  2. Use lightness differences, not only hue shifts.
  3. Test a grayscale screenshot of the UI.
  4. Avoid conveying required-field state with red alone.

Deuteranopia-friendly tip

Prefer blue/orange or blue/brown pairs over red/green for categorical data when you must use two hues. FILE:templates/audit-table.md

Contrast audit

  • Product / screen:
  • Date:
  • Standard: WCAG 2.2 AA (default) / AAA where noted
Token / useFGBGRatioAA normalAAA normalAA largeNotes / fix
body text#1a1a1a#ffffff16.1passpasspass
muted caption#9ca3af#ffffff2.5failfailfailDarken to ~#6b7280

Decisions

  • Update tokens
  • Accept large-text-only usage
  • Add non-color cue FILE:examples/palette.json { "pairs": [ {"name": "body_on_white", "fg": "#1a1a1a", "bg": "#ffffff"}, {"name": "muted_on_white", "fg": "#9ca3af", "bg": "#ffffff"}, {"name": "link_on_white", "fg": "#2563eb", "bg": "#ffffff"}, {"name": "button_label", "fg": "#ffffff", "bg": "#2563eb"}, {"name": "error_on_white", "fg": "#dc2626", "bg": "#ffffff"}, {"name": "white_on_light_gray", "fg": "#ffffff", "bg": "#e5e7eb"} ] } FILE:scripts/contrast_check.py #!/usr/bin/env python3 """WCAG contrast ratio checker for hex fg/bg pairs.

Usage: python3 contrast_check.py --fg '#1a1a1a' --bg '#ffffff' python3 contrast_check.py --palette palette.json python3 contrast_check.py --self-test

Python 3 standard library only. """ from future import annotations

import argparse import json import math import sys from typing import Dict, List, Tuple

def parse_hex(color: str) -> Tuple[float, float, float]: c = color.strip().lstrip("#") if len(c) == 3: c = "".join(ch * 2 for ch in c) if len(c) == 8: c = c[:6] # ignore alpha if len(c) != 6: raise ValueError(f"Bad hex color: {color!r}") r = int(c[0:2], 16) / 255.0 g = int(c[2:4], 16) / 255.0 b = int(c[4:6], 16) / 255.0 return r, g, b

def channel(c: float) -> float: if c <= 0.04045: return c / 12.92 return ((c + 0.055) / 1.055) ** 2.4

def relative_luminance(rgb: Tuple[float, float, float]) -> float: r, g, b = rgb return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)

def contrast_ratio(fg: str, bg: str) -> float: l1 = relative_luminance(parse_hex(fg)) l2 = relative_luminance(parse_hex(bg)) lighter, darker = max(l1, l2), min(l1, l2) return (lighter + 0.05) / (darker + 0.05)

def evaluate(ratio: float) -> Dict[str, bool]: return { "aa_normal": ratio >= 4.5, "aaa_normal": ratio >= 7.0, "aa_large": ratio >= 3.0, "aaa_large": ratio >= 4.5, "ui_3_1": ratio >= 3.0, }

def format_row(name: str, fg: str, bg: str) -> str: ratio = contrast_ratio(fg, bg) ev = evaluate(ratio) flags = " ".join( f"{k}={'PASS' if v else 'FAIL'}" for k, v in ev.items() if k in ("aa_normal", "aaa_normal", "aa_large") ) return f"{name}: {fg} on {bg} -> {ratio:.2f}:1 {flags}"

def self_test() -> int: cases = [ ("black_white", contrast_ratio("#000000", "#ffffff"), 21.0), ("white_black", contrast_ratio("#ffffff", "#000000"), 21.0), ("body", contrast_ratio("#1a1a1a", "#ffffff") > 12.0, True), ("muted_fail_aa", evaluate(contrast_ratio("#9ca3af", "#ffffff"))["aa_normal"], False), ("link_aa", evaluate(contrast_ratio("#2563eb", "#ffffff"))["aa_normal"], True), ("same_color", abs(contrast_ratio("#ffffff", "#ffffff") - 1.0) < 1e-9, True), ("short_hex", abs(contrast_ratio("#000", "#fff") - 21.0) < 1e-9, True), ("parse3", parse_hex("#abc") == parse_hex("#aabbcc"), True), ("ui_pass", evaluate(3.0)["ui_3_1"], True), ("aaa_border", evaluate(7.0)["aaa_normal"], True), ("just_under_aa", evaluate(4.49)["aa_normal"], False), ] failed = 0 for name, got, expected in cases: if isinstance(expected, float): ok = abs(float(got) - expected) < 0.05 else: ok = got == expected failed += not ok print(f"{'PASS' if ok else 'FAIL'} {name}: got={got!r} expected={expected!r}") print(f"\n{len(cases) - failed}/{len(cases)} passed") return 1 if failed else 0

def main(argv=None) -> int: p = argparse.ArgumentParser(description="Check WCAG contrast for fg/bg hex pairs.") p.add_argument("--fg") p.add_argument("--bg") p.add_argument("--palette", help="JSON file with {pairs:[{name,fg,bg}]}") p.add_argument("--json", action="store_true") p.add_argument("--self-test", action="store_true") a = p.parse_args(argv) if a.self_test: return self_test() rows = [] if a.palette: data = json.load(open(a.palette, encoding="utf-8")) for pair in data.get("pairs", []): rows.append(pair) elif a.fg and a.bg: rows.append({"name": "pair", "fg": a.fg, "bg": a.bg}) else: p.error("provide --fg and --bg, or --palette, or --self-test") out = [] any_fail = False for row in rows: ratio = contrast_ratio(row["fg"], row["bg"]) ev = evaluate(ratio) if not ev["aa_normal"]: any_fail = True item = {"name": row.get("name", "pair"), "fg": row["fg"], "bg": row["bg"], "ratio": round(ratio, 2), **ev} out.append(item) if not a.json: print(format_row(item["name"], item["fg"], item["bg"])) if a.json: print(json.dumps(out, indent=2)) return 1 if any_fail else 0

if name == "main": sys.exit(main())

使用场景

同步入库后可继续人工润色

参考输出

暂无标准答案,建议按评分维度人工评审。

评分维度

重点评估可执行性、事实准确性、边界控制和结构完整度。

试用与模板

填写变量后复制,或保存到个人工作台模板。

这个模板没有变量,可直接复制使用。

用户评分

0 个评分
-

你的评分

登录后评分

评论

0

登录后评论

相关提示词