Skip to content

check: text hidden under an opaque cover is reported twice, as text_occluded and as a contrast failure against the cover #7711

Open

Description

@wonjangcloud9

Context: I've been using HyperFrames every day for the past two weeks to make Korean Shorts and long-form videos, and it's been great to work with. This is one of a batch of things I ran into along the way (most came as small PRs from me today) — sorry for the pile-up, and thanks for your patience.

What happens

A source label sits under an opaque cover, a full-width card or a video placed over it:

<div id="label" style="position:absolute;left:60px;top:300px;font:40px sans-serif;color:#ccc">Source: Example Agency</div>
<div id="cover" style="position:absolute;left:0;top:200px;width:1080px;height:800px;background:#c8c4bc;z-index:2"></div>

hyperframes check --no-cache --json (main affa03b6, CLI 0.9.43):

layout   text_occluded        error  #label  Text is hidden beneath an opaque element.
contrast contrast_aa_failure  error  #label  Contrast is 1.08:1; WCAG AA requires 3:1.  fg rgb(204,204,204)  bg rgb(200,196,188)

One hidden element produces two errors. The second is not a measurement of anything a viewer sees: bg rgb(200,196,188) is the cover. The contrast audit hides the label's glyphs and samples the pixels inside the label's box, and the cover is what sits there. Its suggested fix ("Try rgb(…)") would recolour text nobody can see.

Current behaviour is partly intentional

#2366 made contrast skip fully covered text, but only under data-layout-allow-occlusion. Its description calls the covered-text ratios "meaningless low ratios". The probe comment in contrast-audit.browser.js (isClippedAway) says the probe runs "only when a clip-path is in effect … so genuinely-occluded but unclipped text is not skipped". So keeping unflagged occluded text in the contrast audit looks deliberate. That is why this is an issue, not a PR.

Proposal

When the layout audit reports text_occluded for an element at a sample time, leave that element out of the contrast audit at the same time. The occlusion error already names the problem, and the contrast number for that sample is measured against the occluder. Text that is only partly covered and still reported text_occluded (atomic labels flag at any hit) would also be left out for that sample, because its sample box mixes the occluder in.

Implementation sketch: collectSampleAt (packages/cli/src/utils/checkPipeline.ts) already has the layout issues for the time (issuesAtTime) before collectContrastAt. Hand the text_occluded selectors to collectContrast and drop those candidates in prepareContrast's page evaluate, by element identity (document.querySelector(selector) === restore.el). The two audits format selectors differently, so matching selector strings in Node would miss elements without ids.

Repro (index.html above) and main output are kept.

Activity

  1. added
    enhancementNew feature or request
    triage/needs-designEvidence is adequate; desired behavior or scope needs a maintainer decision.
    difficulty/mediumBounded work requiring subsystem knowledge, related paths, or additional regression fixtures.
    on Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    difficulty/mediumBounded work requiring subsystem knowledge, related paths, or additional regression fixtures.enhancementNew feature or requesttriage/needs-designEvidence is adequate; desired behavior or scope needs a maintainer decision.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions