# TimePicker A styled time selector with separate dropdowns for hours and minutes (and optionally AM/PM). The selected time is always stored in a hidden input as `HH:MM` in 24-hour format, regardless of whether you show the 12-hour display mode. --- ## Quick example ```csharp new TimePicker(name: "startTime", label: "Start time") ``` --- ## All the options ```csharp public TimePicker( string name, string? selected = null, string label = "", string description = "", bool use12h = false) ``` | Parameter | What it does | |---|---| | `name` | Form field name. The hidden input gets this name and always holds a `HH:MM` value. The visible selects get `{name}-h`, `{name}-m`, `{name}-ampm`. | | `selected` | Pre-selected time as `"HH:MM"` in 24-hour format. Defaults to the current time. | | `label` | Visible text label above the picker. | | `description` | Small hint text below the picker. | | `use12h` | Show 12-hour mode with an AM/PM dropdown. The hidden input still stores 24h format. | --- ## Real-world examples ### Appointment booking with start and end times ```html
$$Token$$ $$StartTime$$ $$EndTime$$
``` ```csharp // ScheduleForm.htmx.cs _startTime = new TimePicker(name: "startTime", label: "Start", selected: "09:00"); _endTime = new TimePicker(name: "endTime", label: "End", selected: "17:00"); ``` Reading on the server: ```csharp public record Command( [property: FromForm] string StartTime, // "HH:MM" [property: FromForm] string EndTime ); var start = TimeOnly.ParseExact(command.StartTime, "HH:mm"); var end = TimeOnly.ParseExact(command.EndTime, "HH:mm"); ``` ### 12-hour display mode with a pre-selected time ```csharp new TimePicker( name: "alarmTime", selected: "07:30", label: "Alarm time", use12h: true) ``` The user sees `7:30 AM` in the dropdowns, but `07:30` is what gets submitted. --- ## How it works TimePicker renders three `` that holds the combined value. JavaScript in `components.js` listens for changes on any of the three selects and writes the correctly formatted `HH:MM` value to the hidden input, converting from 12h to 24h when needed. public record Command( [property: FromForm] string StartTime, // "09:00" [property: FromForm] string EndTime // "17:00" ); var start = TimeOnly.ParseExact(command.StartTime, "HH:mm"); var end = TimeOnly.ParseExact(command.EndTime, "HH:mm"); ``` --- ## Tips and tricks - The hidden input always stores 24h `HH:MM` regardless of `use12h` — parse with `"HH:mm"` format on the server. - `selected` defaults to the current server time if not specified — pass `"00:00"` if you want the picker to start at midnight. - The visible hour/minute selects are independent form fields (`{name}-h`, `{name}-m`) — only the hidden input with `name` is needed in your command record. Ignore the `-h`, `-m`, and `-ampm` fields server-side. - For a date+time combination, pair `Calendar` (for date) with `TimePicker` (for time) and combine their values in the handler. - For a date+time combination, pair `Calendar` (for date) with `TimePicker` (for time) and combine their values in the handler. --- ## Complete page example **`Templates/ScheduleMeetingPage.htmx`** ```html

Schedule a meeting

Pick a date and time that works for you.

$$AntiforgeryToken$$
$$DatePicker$$
$$TimePicker$$
$$TitleInput$$
$$SubmitBtn$$
$$SuccessAlert$$
``` **`Templates/ScheduleMeetingPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ScheduleMeetingPage : ScheduleMeetingPageBase { private readonly IHtmxComponent _calendar; private readonly IHtmxComponent _timePicker; private readonly IHtmxComponent _title; private readonly IHtmxComponent _submit; private readonly IHtmxComponent _success; private readonly byte[] _afToken; public ScheduleMeetingPage( IAntiforgery af, HttpContext ctx, DateOnly? selectedDate = null, string selectedTime = "", bool booked = false) { var tokens = af.GetAndStoreTokens(ctx); _afToken = $"""""".ToUtf8Bytes(); _calendar = new Components.Calendar(name: "date", selectedDate: selectedDate); _timePicker = new Components.TimePicker(name: "time", value: selectedTime, placeholder: "09:00"); _title = new Components.Input(id: "title", name: "title", label: "Meeting title", placeholder: "Sync call"); _submit = new Components.Button("Book meeting", type: "submit"); _success = booked ? new Components.Alert(title: "Meeting booked!", description: $"Scheduled for {selectedDate:d} at {selectedTime}.") : HtmxEmpty.Instance; } protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken); protected override void RenderDatePicker(HtmxRenderContext ctx) => _calendar.Render(ctx.Next()); protected override void RenderTimePicker(HtmxRenderContext ctx) => _timePicker.Render(ctx.Next()); protected override void RenderTitleInput(HtmxRenderContext ctx) => _title.Render(ctx.Next()); protected override void RenderSubmitBtn(HtmxRenderContext ctx) => _submit.Render(ctx.Next()); protected override void RenderSuccessAlert(HtmxRenderContext ctx) => _success.Render(ctx.Next()); } ``` **POST handler** ```csharp [Handler] [MapPost("/meetings/new")] public static partial class PostScheduleMeetingHandler { public record Command( [property: FromForm] DateOnly Date, [property: FromForm] string Time, [property: FromForm] string Title); private static Task HandleAsync( [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct) { // Persist meeting… return ctx.WriteHtmxPage( new ScheduleMeetingPage(af, ctx, cmd.Date, cmd.Time, booked: true), title: "Schedule meeting"); } } ``` **`AppJsonSerializerContext.cs`** ```csharp [JsonSerializable(typeof(PostScheduleMeetingHandler.Command), TypeInfoPropertyName = "ScheduleMeetingCommand")] ```