Apca contrast skill
Calculate APCA Lc contrast scores (APCA-W3 v0.1.9) and look up exact pass/fail per font size and weight using the official fontLookupAPCA table. Audits Tailwind v4 globals.css automatically (extracts :root and .dark blocks side-by-side), supports every CSS color notation (hex, rgb/rgba, hsl/hsla, oklch, oklab, color() with display-p3 / rec2020 / srgb / srgb-linear, and 148 named colors incl. rebeccapurple), handles alpha compositing, and offers Reverse APCA (find a color that hits target Lc on a given background). Use this skill whenever the user asks to measure, audit, or check contrast — design system tokens, light/dark mode color audits, accessibility reviews, shadcn/Tailwind oklch tokens, "is this readable?" questions. Trigger even when the user only says "contrast", "Lc", "readability", "a11y review", "대비", "가독성", or pastes color values without explicitly naming APCA — APCA is the modern WCAG 2.x successor used in WCAG 3 draft and gives more accurate perceptual results.From its SKILL.md
npx -y skills add rhino-ty/apca-contrast-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.7 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
APCA Contrast Calculator
APCA-W3 v0.1.9 with the official fontLookupAPCA matrix for precise pass/fail per font size and weight. Algorithm constants frozen at 0.0.98G-4g (Feb 2021), library version 0.1.9 (Jul 2022) — both verified current as of 2026.
The bundled Python script is deterministic and dependency-free. Use it instead of regenerating math at runtime.
When to use
- Auditing design system color tokens — usually one shot via
--from-tailwind - Comparing light vs dark mode contrast for the same semantic role
- Validating shadcn/Tailwind oklch tokens directly (no manual hex conversion)
- Checking whether a token works at a specific font size + weight (e.g. "does muted-foreground pass for 14px medium?")
- Reverse-engineering a color: "what shade do I need for Lc 75 on white?"
- Any "is this readable / does this pass" question
If the user asks about WCAG 2.x ratio, mention APCA is the modern successor (WCAG 3 draft) and offer Lc instead.
Color formats supported
| Format | Examples |
|---|---|
| Hex | #fff, #ffffff, #fff8, #ffffff80 |
| rgb / rgba | rgb(255 255 255), rgba(0,0,0,0.5), rgb(100% 0 0) |
| hsl / hsla | hsl(0 0% 50%), hsla(120, 100%, 25%, 0.5) |
| oklch | oklch(0.205 0 0), oklch(50% 0.1 200deg) |
| oklab | oklab(0.5 0.05 -0.1) |
| color() sRGB | color(srgb 1 0 0), color(srgb-linear 0.5 0.5 0.5) |
| color() P3 | color(display-p3 1 0 0) (auto-clamps to sRGB gamut) |
| color() 2020 | color(rec2020 1 0 0) (auto-clamps to sRGB gamut) |
| Named | red, white, rebeccapurple, transparent, ... |
Wide-gamut inputs (Display-P3, Rec.2020) are converted via D65 matrices and clamped to sRGB — any out-of-gamut clipping is silent. APCA itself targets sRGB displays, so this is the correct behavior, but mention it in reports if the user is working in a P3-aware pipeline.
Running the script
Path: ~/.claude/skills/apca-contrast/scripts/apca.py (Python stdlib only).
1. Single pair
python3 ~/.claude/skills/apca-contrast/scripts/apca.py "<fg>" "<bg>"
Output includes the signed Lc, polarity (BoW/WoB), the verdict, and minimum font size per weight (400/500/600/700) from the official fontLookupAPCA table. Add --matrix for the full size×weight pass/fail grid.
2. Tailwind v4 audit (preferred for this project)
python3 ~/.claude/skills/apca-contrast/scripts/apca.py \
--from-tailwind <path-to-globals.css>
Auto-extracts :root (light) and .dark blocks, pairs each common foreground token (foreground, muted-foreground, primary, secondary-foreground, accent-foreground, destructive, card-foreground, popover-foreground, border) against --background, and emits a side-by-side light/dark report. Use --bg-token <name> to audit against a different background (e.g. --bg-token card to check on-card contrast).
3. Batch mode
python3 ~/.claude/skills/apca-contrast/scripts/apca.py --batch pairs.json
Two batch shapes:
[
{"name": "foreground on bg", "fg": "#0a0a0a", "bg": "#ffffff"},
{"name": "primary on card", "fg": "oklch(0.205 0 0)", "bg": "oklch(0.985 0 0)"}
]
{
"background": "#ffffff",
"tokens": {
"foreground": "#0a0a0a",
"muted-foreground": "rgb(115, 115, 115)"
}
}
4. Reverse APCA — find a passing color
python3 ~/.claude/skills/apca-contrast/scripts/apca.py --reverse <bg> <target_lc>
Returns the closest neutral grey foreground that hits the target |Lc|. Useful for: "what shade do I need for body text on this card?"
5. JSON output
Add --json to any command for structured output (use when feeding into a report).
Interpreting Lc — the official ladder
The script attaches a verdict using the fontLookupAPCA Ascend matrix (apca-w3 0.1.9), keyed at weight 400. Always also report the per-weight breakdown — heavier weights pass at smaller sizes.
| Lc (abs) | Practical meaning at 400wt |
|---|---|
| ≥ 90 | Body text any size (≥14–16px @ 400) |
| 75 – 89 | Body text comfortable (≥16–17px @ 400) |
| 60 – 74 | Body minimum (≥18–24px @ 400) |
| 45 – 59 | Large text only (≥28–42px @ 400) — headlines |
| 30 – 44 | Non-text UI / very large headlines only |
| 15 – 29 | Decorative / disabled UI — fails for text |
| < 15 | Insufficient |
For typical body copy targets: Lc ≥ 75 is comfortable, Lc ≥ 60 is the practical floor at 16px+. The script gives you the exact px threshold so you don't have to eyeball this ladder.
Reporting back
After running the script:
- Lead with the headline finding (which tokens fail, which are borderline).
- Show a clean markdown table — extract Name, Lc, and Verdict columns; don't dump raw script output.
- For light/dark audits, flag asymmetries explicitly. A token that's Lc 90+ in one mode and Lc 30–50 in the other is the most common design-system bug.
- If the user pasted alpha colors or wide-gamut colors, mention the relevant caveat (alpha compositing is unofficial; wide-gamut clamps to sRGB).
Limitations to flag honestly
These caveats matter — don't let the user walk away thinking an APCA-pass is the same as "fully accessible / legally safe / works for everyone."
What APCA itself cannot tell you (mention these proactively)
- APCA does NOT replace WCAG 2.x for legal compliance. ADA, EN 301 549, KWCAG, Section 508 — every legal regime still mandates WCAG 2.x AA. APCA is a WCAG 3 draft, not law. If the user mentions "audit," "compliance," "WCAG," "ADA," "법적," "공공기관," "장애인차별금지법," or anything similar, explicitly tell them this skill does NOT cover legal compliance and recommend they run a WCAG 2.x ratio checker alongside (browser DevTools accessibility panel, axe DevTools, WAVE).
- Single perceptual model. APCA assumes one "average user." Color blindness, low vision, age-related changes aren't differentiated. For projects with known low-vision audiences, recommend additional vision-simulation tools.
- Contrast ≠ readability. Lc is necessary but not sufficient. Cramped line-height, tight tracking, and bad fonts can make Lc-90 text hard to read. If the user is reviewing real UI screens, mention typography is also a factor.
- Lc 0–30 zone is noisy. When findings land in this range, say "this fails" — don't quote the exact Lc as if it were precise to one decimal.
- Font family and rendering are ignored. The minimum px values from
fontMatrixAscendare a starting point, not the final word. Pretendard 14px ≠ Times New Roman 14px in actual readability. - Algorithm frozen since Feb 2021. New display tech (HDR, OLED light bleed, P3) isn't reflected. If the user's audience is on modern devices, mention real on-screen contrast may differ slightly from APCA's prediction.
This implementation's specific limits
- Alpha compositing is not part of the APCA spec. The script pre-composites in sRGB (pragmatic default) and adds a warning. For arbitrary content behind translucent overlays (hero images with text), the approximation can be wrong by 5–10 Lc.
fontLookupAPCAAscend variant only. Body text. Other variants (headlines, non-text UI) aren't bundled.- Rounding: lookup keys round abs(Lc) down to the nearest 5. Lc 73 and Lc 70 hit the same row. Surface this when results are right on a boundary.
- No CSS variable resolution. If a token's value is
var(--other), the user must resolve it or run the extractor against built CSS, not source. - Wide-gamut inputs clamp to sRGB.
color(display-p3 ...)is approximated, not true P3-display contrast.
Common findings to watch for
destructiveandprimarytokens often fail in dark mode while passing in light mode — the brand color was tuned on a light page and doesn't have enough luminance contrast against a dark background. Flag it; a separatedestructive-dark/primary-darkshade is usually the fix.muted-foregroundtypically lands in the Lc 50–70 range. Fine for 16px+ body, fails for 12–14px caption text. Recommend "use only for ≥16px" or darken.bordertokens score Lc 5–20. Acceptable for non-text UI per APCA, but flag if the same color is reused for placeholder text.- A token that scores below Lc 30 in either mode and is used for any text role is a hard failure — call this out explicitly.
What ships with it: 12 files
77.8 KB alongside SKILL.md, 2 of them executable
examples/
- sample-audit-report.md5.4 KB
- sample-batch.json809 B
- sample-globals.css1.6 KB
references/
- APCA-ALGORITHM.md10.2 KB
- FONT-LOOKUP-TABLE.md5.2 KB
scripts/
- apca.pyruns24.9 KB
- _tables.pyruns10.6 KB
- CHANGELOG.md2.5 KB
- .gitignore212 B
- LICENSE1.0 KB
- NOTICE741 B
- README.md14.5 KB