Quick answer: If Mask shows the wrong area, check that the mask graphic and its RectTransform match the desired clip region; ShowMaskGraphic toggles whether the source image is also drawn. For text bleeding past the mask, ensure TMP submeshes use a mask-compatible material and call SetMaterialDirty after layout changes.

Here is how to fix Unity UI Masks that clip too much, too little, or seem to clip arbitrary unrelated elements. The Mask component uses the stencil buffer; alignment quirks, RectMask2D differences, and TextMeshPro’s independent submesh handling can produce surprising visual results. The fix depends on which mask type you are using.

The Symptom

A scrolling list cuts off the wrong rows. A circular avatar mask shows square corners. Text inside a Mask spills out past the edges. Or the mask graphic itself is invisible when you expected it to be drawn.

What Causes This

ShowMaskGraphic confusion. Mask defaults to hiding the source image (it is just a stencil reference). Without ShowMaskGraphic, the silhouette clips children but is itself invisible.

Mask vs RectMask2D semantics. RectMask2D clips along the RectTransform’s rect, regardless of the image. Mask uses the image’s alpha as the stencil shape.

TMP submesh isolation. TextMeshPro creates submeshes for material variations (e.g., different fonts in one label). Each submesh needs to be told about the mask separately.

Mismatched RectTransform anchoring. The mask’s RectTransform anchors to its parent, which may resize unexpectedly with layout changes, shifting the clip region.

The Fix

Step 1: Decide between Mask and RectMask2D.

// Mask:
//  + Any shape (rounded corners, circles, sprite alpha)
//  - Uses stencil buffer; one extra draw call per mask
//  - Nested masks limited by stencil depth

// RectMask2D:
//  + Cheap; no stencil draw call
//  + Soft-pixel clipping (better edges)
//  - Only rectangular
//  - Some shaders need to read clipping rect uniform manually

Use Mask for hero portraits, custom shapes. Use RectMask2D for scrolling lists, viewport content, anything rectangular.

Step 2: Toggle Show Mask Graphic. If you want the mask source to be visible (for example, a portrait frame around a clipped photo), check Show Mask Graphic on the Mask. Otherwise leave unchecked — the children are clipped but the mask itself is invisible.

Step 3: For RectMask2D, ensure children render with mask-aware shaders.

// Standard UI shaders (UI/Default, TMP_Default) read the clip rect
// Custom shaders need to:
//   #include "UnityUI.cginc"
//   sample _ClipRect uniform and discard outside it
fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    col.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
    return col;
}

Step 4: Refresh TMP after layout.

using TMPro;

void RefreshTextInsideMask(TextMeshProUGUI text)
{
    text.ForceMeshUpdate();
    var subs = text.GetComponentsInChildren<TMP_SubMeshUI>();
    foreach (var s in subs) s.SetMaterialDirty();
}

Call after the scroll position changes or the mask’s parent resizes.

Step 5: Avoid deeply nested masks. Stencil buffer depth is limited (effectively 8-bit, so ~8 nested Masks). Beyond that, masks misbehave. Replace deep nests with RectMask2D where possible.

Common Pitfalls

Mask on a Canvas with ScreenSpace - Camera in worldspace can produce unexpected clipping if the camera plane is angled. Use ScreenSpace - Overlay for simple masking unless you specifically need camera-space.

RectMask2D + TextMeshPro requires TMP >= 3.x for proper soft clipping. Older TMP versions hard-clip with stair-step edges.

Understanding the issue

UI frameworks have their own lifecycle (mount, update, unmount). When game state changes faster than the UI can respond, you get either stale displays or visible flicker.

The specific bug described above is the kind that surfaces during integration rather than unit testing. It depends on a combination of factors: the asset configuration, the runtime state, the platform's specific behavior. In isolation, each piece looks correct; in combination, the bug emerges. This is why thorough integration testing - playing the actual game in realistic conditions - catches things that automated tests miss.

Why this happens

This bug class disproportionately affects late-stage development. The work to surface it is interactive testing in realistic conditions, which only really happens after the gameplay is in place and assets are populated. Catching it early requires deliberate testing of conditions that look unimportant.

At the engine level, the behavior comes from a deliberate design decision in Unity. The engine team chose a particular trade-off - usually performance versus convenience, or generality versus specificity - and that trade-off has consequences when you push against it. Understanding the trade-off is what turns 'this bug is mysterious' into 'this bug is the expected consequence of this design'.

