Tailwind CSS 4.1: keep masks away from essential content

Compile and render a pinned component, catch a mask placed on the wrong ancestor, and inspect what survives when its visual effects disappear.
The mask belongs to the image
A bottom fade looks harmless until the masked element also contains the button. The button can still receive keyboard focus while its pixels disappear into the gradient. For a component that pairs an illustration with a decision, put the mask on the illustration and keep the heading, button and status outside every masked ancestor.
This explainer is for developers already compiling Tailwind styles who need to review that boundary. The example uses Tailwind CSS and CLI 4.1.18, locked in a disposable browser fixture on 13 October 2026. It is a historical version check, not a recommendation to install an old patch or a claim about the latest release. Ulties publishes this demonstration. It does not describe a customer deployment.
Compile the effect instead of guessing the CSS
The stylesheet below is the exact fixture entry. Its layout.css import includes the Tailwind import, panel layout, theme colours and button focus rule. The download contains those dependencies. Copying this file alone into an unrelated application does not recreate the example.
The heading uses separate shadow size and colour utilities. The image uses an opaque start at 60% and a transparent end at 100%, directed toward its bottom edge. These are positions within the mask, not percentages of the page or opacity values. The compiled stylesheet contains a mask-image declaration. The browser check separately confirms the image has a computed mask.
The shadow is optional decoration. The fixture puts light text on a dark surface or dark text on a light surface, and lowers the shadow colour opacity in light mode. A shadow over an arbitrary photograph would need its own contrast review. This example supplies no contrast measurement.
@import "./layout.css";
.hero-title {
@apply text-3xl font-semibold tracking-tight;
@apply text-shadow-sm text-shadow-black/40;
}
.hero-art {
@apply block h-48 w-full object-cover;
@apply mask-b-from-60% mask-b-to-100%;
}
Make the unmasked boundary visible in the template
The image and copy are siblings. Moving hero-art onto the section would mask the entire rendered panel, including its descendants. Moving only the button out of the image would not help if another ancestor still owned the mask. The fixture walks from each essential node to the document root and requires every computed mask-image to be none.
The SVG is decorative, so its alternative text is empty. The heading communicates the decision independently. If your image conveys a chart value, a product state or an instruction, this template is the wrong semantic starting point: preserve that information and provide an appropriate alternative.
The button has an explicit non-submit type. The included interaction.js changes the status to Release reviewed. when activated. This is a local demonstration of keyboard activation, not a release approval system or deployment action. It contains no persistence, permissions or network request.
<section class="hero-panel" aria-labelledby="release-title">
<img class="hero-art" src="art.svg" alt="">
<div class="hero-copy">
<h1 id="release-title" class="hero-title" data-essential>Review the release before it ships</h1>
<p data-essential>The image is decoration. Keep the decision and its control outside the fade.</p>
<button class="hero-action" type="button" data-essential>Review release</button>
<p id="review-status" role="status" data-essential>Release not reviewed.</p>
</div>
</section>
Prove the failure before accepting the component
The verifier compiles the stylesheet and opens the actual assembled template at 390px and 1440px in both themes. Its 41 normal observations check mask ownership, image masking, shadow presence, page overflow, empty alternative text, Tab focus, Enter activation and simulated effect loss. The eight screenshots show normal and fallback rendering. Inspect the visible focus outline as well as the DOM focus result.
The first negative control replaces the hero-art selector with hero-panel in the actual stylesheet. Compilation still succeeds, but the first essentialUnmasked observation becomes false. The verifier requires that exact failure and the preceding successful compilation observation. A timeout or browser crash does not count as detecting the defect.
The second control removes the title shadow utility. It must fail at titleShadow after the earlier mask observations pass. This catches a missing effect in this fixture. It does not make shadows an accessibility requirement. If your design intentionally omits the shadow, remove that product requirement and review the resulting component rather than keeping decoration just to satisfy a test.
Run it, then review what the browser test leaves out
Download and extract the source bundle, then run python3 verify.py --artifacts /tmp/tailwind-review with Python 3 and Docker available. The initial build retrieves locked dependencies. The normal and deliberately broken cases then run without external network access. A successful run prints a JSON record with status pass, actual versions, source hashes and exact observations. It also records both intended defect failures. The artifact directory contains compiled.css and eight PNGs.
To simulate effect loss, the test injects mask-image: none and text-shadow: none after the normal screenshot. The image becomes an ordinary rectangle, while the copy and control stay outside the affected layer. This tests a deliberately reduced appearance in the same Chromium engine. It does not reproduce an older browser’s parser, fallback cascade or rendering bugs.
The fixture does not test Safari, Firefox, text zoom, forced colours or screen readers. Empty alternative text, status markup, DOM focus and inspected screenshots are evidence for particular checks, not a complete accessibility certificate. Before using the component, test your required browsers and replacement asset, review focus and contrast at the real font sizes, and confirm that losing the decoration leaves the decision understandable.
Reproduce the examples
Exact stylesheet and template, companion layout, decorative SVG, local interaction, locked compiler and pinned Chromium runner. Requires Docker and Python 3. Includes normal results and two intended CSS failures; no deployment or cross-browser certification.
Download the source and tests (.zip)
