LilyDesignSystem.Blazor.DateTimePicker 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package LilyDesignSystem.Blazor.DateTimePicker --version 0.1.0
                    
NuGet\Install-Package LilyDesignSystem.Blazor.DateTimePicker -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LilyDesignSystem.Blazor.DateTimePicker" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LilyDesignSystem.Blazor.DateTimePicker" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="LilyDesignSystem.Blazor.DateTimePicker" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LilyDesignSystem.Blazor.DateTimePicker --version 0.1.0
                    
#r "nuget: LilyDesignSystem.Blazor.DateTimePicker, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LilyDesignSystem.Blazor.DateTimePicker@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LilyDesignSystem.Blazor.DateTimePicker&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=LilyDesignSystem.Blazor.DateTimePicker&version=0.1.0
                    
Install as a Cake Tool

DateTimePicker (Blazor helper)

A headless Blazor 10 date/time-picking form control: a text field plus an icon button (📅) that opens a WAI-ARIA APG Date Picker Dialog — a month grid with a full keyboard contract. Collects a date, a time, or both, and is locale-correct by construction.

Ships no CSS. The single source of truth is spec/index.md. This file is the human-readable guide.

Install

Add a project reference to LilyDesignSystem.Blazor.DateTimePicker.csproj, or the published LilyDesignSystem.Blazor.DateTimePicker NuGet package.

<ProjectReference Include="path/to/LilyDesignSystem.Blazor.DateTimePicker.csproj" />

Quick start

@using LilyDesignSystem.Blazor.Helpers

<DateTimePicker Label="Appointment date"
                Labels="@Labels"
                @bind-Value="_appointmentDate" />

@code {
    private string _appointmentDate = "";

    private static readonly DateTimePickerLabels Labels = new()
    {
        PreviousYear = "Previous year",
        PreviousMonth = "Previous month",
        NextMonth = "Next month",
        NextYear = "Next year",
        Confirm = "OK",
        Cancel = "Cancel",
    };
}

Value is ISO: YYYY-MM-DD for the default Mode="Date", HH:MM for Mode="Time", YYYY-MM-DDTHH:MM for Mode="DateTime". Sortable as a string, unambiguous across locales, and identical to what <input type="date"> posts.

Locale is derived, not chosen

Month names, weekday names, first day of week, numeric field order, and the 12/24-hour clock all come from Locale (a BCP 47 tag) via .NET's CultureInfo / DateTimeFormatInfo — never a hardcoded English table. Leave Locale unset and the runtime default culture drives everything; set it and the whole dialog follows, including which day the week starts on and whether 03/04/2026 means 3 April or 4 March.

<DateTimePicker Label="Dyddiad" Labels="@WelshLabels" Locale="cy-GB" @bind-Value="_value" />

Override any single derived default without overriding the rest: FirstDayOfWeek (0=Sunday…6=Saturday), Hour12 (true forces a 12-hour clock regardless of locale).

Constraining selection

<DateTimePicker Label="Appointment date"
                Labels="@Labels"
                Min="@DateTimePicker.FormatIsoDate(new CivilDate(2026, 1, 1))"
                Max="@_maxBookableDate"
                IsDateDisabled="@(iso => DateTimePicker.WeekdayOf(iso) is 0 or 6)"
                @bind-Value="_value" />

Min/Max are inclusive. IsDateDisabled vetoes individual dates — here, a weekends-closed clinic. A vetoed day renders aria-disabled="true" plus data-disabled — not the disabled attribute — so it stays focusable and a screen reader announces it as unavailable rather than going silent; activation is simply refused. Style vetoed days with [data-disabled] or [aria-disabled="true"], not :disabled. The keyboard cursor can still cross a vetoed day inside the range (so arrowing across a blocked week works), but it cannot leave the Min/Max window: there is nothing out there to navigate to.

The full civil-date arithmetic (AddDays, AddMonths, WeekdayOf, ParseIsoDate, …) is public static on DateTimePicker, exported for exactly this reason: wiring Min/Max/IsDateDisabled is date maths too, and the alternative is reaching for a zoned DateTime and reintroducing the local-midnight bug civil dates exist to avoid.

Shortcuts

<DateTimePicker Label="Follow-up date" Labels="@Labels"
                Shortcuts="@Shortcuts" @bind-Value="_value" />

@code {
    private static readonly DateTimeShortcut[] Shortcuts =
    {
        new() { Id = "today", Label = "Today", Days = 0 },
        new() { Id = "two-weeks", Label = "+2 weeks", Days = 14 },
        new() { Id = "one-month", Label = "+1 month", Months = 1 },
    };
}

A shortcut that resolves to a blocked date (outside Min/Max, or vetoed by IsDateDisabled) does nothing rather than landing near it — a "+4 weeks" that quietly means "+27 days" is a booking error.

Time and datetime

<DateTimePicker Label="Appointment time" Labels="@TimeLabels"
                Mode="DateTimeMode.Time" MinuteStep="15" Hour12="true"
                @bind-Value="_time" />

Mode="Time" needs Labels.Hour and Labels.Minute; a 12-hour clock (by locale default or Hour12="true") additionally needs Labels.Meridiem. Mode="DateTime" renders both the grid and the time selects and refuses to commit a date with no time, or the reverse — half a timestamp is a different truth, not a smaller one.

Typed input

The text field accepts typed dates as well as picker selection. Resolution is tried on blur or Enter, in order: a ParseInput you supply (if any), ISO YYYY-MM-DD, a numeric form whose field order follows the locale, or a form with a written month matched against the locale's own month names (three-character prefix match, so "Sept" finds September, case- and diacritic-insensitively). Two-digit years pivot at 70.