Verifying the fix

For shipping games, the safest verification is a staged rollout. Apply the fix to 1% of players for 24 hours; watch the affected metric; expand if green. Skipping the staged rollout means the verification is the entire player base, which is too high a stakes for most fixes.

Reproducibility is the prerequisite for verification. If you can't reliably reproduce the bug pre-fix, you can't reliably verify it post-fix. Spend time getting a clean reproduction before you write any fix code. The fix is fast once you understand the reproduction; the reproduction is the slow part.

Variations to watch for

Related bug classes often share the same root cause. If you find yourself fixing this issue, look for cousins: similar symptoms in adjacent systems, the same data flow but a different value, or the same fix pattern in another module. The catalog of 'we've seen this before' becomes valuable institutional knowledge.

Adjacent bugs often share a root cause. After fixing the case you've found, spend an hour searching the codebase for similar patterns. What's the same call with different arguments? The same data flow with a different entity type? The same lifecycle issue in a sibling system? Each match is a candidate for the same fix, or a related fix that prevents future bugs of the same class.

In production

For shipping titles with a long support window, watch for this issue resurfacing after dependency updates. Engine upgrades, driver updates, OS releases - each one can resurface a bug class you thought you'd fixed because the underlying behavior changed slightly. Regression tests catch the obvious ones; player reports catch the rest.

When triaging a similar issue in production, prioritize gathering data over hypothesizing causes. A player report describes a symptom; what you need is a build SHA, a session timestamp, and ideally a screen recording or session replay. With those, the bug becomes tractable. Without them, you're guessing at hypothetical reproductions that may not match what the player actually hit.

Performance considerations

If this issue manifests under high load (many actors, many particles, many network connections), profile the post-fix code path with realistic counts. The original cost was a bug; the new cost is real work, and real work has a budget.

Diagnostic approach

Diagnosing this class of bug benefits from a structured approach: confirm the symptom, isolate the variables, hypothesize the cause, and verify the hypothesis before writing fix code. Skipping the isolation step is the most common mistake; without it, fixes often address symptoms while the underlying cause continues to produce other variations.

For Unity-specific diagnostics, the editor's profiler is the canonical starting point. Capture a representative frame with the symptom present; compare against a frame without the symptom; the diff often points directly at the cause. If the symptom is non-deterministic, capture multiple frames and look for the pattern - the cause is usually a state transition or a specific input value rather than a continuous effect.

Tooling and ecosystem

The tooling around this bug class matters as much as the fix itself. Good logging, accessible profilers, and clear error messages turn 30-minute investigations into 5-minute ones. If your project doesn't have visibility into this code path, the first fix should add the visibility - the second fix uses it.

Within Unity, the relevant diagnostic surfaces include the standard frame debugger, memory profiler, and engine-specific debug overlays. Each one shows a different facet of what's happening. The frame debugger reveals draw call ordering and state transitions; the memory profiler shows allocation patterns; the debug overlay reveals per-system state. Bugs that resist one tool usually surrender to another - the trick is knowing which tool to reach for first.

Edge cases and pitfalls

Edge cases for this class of issue often involve specific timing: the first frame after a state change, the last frame before a transition, frames where multiple subsystems update simultaneously. Reproducing these reliably is part of what makes the bug class hard to test.

When writing a regression test for this fix, focus on the boundary conditions that surfaced the original bug. Tests that exercise the happy path catch obvious regressions; tests that exercise the boundary catch the subtler regressions that look like new bugs but are really the original returning. The latter are the tests that earn their keep over the long life of the project.

Team communication

When this bug class affects multiple teams (often the case for cross-system issues), early communication prevents duplicate work. The team that owns the symptom may not own the cause. A 15-minute conversation at the start of triage often saves hours of independent investigation.

If this fix touches a system several engineers work in, a short writeup in the team's engineering channel helps. Not a full design doc - a paragraph explaining what was wrong, what's fixed, and what to watch for. Future engineers encountering similar symptoms will search for the fix; making it findable is a small investment that pays back later.

“Mask for shapes, RectMask2D for rectangles. Show Mask Graphic when you want to see the frame.”

Related Issues

For canvas group fade issues, see CanvasGroup Alpha Children. For UI clicks blocked, see Raycaster Blocking Clicks.

Pick the right mask. Toggle Show Mask Graphic. Refresh TMP after layout. Children clip cleanly.