Components
Calendar
A month of days to pick from: one day, several, or a range, for scheduling and booking. The step that opens it says which, and the type it binds follows.
Drawn and behaving as Flux UI's calendar does, measured against its live docs.
Calendar
using System.Globalization;
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarExample : Component
{
private DateOnly _date;
protected override Component? Render() =>
Div.Class("space-y-3")[
Ui.Calendar.Value(_date).OnChange(date => { _date = date; }).Class("w-fit"),
Ui.Text.Data("testid", "ui-calendar-state")[
_date == default ? "No date chosen." : "Chosen: " + _date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
]
];
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
No date chosen.
Basic usage
Bound to a DateOnly that starts on today, and written as a day is picked.
using System.Globalization;
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarBoundExample : Component
{
private readonly Appointment _appointment = new();
protected override Component? Render() =>
Div.Class("space-y-3")[
Ui.Calendar.Bind(() => _appointment.Date).Live().Class("w-fit"),
Ui.Text["Booked for " + _appointment.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + "."]
];
public sealed class Appointment
{
public DateOnly Date { get; set; } = DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date);
}
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Booked for 2026-10-22.
Multiple dates
Several days that need not be next to each other, held in a collection.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarMultipleExample : Component
{
private List<DateOnly> _dates = [];
protected override Component? Render() =>
Div.Class("space-y-3")[
Ui.Calendar.Multiple.Values(_dates).OnChange(dates => { _dates = [.. dates]; }).Class("w-fit"),
Ui.Text.Data("testid", "ui-calendar-days")[$"{_dates.Count} days off"]
];
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
0 days off
Date range
Two months and a UiDateRange. The first click is drawn as the start and written nowhere; the second writes the whole range.
using System.Globalization;
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarRangeExample : Component
{
private UiDateRange _range;
protected override Component? Render() =>
Div.Class("space-y-3")[
Ui.Calendar.Range.Value(_range).OnChange(range => { _range = range; }).Class("w-fit"),
Ui.Text.Data("testid", "ui-calendar-stay")[
_range == default ? "No range chosen." : $"Range: {Iso(_range.Start)} to {Iso(_range.End)}"
]
];
private static string Iso(DateOnly date) => date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
No range chosen.
Range configuration
A range of three to ten days: while it waits for its end, the days that would make it too short or too long are disabled.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarRangeLimitsExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Range.Value(default(UiDateRange)).MinRange(3).MaxRange(10).Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Size
Xs, Sm, Lg, Xl and Xxl, for the layout around it.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarSizeExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).Xl.Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Static
A calendar to look at, with no month steps.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarStaticExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date)).Static().Xs.Navigation(false).Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Min/max dates
Nothing after today. Flux's "today" shorthand is today's DateOnly.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarMaxExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).Max(DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date)).Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Unavailable dates
Yesterday and tomorrow struck through: holidays, booked days.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarUnavailableExample : Component
{
private static readonly DateOnly Today = DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date);
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).Unavailable([Today.AddDays(-1), Today.AddDays(1)]).Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
With today shortcut
A button that comes back to this month, and picks today once there.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarTodayExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).WithToday().Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Selectable header
The month and the year in the header are selects.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarHeaderExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).SelectableHeader().Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
With inputs
A field over the month to type the day into, and a range's start and end over two. A whole date typed is chosen at once; a pick is written into the field.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarInputsExample : Component
{
protected override Component? Render() =>
Div.Class("flex flex-wrap items-start gap-8")[
Ui.Calendar.Value(default(DateOnly)).WithInputs().Class("w-fit"),
Ui.Calendar.Range.Value(default(UiDateRange)).WithInputs().Class("w-fit")
];
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Fixed weeks
Always six weeks, so paging never changes the height.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarFixedWeeksExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).FixedWeeks().Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Start day
The week starts on the locale's own first day unless this says otherwise.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarStartDayExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).StartDay(DayOfWeek.Monday).Class("w-fit");
}Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
Open to
The month it opens on while nothing is chosen: next month, a year on.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarOpenToExample : Component
{
private static readonly DateOnly Today = DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date);
// The first of next month, a year on.
private static readonly DateOnly NextYear = new DateOnly(Today.Year, Today.Month, 1).AddMonths(1).AddYears(1);
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).OpenTo(NextYear).Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Force open to
ForceOpenTo opens on that month whatever is chosen: today is picked here, and a year on is shown.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarForceOpenToExample : Component
{
private static readonly DateOnly Today = DateOnly.FromDateTime(TimeProvider.System.GetLocalNow().Date);
private static readonly DateOnly NextYear = new DateOnly(Today.Year, Today.Month, 1).AddMonths(1).AddYears(1);
protected override Component? Render() =>
Ui.Calendar.Value(Today).OpenTo(NextYear).ForceOpenTo().Class("w-fit");
}Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Week numbers
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarWeekNumbersExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).WeekNumbers().Class("w-fit");
}# | Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|---|
| 40 | |||||||
| 41 | |||||||
| 42 | |||||||
| 43 | |||||||
| 44 |
Localization
The app's culture unless Locale names another: month and weekday names and the first day of the week.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class CalendarLocaleExample : Component
{
protected override Component? Render() =>
Ui.Calendar.Value(default(DateOnly)).Locale("ja-JP").Class("w-fit");
}日 | 月 | 火 | 水 | 木 | 金 | 土 |
|---|---|---|---|---|---|---|
Behaviour
The mode is the opening step
No step is Flux's default, one day: Ui.Calendar.Bind(() => m.Delivery) binds a DateOnly or a DateOnly?. Ui.Calendar.Multiple binds a collection of days and Ui.Calendar.Range a UiDateRange, so a range bound to a single day does not compile. A mode given as a value, Mode(Ui.CalendarMode.Range), has to agree with what is bound; when it does not, rendering throws and names both.
A second click on the chosen day clears it: default(DateOnly), or null through a nullable binding. Min and Max disable the days outside them, and a month step too once the whole month that way is out of reach.
A range is always whole
UiDateRange(Start, End) is written only once it has both ends. The first click is held by the calendar and drawn as the start, the stretch to the day under the pointer or the keyboard is drawn as it would be, and the second click, on that day or a later one, writes the range; a click before the start begins again from there. Count, Contains(day) and Between(a, b) read it, and default(UiDateRange) is nothing chosen. Flux's DateRange presets are UiDateRange.Of(preset, today, …), shown on the date picker's page.
The keyboard
One day is in the Tab order: the chosen one, else today, else the 1st. The keys and the focus are the runtime's (data-rask-contain-keys and data-rask-focus-follows on the grid, data-rask-focus-target on the tab stop), and none of them scrolls the page behind the grid.
Key | What happens |
|---|---|
| ArrowLeft, ArrowRight | A day back or on, stepping over disabled days and into the next month; the focus goes with the day. |
| ArrowUp, ArrowDown | A week back or on. |
| PageUp, PageDown, Home, End | Page a month and let the focus fall to the page, as Flux's do. |
| Enter, Space | Pick the day. |
On a phone
Under 640 px the calendar folds to one month whatever Months says, and its days grow to 44 px a thumb can press. Each cell is a gridcell with aria-selected, and its button carries the full date as its name ("Thursday, January 15, 2026").
Reference
Ui.Calendar
A month of days to pick from. Ui.Calendar picks a day, Ui.Calendar.Multiple several, Ui.Calendar.Range a range.
Step | Takes | What it does |
|---|---|---|
.Bind(…) | () => what the mode binds | The member it reads and writes: () => m.Day, a collection of days, or a UiDateRange. Or Value with OnChange. |
.Value(…) | a DateOnly, days, or a UiDateRange | What it holds, for a page that holds it itself. Values(days) for several. |
.OnChange(…) | a handler given the new value | Runs with the new value when a day is picked. |
.AfterBind(…) | a handler given the new value | Runs after Bind has written the model. |
.Validate(…) | a validator | A rule the value is checked by when it is written. |
.Live() | bool | Writes the model as soon as a day is picked, not with the next action. |
.Mode(…) | Ui.CalendarModeSingle Multiple Range Each is a step of its own too, unless an HTML tag has its name. | What is picked, as a value. The opening step says it; this has to agree with what is bound. |
.Multiple() | bool | Flux's multiple: the same as Mode(Ui.CalendarMode.Multiple). |
.Min(…) | a DateOnly | The earliest day that can be picked. Days before it are disabled. |
.Max(…) | a DateOnly | The latest day that can be picked. |
.Unavailable(…) | DateOnly days: [today.AddDays(1)] | Days that cannot be picked: holidays, booked days. |
.Size(…) | Ui.CalendarSizeBase Xs Sm Lg Xl Xxl Each is a step of its own too, unless an HTML tag has its name. | How large the day cells are. 44px when unset. |
.StartDay(…) | DayOfWeekSunday Monday Tuesday Wednesday Thursday Friday Saturday Each is a step of its own too, unless an HTML tag has its name. | The day the week starts on. The locale's own when unset. |
.Months(…) | int | How many months are shown side by side. One, or two for a range, when unset. |
.MinRange(…) | int | The fewest days a range may hold. |
.MaxRange(…) | int | The most days a range may hold. |
.OpenTo(…) | a DateOnly | The day it opens to while nothing is chosen. |
.ForceOpenTo() | bool | Opens to OpenTo whatever is chosen. |
.Navigation() | bool | False hides the month steps. |
.Static() | bool | A calendar to look at: no day can be picked. |
.WeekNumbers() | bool | Shows each week's number. |
.SelectableHeader() | bool | Makes the month and the year in the header selects. |
.WithToday() | bool | A button that comes back to this month, and picks today once there. |
.WithInputs() | bool | A field over the months to type the day into, or a range's start and end. None for several days. |
.FixedWeeks() | bool | Always six weeks, so paging never changes the height. |
.Locale(…) | string | The locale the names and the week follow: "ja-JP". The app's culture when unset. |