Text that will not parse, or that parses to a blocked date, stays exactly as typed and sets aria-invalid="true", firing OnInvalidInput — it is never silently snapped to a nearby legal date the user did not type. Escape in the field discards a pending typed edit, restoring the committed display and clearing the invalid state without committing anything; when nothing is pending the key is left alone.

Supply Labels.Invalid to have the refusal announced as well as marked: a role="status" live region (class hook date-time-picker-status, present-but-empty while the field is valid) fills with your message and is wired to the field via aria-errormessage plus an appended aria-describedby. Without it, aria-invalid flips silently and a screen-reader user who has already left the field never learns their date was refused.

<DateTimePicker Label="Date of birth" Labels="@Labels"
                OnInvalidInput="@(text => _dobError = $"Could not read \"{text}\" as a date")"
                @bind-Value="_dob" />

Clear button

<DateTimePicker Label="Appointment date" Labels="@ClearableLabels" @bind-Value="_value" />

@code {
    private static readonly DateTimePickerLabels ClearableLabels = new()
    {
        PreviousYear = "Previous year", PreviousMonth = "Previous month",
        NextMonth = "Next month", NextYear = "Next year",
        Confirm = "OK", Cancel = "Cancel",
        Clear = "Clear date",
    };
}

The clear button renders only when Labels.Clear is supplied — there is no default, because a default would be a hardcoded English string.

Dialog keyboard help

Supply Labels.Instructions to render keyboard help inside the dialog (class hook date-time-picker-instructions, the dialog's first child) and have the dialog reference it via aria-describedby, so a screen reader speaks it once on open — the APG date-picker example ships exactly this affordance. It is visible by default; hide it with your own CSS if you want it screen-reader-only. Like every other label, it renders nothing when not supplied.

Custom glyph

ChildContent replaces the 📅 glyph inside the trigger button and receives a DateTimePickerContext of { Value, Open, Display }:

<DateTimePicker Label="Appointment date" Labels="@Labels" @bind-Value="_value">
    <span class="my-icon" aria-hidden="true">@(context.Open ? "▲" : "📅")</span>
</DateTimePicker>

Parameters

Full table in spec/index.md §4.1. Required: Label, Labels.

Static helpers

Member Purpose
DateTimePicker.Calendar The default glyph, "\U0001F4C5\uFE0E" (U+1F4C5 + U+FE0E).
DateTimePicker.NextDateTimePickerId() Mint a stable, prerender-safe id prefix.
DateTimePicker.ParseIsoDate / FormatIsoDate ISO date parsing/formatting.
DateTimePicker.AddDays / AddMonths Civil-date arithmetic (AddMonths clamps the day).
DateTimePicker.WeekdayOf / IsoWeek Day-of-week and ISO-8601 week number.
DateTimePicker.ParseDateInput / ParseTimeInput The typed-input parsers, directly callable.
DateTimePicker.FirstDayOfWeekFor(locale) The locale's first weekday.

Full list in spec/index.md §4.4.

Accessibility

  • Follows the WAI-ARIA APG Date Picker Dialog pattern: role="dialog", aria-modal="true", role="grid", roving tabindex, full keyboard contract, a real focus trap.
  • Closing the dialog returns focus to whichever element opened it — the trigger button after a click, the text field after Alt+ArrowDown — per the APG dialog pattern. Click-outside (which includes the component's own text field, honouring aria-modal) closes without moving focus.
  • Vetoed days are aria-disabled, never disabled, so the roving cursor can land on them with real focus and a screen reader announces them as unavailable instead of going silent.
  • The glyph is aria-hidden; the trigger's and dialog's accessible name both come from Label.
  • Tradeoff: a hand-rolled grid has weaker assistive-technology support than <input type="date">, which is the right default for many services. See docs/accessibility.md.

Styling

Class hooks: .date-time-picker (root), .date-time-picker-field, .date-time-picker-input, .date-time-picker-button, .date-time-picker-icon, .date-time-picker-status (only with Labels.Invalid), .date-time-picker-dialog, .date-time-picker-instructions (only with Labels.Instructions), .date-time-picker-header, .date-time-picker-previous-year / -previous-month / -next-month / -next-year, .date-time-picker-period, .date-time-picker-calendar, .date-time-picker-week-heading, .date-time-picker-weekday, .date-time-picker-week, .date-time-picker-day, .date-time-picker-time, .date-time-picker-time-label, .date-time-picker-hour / -minute / -meridiem, .date-time-picker-shortcuts, .date-time-picker-shortcut, .date-time-picker-footer, .date-time-picker-clear / -cancel / -confirm.

The package ships no CSS and no positioning for the dialog — without positioning CSS from the consumer, it renders in normal document flow rather than as an overlay.

Tests

From ../tests/LilyDesignSystem.Blazor.Helpers.Tests:

dotnet test

65 cases for this package, one or more per §7 clause.


Lilyâ„¢ and Lily Design Systemâ„¢ are trademarks.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on LilyDesignSystem.Blazor.DateTimePicker:

Package Downloads
LilyDesignSystem.Blazor.GanttChart

Lily Design System Blazor Gantt chart: a keyboard-accessible Gantt chart composing the headless GanttTable family plus the sibling DateTimePicker helper (twice per edit session) for start/end date editing. Row hierarchy, milestones, percent-complete, today marker, finish-to-start dependency data. Headless, SSR-safe, no CSS.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 44 10/5/2026
0.2.1 56 10/1/2026
0.2.0 99 9/16/2026
0.1.0 104 9/2/2026