Repository: MudBlazor/MudBlazor (2026-04-17T16:23:01Z)

Issue:
MudFileUpload @ondragleave does not fire when you dragleave
### Before reporting

- [x] I searched existing issues
- [x] I tried in private/incognito browser

### What went wrong?

When drag-n-dropping files on a MudFileUpload with custom content, only the ondragenter event fires. Ondragleave does not. Dragging a file over the dropzone fires ondragenter. Dragging the file away from the drop zone again does not fire ondragleave

### Reproduction link

https://try.mudblazor.com/snippet/cumKOIvUTbEJffRp

### Reproduction steps

1. Open file upload sample in the MudBlazor docs, at the Create a Custom Solution section. I also created a snippet for it which is a copy-paste of the example.
2. Drag a file over the dropzone and note the formatting change. 
3. Continue dragging the file out of the dropzone again and note the formatting does not switch back.
4. Compare this behaviour to the Using Built-In Controls section, where the formatting returns to normal once the dragged file leaves the drop zone.


### Version (bug)

v9.3.0

### Version (working)

_No response_

### Environment

Edge

### Blazor rendering mode

Server


Discussion:

Patch:
diff --git a/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropCustomScenarioExample.razor b/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropCustomScenarioExample.razor
index 0b6a6d6b612b..e19200f4c138 100644
--- a/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropCustomScenarioExample.razor
+++ b/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropCustomScenarioExample.razor
@@ -21,10 +21,10 @@
                    Hidden="@false"
                    InputClass="file-upload-input"
                    tabindex="-1"
-                   @ondrop="@ClearDragClass"
-                   @ondragenter="@SetDragClass"
-                   @ondragleave="@ClearDragClass"
-                   @ondragend="@ClearDragClass">
+                   OnDrop="@(_ => ClearDragClass())"
+                   OnDragEnter="@(_ => SetDragClass())"
+                   OnDragLeave="@(_ => ClearDragClass())"
+                   OnDragEnd="@(_ => ClearDragClass())">
         <CustomContent>
             <MudPaper Height="300px"
                       Outlined="true"
diff --git a/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropWithFormValidationExample.razor b/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropWithFormValidationExample.razor
index f2218e958982..9ad3f04493af 100644
--- a/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropWithFormValidationExample.razor
+++ b/src/MudBlazor.Docs/Pages/Components/FileUpload/Examples/FileUploadDragAndDropWithFormValidationExample.razor
@@ -30,10 +30,10 @@
                            InputClass="file-upload-input"
                            ErrorText="@string.Empty"
                            tabindex="-1"
-                           @ondrop="@ClearDragClass"
-                           @ondragenter="@SetDragClass"
-                           @ondragleave="@ClearDragClass"
-                           @ondragend="@ClearDragClass">
+                           OnDrop="@(_ => ClearDragClass())"
+                           OnDragEnter="@(_ => SetDragClass())"
+                           OnDragLeave="@(_ => ClearDragClass())"
+                           OnDragEnd="@(_ => ClearDragClass())">
                 <CustomContent>
                     <MudPaper Height="300px"
                               Outlined="true"
diff --git a/src/MudBlazor.Docs/Pages/Components/FileUpload/FileUploadPage.razor b/src/MudBlazor.Docs/Pages/Components/FileUpload/FileUploadPage.razor
index ec3350d93464..9931aa66e34b 100644
--- a/src/MudBlazor.Docs/Pages/Components/FileUpload/FileUploadPage.razor
+++ b/src/MudBlazor.Docs/Pages/Components/FileUpload/FileUploadPage.razor
@@ -131,7 +131,7 @@
                     <SectionHeader Title="Using Built-In Controls">
                         <Description>
                             The component provides properties to manage drag-and-drop behavior and drop-zone visibility. You can customize the drop zone inside
-                            <CodeInline>CustomContent</CodeInline> and use an <CodeInline>@@ondragenter</CodeInline> handler to set <CodeInline>Dragging</CodeInline>. When the drag ends,
+                            <CodeInline>CustomContent</CodeInline> and use an <CodeInline>OnDragEnter</CodeInline> callback to set <CodeInline>Dragging</CodeInline>. When the drag ends,
                             the control resets <CodeInline>Dragging</CodeInline> to <CodeInline>false</CodeInline>.
                         </Description>
                     </SectionHeader>
