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:
- Disable the panel GameObject when input should be allowed through.
- Uncheck Raycast Target on the panel’s Image component when you want clicks to pass through.
- Use a
CanvasGroupon the panel withblocksRaycastsset tofalsewhen the panel should be passthrough.
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:
interactable = false— visually grays out all UI controls in the group (buttons appear disabled). The controls still receive raycasts; they just don’t respond to them. The CanvasGroup itself still blocks raycasts from passing through to whatever is underneath in the Canvas hierarchy.blocksRaycasts = false— makes the entire CanvasGroup transparent to pointer events. Raycasts pass through the group entirely, reaching elements behind it in the scene. The elements inside the group also become non-interactive.
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
- Button works in Editor but not in build. Occasionally a Screen Space — Camera canvas has its Event Camera field unassigned. In a build the camera reference may fail to resolve. Always assign the Event Camera explicitly and check it isn’t null at startup.
- ScrollRect swallowing button clicks. A
ScrollRectcomponent intercepts drag events and can also swallow clicks if the user moves the mouse even a single pixel while clicking. This is by design, but if your scroll view is small (the scroll threshold is met too easily), increase theEventSystem.current.pixelDragThresholdvalue or set it in Project Settings. - Clicks blocked in VR or alternative input systems. If you use XR Interaction Toolkit or a custom input module, the standard
GraphicRaycastermay not be the active raycaster. Check that the correct raycaster component (e.g.TrackedDeviceGraphicRaycaster) is on the Canvas.
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.