diff --git a/src/Components/Web/src/Forms/Label.cs b/src/Components/Web/src/Forms/Label.cs
new file mode 100644
index 000000000000..010284b7ae6d
--- /dev/null
+++ b/src/Components/Web/src/Forms/Label.cs
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq.Expressions;
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace Microsoft.AspNetCore.Components.Forms;
+
+///
+/// Renders a <label> element for a specified field, reading the display name from
+/// or
+/// if present, or falling back to the property name.
+/// The label wraps its child content (typically an input component), providing implicit association
+/// without requiring matching for/id attributes.
+///
+/// The type of the field.
+public class Label : IComponent
+{
+ private RenderHandle _renderHandle;
+ private Expression>? _previousFieldAccessor;
+ private string? _displayName;
+
+ ///
+ /// Specifies the field for which the label should be rendered.
+ ///
+ [Parameter, EditorRequired]
+ public Expression>? For { get; set; }
+
+ ///
+ /// Gets or sets the child content to be rendered inside the label element.
+ /// Typically this contains an input component that will be implicitly associated with the label.
+ ///
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Gets or sets a collection of additional attributes that will be applied to the label element.
+ ///
+ [Parameter(CaptureUnmatchedValues = true)]
+ public IReadOnlyDictionary? AdditionalAttributes { get; set; }
+
+ ///
+ void IComponent.Attach(RenderHandle renderHandle)
+ {
+ _renderHandle = renderHandle;
+ }
+
+ ///
+ Task IComponent.SetParametersAsync(ParameterView parameters)
+ {
+ var previousChildContent = ChildContent;
+ var previousAdditionalAttributes = AdditionalAttributes;
+
+ parameters.SetParameterProperties(this);
+
+ if (For is null)
+ {
+ throw new InvalidOperationException($"{GetType()} requires a value for the " +
+ $"{nameof(For)} parameter.");
+ }
+
+ // Only recalculate display name if the expression changed
+ if (For != _previousFieldAccessor)
+ {
+ var newDisplayName = ExpressionMemberAccessor.GetDisplayName(For);
+
+ if (newDisplayName != _displayName)
+ {
+ _displayName = newDisplayName;
+ _renderHandle.Render(BuildRenderTree);
+ }
+
+ _previousFieldAccessor = For;
+ }
+ else if (ChildContent != previousChildContent || AdditionalAttributes != previousAdditionalAttributes)
+ {
+ // Re-render if other parameters changed
+ _renderHandle.Render(BuildRenderTree);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private void BuildRenderTree(RenderTreeBuilder builder)
+ {
+ builder.OpenElement(0, "label");
+ builder.AddMultipleAttributes(1, AdditionalAttributes);
+ builder.AddContent(2, _displayName);
+ builder.AddContent(3, ChildContent);
+ builder.CloseElement();
+ }
+}
diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt
index 96e56989f2a9..33e9c4bcbda5 100644
--- a/src/Components/Web/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Web/src/PublicAPI.Unshipped.txt
@@ -74,3 +74,11 @@ Microsoft.AspNetCore.Components.Forms.DisplayName
Microsoft.AspNetCore.Components.Forms.DisplayName.DisplayName() -> void
Microsoft.AspNetCore.Components.Forms.DisplayName.For.get -> System.Linq.Expressions.Expression!>?
Microsoft.AspNetCore.Components.Forms.DisplayName.For.set -> void
+Microsoft.AspNetCore.Components.Forms.Label
+Microsoft.AspNetCore.Components.Forms.Label.AdditionalAttributes.get -> System.Collections.Generic.IReadOnlyDictionary?
+Microsoft.AspNetCore.Components.Forms.Label.AdditionalAttributes.set -> void
+Microsoft.AspNetCore.Components.Forms.Label.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment?
+Microsoft.AspNetCore.Components.Forms.Label.ChildContent.set -> void
+Microsoft.AspNetCore.Components.Forms.Label.For.get -> System.Linq.Expressions.Expression!>?
+Microsoft.AspNetCore.Components.Forms.Label.For.set -> void
+Microsoft.AspNetCore.Components.Forms.Label.Label() -> void
diff --git a/src/Components/Web/test/Forms/LabelTest.cs b/src/Components/Web/test/Forms/LabelTest.cs
new file mode 100644
index 000000000000..20bcf101c0ca
--- /dev/null
+++ b/src/Components/Web/test/Forms/LabelTest.cs
@@ -0,0 +1,353 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Components.Rendering;
+using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.AspNetCore.Components.Test.Helpers;
+
+namespace Microsoft.AspNetCore.Components.Forms;
+
+public class LabelTest
+{
+ [Fact]
+ public async Task RendersLabelElement()
+ {
+ var model = new TestModel();
+ var rootComponent = new TestHostComponent
+ {
+ InnerContent = builder =>
+ {
+ builder.OpenComponent