Skip to content
IzzyBlazor 0.1.0-alpha.20

Enter all results ↑ ↓ move Esc close

DateRangeField

Since 0.1.0-alpha.20
Two whole days, both included, in the browser's own date inputs.

Static server-side rendering: no circuit, no WebAssembly.

Two whole days, both included: the browser's own date inputs in one box, so each posts with the form, as {name}.Start and {name}.End, and takes a typed date, and the library's months to choose from. Bind it to a DateRange.

Usage

razor
<DateRangeField @bind-Value="Model.Period" />
<DateRangeField @bind-Value="Model.Period" Min="@today" MaxDays="90" />
<DateRangeField @bind-Value="Model.Period" DisplayMonths="1" />
<DateRangeField @bind-Value="Model.Period" Presets="DateRangePreset.Common(today)" />
<DateRangeField @bind-Value="Model.Period" UseNative />

Limits

Min, Max, MinDays and MaxDays hold on the server. DateDisabled turns away a range with a disabled day in it; AllowDisabledInRange keeps that to its ends.

Preview

Stay

Two nights to two weeks. No one arrives or leaves on a Sunday.

Code

Limits.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="date-range-limits" OnValidSubmit="Save" MaxWidth="24.Rem" Grow>
    <DateRangeField @bind-Value="Model!.Stay" Min="First" MinDays="2" MaxDays="14" DateDisabled="NoArrivals" AllowDisabledInRange
                    Hint="Two nights to two weeks. No one arrives or leaves on a Sunday." />
    <Row><Button Submit>Book the stay</Button></Row>
    @if (_saved is { } saved)
    {
        <Alert Status="Status.Success">Booked: @saved.Start to @saved.End, @saved.Days days.</Alert>
    }
</Form>

@code {
    private static readonly DateOnly First = new(2027, 3, 1);

    [SupplyParameterFromForm(FormName = "date-range-limits")]
    private Booking? Model { get; set; }

    // What was saved, not the field: on an interactive page it follows typing.
    private DateRange? _saved;

    protected override void OnInitialized() => Model ??= new();

    private static bool NoArrivals(DateOnly day) => day.DayOfWeek == DayOfWeek.Sunday;

    private void Save() => _saved = Model!.Stay;

    public sealed class Booking
    {
        [Required]
        public DateRange? Stay { get; set; } = new(new DateOnly(2027, 3, 8), new DateOnly(2027, 3, 12));
    }
}

Static: The server turns away a range outside the rules, whatever the browser let through.

Presets

Use Presets for ranges one click away, and DateRangePreset.Common for the usual ones, or list your own. They're absolute days from a today you pass in, so they follow your zone. One the field's rules would turn away is disabled.

Preview

Period

This example counts from March 15, 2027.

Code

Presets.razor
<DateRangeField @bind-Value="_period" Label="Period" Presets="DateRangePreset.Common(Today)" MaxWidth="24.Rem" Grow Hint="This example counts from March 15, 2027." />

@code {
    // Yours would come from the clock, in the zone the app means.
    private static readonly DateOnly Today = new(2027, 3, 15);

    private DateRange? _period;
}

Open ends, sizes, read-only and disabled

Either end can stay empty unless the field is Required. StartText and EndText change the captions.

Preview

Open ended

Either end can stay empty.

Small
Read-only
Disabled

Code

States.razor
<Stack Gap="4" MaxWidth="24.Rem" Grow>
    <DateRangeField @bind-Value="_open" Label="Open ended" StartText="From" EndText="Until" Clearable Hint="Either end can stay empty." />
    <DateRangeField @bind-Value="_period" Label="Small" Size="ControlSize.Sm" />
    <DateRangeField @bind-Value="_period" Label="Read-only" ReadOnly />
    <DateRangeField @bind-Value="_period" Label="Disabled" Disabled />
</Stack>

@code {
    private DateRange? _open;
    private DateRange? _period = new(new DateOnly(2027, 3, 8), new DateOnly(2027, 3, 12));
}

Details

The months write the inputs when the second day is chosen, so anything else that closes them changes nothing. A first day shades the days it can run to, and days past MinDays, MaxDays or a disabled day can't end it. UseNative keeps the browser's calendars. So do no script and a phone, unless the field has what a browser's calendar can't show: DateDisabled, MinDays, MaxDays or Presets. Presets the field's rules would turn away are disabled, and they're absolute days from a today the app passes in, so they follow its zone.

A DateRange holds days, so midnight never enters it. To query a time column: range.Contains(order.PlacedAt) a DateTime compares by its clock date var (from, until) = range.ToUtc(zone) from <= x && x < until, either may be null

