| license | MIT | ||||
|---|---|---|---|---|---|
| name | syncfusion-blazor-toolkit-calendars | ||||
| description | Implement Syncfusion Blazor Toolkit calendar and date/time input components — SfCalendar, SfDatePicker, SfDateTimePicker, SfTimePicker. USE FOR: date-only, date-time, or time-only selection; date range restriction (Min/Max); custom date formats (culture-invariant vs locale); multi-date selection; disabled weekends/holidays via DayCellRendering; and culture/timezone-sensitive formatting. DO NOT USE FOR: text or email input (use syncfusion-blazor-toolkit-inputs), scheduling or calendar recurrence (use Scheduler — not in this skill), or date validation that requires an API round-trip (handle in EditContext validator instead). | ||||
| compatibility | .NET 8+, render-modes: Server, WebAssembly, Auto | ||||
| metadata |
|
⚙️ Render mode:
SfCalendar,SfDatePicker,SfDateTimePicker,SfTimePickerrequire an interactive render mode (Server, WebAssembly, or Auto @ .NET 8+). ReadAGENTS.mdbefore generating —EnableUtcand interactive event delivery require it.
The four rules below are the highest-impact; the rest (5–8) live in references/critical-gotchas.md — read it first if you have any time/format/validation question. These top 4 together prevent ~95% of calendar bug reports.
- Pick the right
TValue:DateOnly?for date-only (avoids accidental timezone drift);DateTime?for date-time; never useTimeOnly?— it is not supported bySfTimePicker. - Always set
EnableUtc="true"for Server / Auto render modes that persist dates to a database. Without it the calendar persists the local timezone representation, which round-trips incorrectly. Formatis culture-invariant when you supply a literal pattern ("dd/MM/yyyy"); culture-aware when you supply a token ("d","D"). Never mix the two in one form.Min/Maxare inclusive and timezone-relative. Provide them in the calendar's display timezone; UTC only whenEnableUtc="true".
Rules 5–8 — DayCellRendering mutation discipline, aria-label opt-in,
@bind-Value + ValueChanged double-fire, and midnight re-render — live in
references/critical-gotchas.md.
Cross-reference with critical-gotchas.md: rules below mirror the 8-rule reference at references/critical-gotchas.md; the rules duplicated here (rows 1, 3, 5, 7, 8) are surfaced inline because they're the most common agent mistakes. When in doubt on timezones, values, or rendering — read the full gotchas reference.
| Anti-pattern | Symptom | Fix |
|---|---|---|
TValue="DateTime" (non-nullable) inside EditForm |
"Object reference not set" on first render; type-mismatch exception | Use TValue="DateTime?" (or DateOnly?) |
TValue="TimeOnly?" on SfTimePicker |
Empty input; [Required] rejects valid user input |
TValue="DateTime? Format="HH:mm" |
Min/Max in UTC with EnableUtc="false" |
Off-by-one day selection | Pick one: either EnableUtc="true" and UTC math, or local timezone end-to-end |
args.IsHighlight = true in DayCellRendering |
Throws — property doesn't exist | Use args.CellClass = "highlight" and style the class |
Subscribe ValueChanged and @bind-Value |
Both fire; the second wins; sometimes a render-loop | Pick one. Use @bind-Value:after for side effects |
One SfCalendar driving two date inputs |
Renderer mounts a single DOM, second input has no calendar | Per-input <SfDatePicker>, share state via CascadingValue<T> |
<SfCalendar> inside a Server-mode page that pre-renders today |
Highlight misses today's date because of a half-day clock skew between server UTC and client local | Always set EnableUtc="true" for Server / Auto |
Format="d" mixed with Format="yyyy-MM-dd" in one form |
Validation messages disagree; tooltip provides two formats | Pick one (invariant vs culture) per form |
Reading args.Date and assuming it's DateOnly-typed |
It's DateTime (midnight, server-side); comparison may be wrong |
Compare against args.Date.Date against your Min same way |
Setting Min only — never Max |
Calendar allows any future date; user picks 2099 | Pair with Max whenever Min is set |
Bad:
<SfCalendar TValue="DateTime?" @bind-Value="@today" Min="@new DateTime(2026,1,1)" />
@code { private DateTime? today = DateTime.UtcNow.Date; }Why it fails: Min is provided in UTC, but the calendar's display
timezone is the user's local. A user in Tokyo at 09:00 (Jan 1 local =
00:00 UTC) sees "Jan 1 enabled" — correct by accident. A user in New York
at 19:00 (Jan 1 local = 00:00 UTC) sees "Dec 31 disabled" — wrong.
Correct: keep both in the same timezone, and align with the database side:
@using Syncfusion.Blazor.Toolkit.Calendars
<SfCalendar TValue="DateTime?"
EnableUtc="true"
@bind-Value="@today" />
@code {
private DateTime? today; // stored UTC; ToLocalTime() on display
}Or — if the calendar must stay in local — pass Min in local:
Min="@new DateTime(2026,1,1,0,0,0,DateTimeKind.Local)"Bad:
private void OnCell(RenderDayCellEventArgs args)
{
if (IsHoliday(args.Date)) args.IsHighlight = true; // CS1061
}Fix: use CellClass (you style with CSS):
private void OnCell(RenderDayCellEventArgs args)
{
if (IsHoliday(args.Date))
{
args.CellClass = "holiday-cell";
args.IsDisabled = true; // pick one — these aren't mutually exclusive
}
}.holiday-cell { background: var(--brand-warning-tint); color: var(--brand-warning-fg); }Bad:
@using Syncfusion.Blazor.Toolkit.Calendars
<SfDatePicker TValue="DateTime?"
@bind-Value="@_date"
ValueChanged="OnDate" />
<!-- fires twice, @bind-Value then ValueChanged -->Correct: use @bind-Value:after (.NET 8+) for post-commit logic:
@using Syncfusion.Blazor.Toolkit.Calendars
<SfDatePicker TValue="DateTime?"
@bind-Value="@_date"
@bind-Value:after="OnDateCommitted" />
@code {
private DateTime? _date;
private async Task OnDateCommitted() { /* fires once after binding commits */ }
}Bad: leave SfCalendar open across midnight expecting the highlight to
move forward — the renderer doesn't re-tick.
Correct: for "ticking" UX, wire a Timer for the page-level refresh:
@implements IDisposable
@code {
private System.Threading.Timer? _t;
protected override void OnAfterRender(bool first)
{
if (first)
{
_t = new System.Threading.Timer(_ => InvokeAsync(StateHasChanged),
null,
TimeSpan.Zero,
TimeSpan.FromMinutes(15));
}
}
public void Dispose() => _t?.Dispose();
}For non-essential UX, prefer a plain "Today" pill button that re-binds the date on click instead of keeping a timer running.
Correct:
@using Syncfusion.Blazor.Toolkit.Calendars
<SfCalendar TValue="DateTime?"
EnableUtc="true"
@bind-Value="@_utcDate"
Format="yyyy-MM-dd" />
@code {
public DateTime? _utcDate; // stays UTC
public string LocalLabel => _utcDate?.ToLocalTime().ToString("yyyy-MM-dd") ?? "";
}For the full 8-rule reference, see references/critical-gotchas.md.
@using Syncfusion.Blazor.Toolkit.Calendars
<SfDatePicker TValue="DateTime?"
Placeholder="Select a date"
@bind-Value="selectedDate" />
@code {
private DateTime? selectedDate;
}| Need | Read |
|---|---|
| First component / installation | references/getting-started.md |
| Core features (selection, formatting, validation) | references/features.md |
Per-component reference (SfCalendar, DatePicker, DateTimePicker, TimePicker) |
references/calendar.md, references/datepicker.md, references/datetimepicker.md, references/timepicker.md |
| Events and two-way binding | references/events-binding.md |
| API reference (properties, events, methods, enums) | references/api-reference.md |
| Troubleshooting | references/troubleshooting.md |
⚠️ Read references/critical-gotchas.md first — UTC vs local time,TValueselection,Formatmixing,Min/Maxinclusivity,DayCellRenderingmutation rules, accessibility labelling,@bind-Value+ValueChangedinteraction,DateTime.Nowsnapshot.
- Read first: references/critical-gotchas.md
- Setup: references/getting-started.md
- Per-component reference: the relevant
references/*.md