Quick answer: An Image or Panel component with Raycast Target enabled is sitting in front of your button and consuming the click. Disable Raycast Target on every UI element that doesn’t need to receive input, or use a CanvasGroup with blocksRaycasts = false to make an entire panel passthrough.

You wire up a button’s OnClick event, hit Play, click the button — and nothing happens. No hover highlight, no click callback, no error. The button is active, the EventSystem is in the scene, the Canvas is set up correctly. But some invisible element is eating every pointer event before they reach the button. This is one of the most frustrating UI bugs in Unity because the cause is completely invisible.

The Symptom

Clicking a UI button produces no response — no visual feedback, no callback firing. The button may or may not show a hover state. In some cases the button works when you click the very edge of it but not the center. In others, an entire region of the screen is dead to input.

The common thread is that the Unity EventSystem sends pointer events to the topmost object under the cursor in raycast order. If any element with Raycast Target enabled is rendered above the button and covers its area, that element receives the event first — and if it has no handler, the event is consumed silently rather than propagating down.

What Causes This

1. Background Image with Raycast Target enabled

The most common cause is a background Image component — often set to fully transparent (alpha 0) or using a solid color that happens to cover the button — that has Raycast Target checked. Unity’s GraphicRaycaster hits this image first and reports it as the target of the click. The button behind it never gets the event.

The fix is straightforward: open the offending Image component in the Inspector and uncheck Raycast Target. Only UI elements that actually need to receive input (buttons, toggles, sliders, input fields, scroll views) should have Raycast Target enabled. Every decorative or structural element — background panels, icons, labels, borders — should have it disabled.

2. Text components intercepting clicks

Both the legacy Text component and TextMeshProUGUI have Raycast Target enabled by default. A label overlapping a button (for example, a tooltip or counter text that extends beyond the button’s rect) can intercept clicks on that region. Disable Raycast Target on all text components that are not themselves interactive.

3. Full-screen transparent panel

A common UI pattern is a full-screen transparent panel used for click-to-dismiss modals, backdrop darkening, or input blocking during loading. If this panel is active and covers the area where interactive buttons live, it blocks all input to those buttons. You have three options:

using UnityEngine;

public class ModalBackdrop : MonoBehaviour
{
    private CanvasGroup canvasGroup;

    private void Awake()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }

    public void ShowBackdrop(float alpha = 0.5f)
    {
        canvasGroup.alpha = alpha;
        canvasGroup.blocksRaycasts = true;   // block input to buttons behind
        canvasGroup.interactable   = false;  // disable any controls on the panel
    }

    public void HideBackdrop()
    {
        canvasGroup.alpha = 0f;
        canvasGroup.blocksRaycasts = false;  // allow clicks to pass through
        canvasGroup.interactable   = false;
    }
}

4. CanvasGroup.interactable vs. CanvasGroup.blocksRaycasts

These two properties are frequently confused:

If you want a panel to be invisible and completely non-interactive (click-through), set both alpha = 0 and blocksRaycasts = false. Setting only alpha = 0 still blocks raycasts.

5. Raycast Padding on Image

Unity 2020.1 added a Raycast Padding property on Image components. This expands or shrinks the hit area of the image beyond its visible rect. A negative value shrinks the hit area (so clicks near the edge miss the button), and a positive value expands it (so a small invisible image can intercept clicks outside its visible bounds). If a component is eating clicks in a suspiciously rectangular area that doesn’t match its visible size, check Raycast Padding.

using UnityEngine;
using UnityEngine.UI;

public class RaycastPaddingFix : MonoBehaviour
{
    private void Start()
    {
        var image = GetComponent<Image>();
        if (image == null) return;

        // Log current padding to diagnose unexpected hit areas
        Debug.Log($"[{name}] Raycast Padding: {image.raycastPadding}");

        // Reset to zero if padding is expanding the hit area unintentionally
        image.raycastPadding = Vector4.zero;
    }
}

The Fix

Use the UI Event Debugger

The fastest way to identify which element is consuming a click is the UI Event Debugger built into the Unity EventSystem. Open it from Window › Analysis › Event System Debugger (Unity 2021.2+). While in Play mode, hover over the problem area and the debugger shows the complete raycast result list in order, with the topmost hit first. The element at position [0] is the one consuming your click.

For older Unity versions, add a script that logs all raycasts to the console:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class RaycastDebugger : MonoBehaviour
{
    private GraphicRaycaster raycaster;
    private PointerEventData pointerData;
    private EventSystem eventSystem;

    private void Awake()
    {
        raycaster  = GetComponentInParent<GraphicRaycaster>();
        eventSystem = EventSystem.current;
    }

    private void Update()
    {
        if (!Input.GetMouseButtonDown(0)) return;

        pointerData = new PointerEventData(eventSystem)
        {
            position = Input.mousePosition
        };

        var results = new List<RaycastResult>();
        raycaster.Raycast(pointerData, results);

        foreach (var result in results)
            Debug.Log($"Hit: {result.gameObject.name} "
                + $"depth={result.depth} sortOrder={result.sortingOrder}");
    }
}

Disable Raycast Target across the hierarchy

Rather than tracking down each offending component manually, use an Editor utility to audit the entire Canvas hierarchy:

using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

public static class RaycastTargetAuditor
{
    [MenuItem("Tools/UI/Disable Raycast Target on Non-Interactive Elements")]
    private static void DisableNonInteractiveRaycastTargets()
    {
        var images = Object.FindObjectsByType<Image>(FindObjectsSortMode.None);
        int changed = 0;

        foreach (var img in images)
        {
            // Skip images on GameObjects that have interactive components
            bool hasInteractable =
                img.GetComponent<Button>()     != null
                || img.GetComponent<Toggle>()  != null
                || img.GetComponent<Slider>()  != null
                || img.GetComponent<Dropdown>() != null
                || img.GetComponent<InputField>() != null
                || img.GetComponent<ScrollRect>() != null;

            if (!hasInteractable && img.raycastTarget)
            {
                Undo.RecordObject(img, "Disable Raycast Target");
                img.raycastTarget = false;
                changed++;
            }
        }

        Debug.Log($"Disabled Raycast Target on {changed} Image components.");
    }
}

Related Issues

Make it a habit to uncheck Raycast Target on every Image and Text you add to a Canvas — only turn it back on when you actually need click detection. It’s much easier than hunting down invisible blockers later.