diff --git a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor
index 13586814cf72..40ce1ad7810e 100644
--- a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor
+++ b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor
@@ -20,9 +20,10 @@
                        accept="@Accept"
                        disabled="@(GetDisabledState())"
                        @attributes="UserAttributes"
-                       @ondrop="OnDragResetAsync"
-                       @ondragleave="OnDragResetAsync"
-                       @ondragend="OnDragResetAsync"
+                       @ondragenter="OnDragEnterAsync"
+                       @ondrop="OnDropAsync"
+                       @ondragleave="OnDragLeaveAsync"
+                       @ondragend="OnDragEndAsync"
                        id="@GetInputId(localIndex)"
                        required="@Required"
                        aria-required="@Required.ToString().ToLowerInvariant()" />
diff --git a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs
index 3fdbbcfb755d..0c0960f2df94 100644
--- a/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs
+++ b/src/MudBlazor/Components/FileUpload/MudFileUpload.razor.cs
@@ -97,6 +97,34 @@ public MudFileUpload()
         [Category(CategoryTypes.FileUpload.Behavior)]
         public EventCallback<InputFileChangeEventArgs> OnFilesChanged { get; set; }
 
+        /// <summary>
+        /// Occurs when a drag operation enters this component.
+        /// </summary>
+        [Parameter]
+        [Category(CategoryTypes.FileUpload.Behavior)]
+        public EventCallback<DragEventArgs> OnDragEnter { get; set; }
+
+        /// <summary>
+        /// Occurs when files are dropped onto this component.
+        /// </summary>
+        [Parameter]
+        [Category(CategoryTypes.FileUpload.Behavior)]
+        public EventCallback<DragEventArgs> OnDrop { get; set; }
+
+        /// <summary>
+        /// Occurs when a drag operation leaves this component.
+        /// </summary>
+        [Parameter]
+        [Category(CategoryTypes.FileUpload.Behavior)]
+        public EventCallback<DragEventArgs> OnDragLeave { get; set; }
+
+        /// <summary>
+        /// Occurs when a drag operation ends for this component.
+        /// </summary>
+        [Parameter]
+        [Category(CategoryTypes.FileUpload.Behavior)]
+        public EventCallback<DragEventArgs> OnDragEnd { get; set; }
+
         /// <summary>
         /// Appends new files to the existing selection.
         /// </summary>
@@ -262,6 +290,24 @@ private async Task OnDragResetAsync()
             await _draggingState.SetValueAsync(false);
         }
 
+        private async Task OnDropAsync(DragEventArgs args)
+        {
+            await OnDragResetAsync();
+            await OnDrop.InvokeAsync(args);
+        }
+
+        private async Task OnDragLeaveAsync(DragEventArgs args)
+        {
+            await OnDragResetAsync();
+            await OnDragLeave.InvokeAsync(args);
+        }
+
+        private async Task OnDragEndAsync(DragEventArgs args)
+        {
+            await OnDragResetAsync();
+            await OnDragEnd.InvokeAsync(args);
+        }
+
         private Task OnDragAreaClickAsync()
         {
             if (GetDisabledState())
@@ -272,14 +318,15 @@ private Task OnDragAreaClickAsync()
             return OpenFilePickerAsync();
         }
 
-        private Task OnDragEnterAsync()
+        private async Task OnDragEnterAsync(DragEventArgs args)
         {
             if (GetDisabledState())
             {
-                return Task.CompletedTask;
+                return;
             }
 
-            return _draggingState.SetValueAsync(true);
+            await _draggingState.SetValueAsync(true);
+            await OnDragEnter.InvokeAsync(args);
         }
 
         private Task OnFileChipCloseAsync(string filename)


Test patch:
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadWithDragAndDropActivatorTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadWithDragAndDropActivatorTest.razor
index 6e12b192c88f..73a3f04c30e6 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadWithDragAndDropActivatorTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/FileUpload/FileUploadWithDragAndDropActivatorTest.razor
@@ -18,10 +18,10 @@
                Accept=".png,.jpg,.jpeg"
                Hidden="@false"
                tabindex="-1"