Min, Max, MinDays, MaxDays and DateDisabled are checked on the server, along with the end not being before the start, so they hold against a phone, no script or a crafted post. DateDisabled turns away a span with a disabled day in it, unless AllowDisabledInRange. With Required, a field with only one end is turned away too. An empty field is null on an interactive page, and after a static post it can be a range with no ends: test IsEmpty rather than null.

Mode support

What works in each mode, DateRangeField
FeatureStaticInteractive
Two date inputs in one field, posting each end, with the end's minimum following the startYesYes
Months side by side to choose from, and presets beside themYesYes
Min, Max, MinDays, MaxDays and disabled days checked on the serverYesYes
Binds DateRange, whose days keep midnight out of it, with Contains and ToUtc for queriesYesYes

Static: static server-side rendering, with no circuit and no WebAssembly. Interactive: Server, WebAssembly or Auto.

Without JavaScript

Every page starts as plain HTML: before IzzyBlazor's script loads on a static page, while an interactive page prerenders, and with JavaScript off. With JavaScript off, the browser's own calendar opens on each date in place of the library's two months, and the end's minimum follows the start only after the form posts.

Accessibility

  • It renders a fieldset named by the label, with a label linked to each date input, and the hint and errors linked to the group.
  • Screen readers read each part of each date, and arrow keys change it.
  • The arrow between the inputs is hidden from screen readers.
  • The months' button is named "Choose dates" (ChooseLabel), or "Change dates" and both days once they're set. Its panel is a non-modal dialog, named by the button, with each month a grid named by its title. Both ends are selected in the grid. Focus goes to the start or today, and on closing back to where it opened from. A line under the months says how many days are chosen, and that a first day is waiting for its second.
  • Presets are a labelled group of buttons, and the one the field is set to is marked pressed.

Keyboard navigation

In a date

DateRangeField keys, In a date
Press this keyTo do this
Up or DownChange the focused part of the date
Left or RightMove between the parts of the date
TabMove to the next part, then the other date
Alt+DownOpen the months

In the months' days

DateRangeField keys, In the months' days
Press this keyTo do this
Left or RightMove a day (reversed in right-to-left)
Up or DownMove a week
Home or EndGo to the start or end of the week
Page Up or Page DownMove a month, keeping the day number or the month's last day
Shift+Page Up or Shift+Page DownMove a year
Enter or SpaceChoose the first day, or the last, which saves both and closes

In the months

DateRangeField keys, In the months
Press this keyTo do this
Tab or Shift+TabMove through the presets, the arrows and the days, and close past either end
EscapeClose without changing the value

Parameters

DateRangeField parameters
PropTypeDefault
ReadOnlyMakes the field read-only: it can be focused, and it still posts.boolfalse
MinThe earliest day either end can be.DateOnly?
MaxThe latest day either end can be.DateOnly?
MinDaysThe fewest days a range can span, both ends counted.int?
MaxDaysThe most days a range can span, both ends counted.int?
AllowDisabledInRangeLets a range span days that DateDisabled turns off, as a stay can span a day with no arrivals.boolfalse
UseNativeKeeps the browser's calendars in place of the library's months.boolfalse
DisplayMonthsHow many months show side by side, from 1 to 6.int2
ClearableA button in the field that empties both ends, there while it holds something.boolfalse
PresetsRanges to choose in one click, beside the months, such as Common.IReadOnlyList<DateRangePreset>?
ChooseLabelThe name of the months' button, in place of "Choose dates" and "Change dates" with both days.string?
StartTextThe caption of the first date, in place of "Start".string?
EndTextThe caption of the second date, in place of "End".string?
FirstDayOfWeekThe first column.DayOfWeek?computed
CultureThe culture for the month and day names, the first day and the week numbers.CultureInfo?computed
ShowWeekNumbersAdds a first column with the week of the year, counted as the culture counts them.boolfalse
ShowAdjacentDaysWhether to show the days of the months before and after, muted, in the first and last weeks.booltrue
DateDisabledWhether a day can't be chosen.Func<DateOnly, bool>?
DateDisabledWindowHow far each way from the value or today to call DateDisabled and DayClass, in whole days, for an end the limits don't set.TimeSpan?
DayClassA class for a day's button, or null.Func<DateOnly, string?>?
@bind-ValueThe value, bound with @bind-Value.DateRange?
DisplayNameThe field's name in validation messages from the input itself, such as a value that can't be parsed.string?

See also

  • DateField: a date to type or choose from a calendar, or a date and time.
  • Calendar: a month on the page, with the browser's own date input posting the day.
  • Form: posts on static pages, validates live on interactive ones.

Connection lost

Rejoining the server…