diff --git a/src/Components/Web/src/Forms/InputTime.cs b/src/Components/Web/src/Forms/InputTime.cs
new file mode 100644
index 000000000000..9db0daf039db
--- /dev/null
+++ b/src/Components/Web/src/Forms/InputTime.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace Microsoft.AspNetCore.Components.Forms
+{
+ ///
+ /// An input component for editing time values.
+ /// Supported types are and .
+ ///
+ public class InputTime : InputBase
+ {
+ private const string TimeFormat = "HH:mm:ss"; // Compatible with HTML time inputs
+
+ ///
+ /// Gets or sets the error message used when displaying an a parsing error.
+ ///
+ [Parameter] public string ParsingErrorMessage { get; set; } = "The {0} field must be a time.";
+
+ ///
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
+ {
+ builder.OpenElement(0, "input");
+ builder.AddMultipleAttributes(1, AdditionalAttributes);
+ builder.AddAttribute(2, "type", "time");
+ builder.AddAttribute(3, "class", CssClass);
+ builder.AddAttribute(4, "value", BindConverter.FormatValue(CurrentValueAsString));
+ builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
+ builder.CloseElement();
+ }
+
+ ///
+ protected override string FormatValueAsString(TValue? value)
+ {
+ switch (value)
+ {
+ case DateTime dateTimeValue:
+ return BindConverter.FormatValue(dateTimeValue, TimeFormat, CultureInfo.InvariantCulture);
+ case DateTimeOffset dateTimeOffsetValue:
+ return BindConverter.FormatValue(dateTimeOffsetValue, TimeFormat, CultureInfo.InvariantCulture);
+ default:
+ return string.Empty; // Handles null for Nullable, etc.
+ }
+ }
+
+ ///
+ protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ // Unwrap nullable types. We don't have to deal with receiving empty values for nullable
+ // types here, because the underlying InputBase already covers that.
+ var targetType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
+
+ bool success;
+ if (targetType == typeof(DateTime))
+ {
+ success = TryParseDateTime(value, out result);
+ }
+ else if (targetType == typeof(DateTimeOffset))
+ {
+ success = TryParseDateTimeOffset(value, out result);
+ }
+ else
+ {
+ throw new InvalidOperationException($"The type '{targetType}' is not a supported time type.");
+ }
+
+ if (success)
+ {
+ Debug.Assert(result != null);
+ validationErrorMessage = null;
+ return true;
+ }
+ else
+ {
+ validationErrorMessage = string.Format(ParsingErrorMessage, DisplayName ?? FieldIdentifier.FieldName);
+ return false;
+ }
+ }
+
+ private static bool TryParseDateTime(string? value, [MaybeNullWhen(false)] out TValue result)
+ {
+ var success = BindConverter.TryConvertToDateTime(value, CultureInfo.InvariantCulture, TimeFormat, out var parsedValue);
+ if (success)
+ {
+ result = (TValue)(object)parsedValue;
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ private static bool TryParseDateTimeOffset(string? value, [MaybeNullWhen(false)] out TValue result)
+ {
+ var success = BindConverter.TryConvertToDateTimeOffset(value, CultureInfo.InvariantCulture, TimeFormat, out var parsedValue);
+ if (success)
+ {
+ result = (TValue)(object)parsedValue;
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file