-               @ondrop="@ClearDragClass"
-               @ondragenter="@SetDragClass"
-               @ondragleave="@ClearDragClass"
-               @ondragend="@ClearDragClass">
+               OnDrop="@(_ => ClearDragClass())"
+               OnDragEnter="@(_ => SetDragClass())"
+               OnDragLeave="@(_ => ClearDragClass())"
+               OnDragEnd="@(_ => ClearDragClass())">
     <CustomContent>
         <MudPaper Height="300px"
                   Outlined="true"
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithFileUploadAndDragAndDropActivatorTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithFileUploadAndDragAndDropActivatorTest.razor
index c3d06e6a1439..858db573f3ca 100644
--- a/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithFileUploadAndDragAndDropActivatorTest.razor
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Form/FormWithFileUploadAndDragAndDropActivatorTest.razor
@@ -25,10 +25,10 @@
                            Hidden="@false"
                            tabindex="-1"
                            Required="@true"
-                           @ondrop="@ClearDragClass"
-                           @ondragenter="@SetDragClass"
-                           @ondragleave="@ClearDragClass"
-                           @ondragend="@ClearDragClass">
+                           OnDrop="@(_ => ClearDragClass())"
+                           OnDragEnter="@(_ => SetDragClass())"
+                           OnDragLeave="@(_ => ClearDragClass())"
+                           OnDragEnd="@(_ => ClearDragClass())">
                 <CustomContent>
                     <MudPaper Height="300px"
                               Outlined="true"
diff --git a/src/MudBlazor.UnitTests/Components/FileUploadTests.cs b/src/MudBlazor.UnitTests/Components/FileUploadTests.cs
index 0beb04ccc8ba..6f385332021a 100644
--- a/src/MudBlazor.UnitTests/Components/FileUploadTests.cs
+++ b/src/MudBlazor.UnitTests/Components/FileUploadTests.cs
@@ -5,7 +5,9 @@
 using System.Globalization;
 using AwesomeAssertions;
 using Bunit;
+using Microsoft.AspNetCore.Components;
 using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.AspNetCore.Components.Web;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using MudBlazor.Extensions;
@@ -446,6 +448,47 @@ public async Task FileUpload_DragAndDrop_Test()
             comp.Find(".mud-file-upload-dragarea").ClassList.Should().NotContain("mud-border-primary");
         }
 
+        /// <summary>
+        /// Ensures first-class drag callbacks are invoked together with internal drag state handling.
+        /// </summary>
+        [Test]
+        public async Task FileUpload_DragAndDrop_Should_Invoke_FirstClass_Drag_Callbacks()
+        {
+            var dragEnterCalls = 0;
+            var dragLeaveCalls = 0;
+            var dropCalls = 0;
+            var dragEndCalls = 0;
+
+            var comp = Context.Render<MudFileUpload<IBrowserFile>>(parameters => parameters
+                .Add(x => x.DragAndDrop, true)
+                .Add(x => x.OnDragEnter, EventCallback.Factory.Create<DragEventArgs>(this, (DragEventArgs _) => dragEnterCalls++))
+                .Add(x => x.OnDragLeave, EventCallback.Factory.Create<DragEventArgs>(this, (DragEventArgs _) => dragLeaveCalls++))
+                .Add(x => x.OnDrop, EventCallback.Factory.Create<DragEventArgs>(this, (DragEventArgs _) => dropCalls++))
+                .Add(x => x.OnDragEnd, EventCallback.Factory.Create<DragEventArgs>(this, (DragEventArgs _) => dragEndCalls++)));
+
+            await comp.Find(".mud-file-upload-dragarea").DragEnterAsync();
+            dragEnterCalls.Should().Be(1);
+            comp.Find(".mud-file-upload-dragarea").ClassList.Should().Contain("mud-border-primary");
+
+            await comp.Find("input").DragLeaveAsync();
+
+            dragLeaveCalls.Should().Be(1);
+            comp.Find(".mud-file-upload-dragarea").ClassList.Should().NotContain("mud-border-primary");
+
+            await comp.Find("input").DragEnterAsync();
+            dragEnterCalls.Should().Be(2);
+            comp.Find(".mud-file-upload-dragarea").ClassList.Should().Contain("mud-border-primary");
+
+            await comp.Find("input").DropAsync(new DragEventArgs());
+            dropCalls.Should().Be(1);
+            comp.Find(".mud-file-upload-dragarea").ClassList.Should().NotContain("mud-border-primary");
+
+            await comp.Find("input").DragEnterAsync();
+            await comp.Find("input").DragEndAsync();
+            dragEndCalls.Should().Be(1);
+            comp.Find(".mud-file-upload-dragarea").ClassList.Should().NotContain("mud-border-primary");
+        }
+
         /// <summary>
         /// Ensures the default drag-and-drop activator uses a semantic button.
         /// </summary>