Skip to main content
twblazor
Home
  • Get started
  • Tailwind with Blazor
  • Why twblazor?
  • Theme
  • Class Merge
    • Configuring localization
    • RTL Provider
  • Breadcrumb
  • Card
  • Carousel
  • Collapse
  • Footer
  • MenuNew
  • Navbar
  • On This PageNew
  • Sidebar
  • Stepper
  • Tabs
  • Tree List
  • Button
  • Checkbox
  • Color Picker
    • Date
    • Date Range
    • Datetime
    • Datetime Range
    • Time
    • Time Range
  • File Upload
  • NumericNew
  • Radio Button
  • RatingNew
  • Select
  • Slider
  • Switch
  • Textfield
  • Alert
  • Avatar
  • Chip
  • Icon
  • Progress
  • Skeleton
  • Spinner
  • TooltipNew
  • Data Table
  • Pagination
  • Pick List
  • BarcodeNew
  • QR CodeNew
  • CalendarNew
  • Table
  • Overview
  • Area
  • Bar
  • Box Plot
  • Bullet
  • Bump
  • Candlestick
  • Column
  • Dot Plot
  • Dumbbell
  • Funnel
  • Gantt
  • Gauge
  • Heatmap
  • Histogram
  • Icon
  • Line
  • Lollipop
  • Matrix
  • Nightingale
  • Parallel Coordinates
  • Pie
  • Quadrant
  • Radar
  • Radial Bar
  • Range
  • Scatter
  • Slope
  • Small Multiples
  • Strip Plot
  • Treemap
  • Waffle
  • Waterfall
  • Arc Diagram
  • Barcode
  • Beeswarm
  • Bump Area
  • Categorical Scatter
  • Chord Diagram
  • Circular Treemap
  • Contour
  • Convex Treemap
  • Density Plot
  • Dendrogram
  • Euler Diagram
  • Flowchart
  • Hexagonal Binning
  • Horizon
  • Marimekko
  • Network Diagram
  • Radial Histogram
  • Ridgeline
  • Sankey
  • Stream Graph
  • Sunburst
  • Venn Diagram
  • Violin Plot
  • Word Cloud
  • Dialog Service
  • Toast Service
API DocumentationAI Agents & Skills
Home Get Started

Data

TwCalendar

TwCalendar is a Blazor Day, Week and Month calendar with drag and drop, all-day events, a built-in create and edit dialog, and configurable event details.

Last updated 18 October 2026

Please be aware this component is new, may have bugs and/or be subject to change. Please report any bugs at https://github.com/twblazor/twblazor/issues.

Views

Bind SelectedDate and View to switch between Day, Week and Month. The header names what you're looking at: the day, the week or the month. "Today" is on the left and the previous and next buttons are on the right. Events that overlap in time sit side by side. The Day and Week grids scroll once they pass MaxHeight (40rem by default), and you can pass any CSS length, such as "30rem" or "600px". They open scrolled to ScrollToTime, 8am by default, so you land on the working day instead of midnight.

Click an event to see its details. This calendar isn't editable, so every event opens read-only, and the extra details come from DialogProperties (see "Event Details in the Dialog" below).
18 Oct 2026 - 24 Oct 2026 Week 4
October 2026
Previous week Next week
Day view Week view Month view
 
All day
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="selectedDate" @bind-View="view" Schedules="_schedules" DialogProperties="_dialogProperties" />
CSHARP
@code {
    private DateTime selectedDate = DateTime.Today;
    private TwCalendarView view = TwCalendarView.Week;
    
    private readonly List<Schedule<EventDetails>> _schedules = BuildSampleSchedules();
    
    public enum EventPriority
    {
        Low,
        Medium,
        High
    }
    
    public class EventDetails
    {
        public string? Notes { get; set; }
        public string? Location { get; set; }
        public string? Organizer { get; set; }
        public int Attendees { get; set; }
        public EventPriority Priority { get; set; }
        public bool IsOnline { get; set; }
    }
    
    private readonly Dictionary<string, string> _dialogProperties = new()
    {
        [nameof(EventDetails.Notes)] = "Notes",
        [nameof(EventDetails.Location)] = "Location",
        [nameof(EventDetails.Organizer)] = "Organizer",
        [nameof(EventDetails.Attendees)] = "Attendees",
        [nameof(EventDetails.Priority)] = "Priority",
        [nameof(EventDetails.IsOnline)] = "Online meeting"
    };
    
    private static Schedule<EventDetails> Event(string name, int dayOffset, double startHour, double hours, string color, EventDetails details, bool readOnly = false)
    {
        var start = DateTime.Today.AddDays(dayOffset).AddHours(startHour);
        return new() { Name = name, DateTimeStart = start, DateTimeEnd = start.AddHours(hours), Color = color, ReadOnly = readOnly, Value = details };
    }
    
    private static Schedule<EventDetails> AllDay(string name, int firstDayOffset, int days, string color, EventDetails details)
    {
        var start = DateTime.Today.AddDays(firstDayOffset);
        return new() { Name = name, DateTimeStart = start, DateTimeEnd = start.AddDays(days), Color = color, Value = details };
    }
    
    private static List<Schedule<EventDetails>> BuildSampleSchedules() =>
    [
        Event("Team standup", 0, 9, 0.5, "#2563eb", new() { Notes = "Quick round of blockers. Keep it to 15 minutes.", Location = "Meeting room 2", Organizer = "Priya Shah", Attendees = 8, Priority = EventPriority.Medium }),
        Event("Design review", 0, 10, 1, "#9333ea", new() { Notes = "Walk through the new dashboard mockups and collect feedback.", Location = "Studio", Organizer = "Marcus Lee", Attendees = 6, Priority = EventPriority.High, IsOnline = true }),
        Event("1:1 with Sam", 0, 10.5, 0.75, "#db2777", new() { Notes = "Talk about the Q4 goals and the conference talk.", Organizer = "Jordan Ellis", Attendees = 2, Priority = EventPriority.Medium, IsOnline = true }),
        Event("Lunch", 0, 12, 1, "#f59e0b", new() { Notes = "Team lunch. Booked under Shuter.", Location = "The Corner Cafe", Organizer = "Office team", Attendees = 12, Priority = EventPriority.Low }, readOnly: true),
        Event("Customer demo", 0, 15, 1, "#0d9488", new() { Notes = "Show the new reporting module to the Hartley account team.", Location = "Meeting room 1", Organizer = "Aisha Khan", Attendees = 5, Priority = EventPriority.High, IsOnline = true }),
        Event("Code review", 0, 16.5, 0.5, "#4f46e5", new() { Notes = "Pairing session on the payments refactor.", Organizer = "Tom Becker", Attendees = 3, Priority = EventPriority.Low, IsOnline = true }),
    
        Event("OKR workshop", 1, 10, 2, "#7c3aed", new() { Notes = "Draft next quarter's objectives. Bring your team's draft key results.", Location = "Main hall", Organizer = "Priya Shah", Attendees = 24, Priority = EventPriority.High }),
        Event("Sprint planning", 1, 14, 1.5, "#16a34a", new() { Notes = "Bring estimates for the top ten backlog items.", Location = "Meeting room 1", Organizer = "Marcus Lee", Attendees = 9, Priority = EventPriority.Medium }),
        AllDay("Company holiday", 1, 1, "#f59e0b", new() { Notes = "Offices are closed. Support is on call.", Organizer = "People team", Attendees = 120, Priority = EventPriority.Low }),
    
        Event("Client call", 2, 9, 0.75, "#0d9488", new() { Notes = "Share the revised timeline and ask about the sign-off date.", Organizer = "Aisha Khan", Attendees = 4, Priority = EventPriority.High, IsOnline = true }),
        Event("Workshop prep", 2, 13, 1, "#be185d", new() { Notes = "Print the handouts and test the projector.", Location = "Meeting room 2", Organizer = "Jordan Ellis", Attendees = 2, Priority = EventPriority.Low }),
        AllDay("Product conference", 2, 3, "#be185d", new() { Notes = "Three days of talks. Our slot is on the second morning.", Location = "Convention centre", Organizer = "Events team", Attendees = 40, Priority = EventPriority.High }),
    
        Event("Budget review", 3, 9, 0.5, "#0891b2", new() { Notes = "Check spend against the quarterly forecast.", Location = "Finance office", Organizer = "Rhea Patel", Attendees = 5, Priority = EventPriority.Medium }),
        Event("Interview", 3, 10, 0.5, "#7c3aed", new() { Notes = "Frontend candidate, second round. Questions are in the hiring doc.", Location = "Meeting room 1", Organizer = "Tom Becker", Attendees = 3, Priority = EventPriority.Medium }),
        Event("All hands", 3, 11, 1, "#ea580c", new() { Notes = "Company update and Q&A with the leadership team.", Location = "Main hall", Organizer = "Leadership team", Attendees = 120, Priority = EventPriority.High, IsOnline = true }),
        Event("Extra sync", 3, 13, 0.5, "#059669", new() { Notes = "Follow up on the open questions from the all hands.", Organizer = "Priya Shah", Attendees = 4, Priority = EventPriority.Low }),
        Event("Interview debrief", 3, 14, 0.5, "#7c3aed", new() { Notes = "Share feedback while it's fresh and decide on next steps.", Location = "Meeting room 1", Organizer = "Tom Becker", Attendees = 3, Priority = EventPriority.Medium }),
    
        Event("Hiring sync", 4, 11, 1, "#0369a1", new() { Notes = "Review the pipeline for the two open engineering roles.", Location = "Meeting room 2", Organizer = "People team", Attendees = 5, Priority = EventPriority.Medium }),
        Event("Retro", 4, 16, 1, "#4f46e5", new() { Notes = "What went well, what didn't, and one thing to try next sprint.", Location = "Meeting room 2", Organizer = "Marcus Lee", Attendees = 9, Priority = EventPriority.Medium }),
        AllDay("Release weekend", 5, 3, "#0369a1", new() { Notes = "Freeze on Friday night. Roll out on Saturday.", Organizer = "Release manager", Attendees = 14, Priority = EventPriority.High, IsOnline = true }),
        Event("Payroll run", 6, 9, 1, "#64748b", new() { Notes = "Runs automatically. Finance checks the totals afterwards.", Organizer = "Finance", Attendees = 2, Priority = EventPriority.Low, IsOnline = true }, readOnly: true),
    
        Event("Launch", -2, 9, 1, "#dc2626", new() { Notes = "Version 2.0 goes live. Support is on standby.", Organizer = "Release manager", Attendees = 30, Priority = EventPriority.High, IsOnline = true }),
        Event("Security training", -1, 14, 1.5, "#0891b2", new() { Notes = "Yearly refresher on phishing and password hygiene.", Location = "Main hall", Organizer = "IT security", Attendees = 60, Priority = EventPriority.Medium }),
        Event("Support handover", -1, 17, 0.5, "#16a34a", new() { Notes = "Pass open tickets to the evening rota.", Organizer = "Aisha Khan", Attendees = 4, Priority = EventPriority.Low, IsOnline = true }),
        AllDay("Planning offsite", -5, 2, "#7c3aed", new() { Notes = "Two days away from the desks to plan the year.", Location = "Lakeside lodge", Organizer = "Leadership team", Attendees = 18, Priority = EventPriority.High }),
    
        Event("Board meeting", 9, 10, 2, "#9333ea", new() { Notes = "Quarterly results and the hiring plan.", Location = "Boardroom", Organizer = "Leadership team", Attendees = 8, Priority = EventPriority.High }),
        Event("Roadmap review", 10, 14, 1, "#2563eb", new() { Notes = "Check the next two quarters against customer requests.", Location = "Meeting room 1", Organizer = "Priya Shah", Attendees = 10, Priority = EventPriority.Medium }),
        AllDay("Customer onsite", 12, 3, "#ea580c", new() { Notes = "The Hartley team visits for training and a product tour.", Location = "Head office", Organizer = "Aisha Khan", Attendees = 16, Priority = EventPriority.High }),
    ];
}

Choosing which views to offer

All three views are available by default. Pass EnabledViews an array of TwCalendarView values to offer only some of them. The switcher in the header only shows the views you list, always in the order Day, Week, Month. With a single view the switcher disappears, and the calendar just shows that view. If View isn't in the list, the first enabled view is shown instead. When the Day view is turned off, clicking a day in the Week or Month view only selects the date. This example offers just Week and Month.
18 Oct 2026 - 24 Oct 2026 Week 4
October 2026
Previous week Next week
Week view Month view
 
All day
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="enabledViewsSelectedDate" @bind-View="enabledViewsView" EnabledViews="_enabledViews" Schedules="_schedules" DialogProperties="_dialogProperties" />
CSHARP
@code {
    private DateTime enabledViewsSelectedDate = DateTime.Today;
    private TwCalendarView enabledViewsView = TwCalendarView.Week;
    
    private readonly TwCalendarView[] _enabledViews = [TwCalendarView.Week, TwCalendarView.Month];
}

All-day and multi-day events

An event that runs from midnight to midnight is an all-day event, and anything that lasts a day or longer is treated the same way. In the Day and Week views these sit in an "All day" strip above the time grid, like Microsoft Teams, so they don't cover the hours below. An event that covers several days is one bar across those days. In the Month view every event that covers more than one day is drawn as a bar across its days too, and it breaks at the end of each week and carries on in the next. An event that crosses midnight but lasts less than a day stays in the time grid and shows on both days.

To make an event all day, turn on "All day" in the edit dialog, or set it to start and end at midnight yourself. The dialog then asks for dates instead of times, and the end date is the last day you want to cover. You can drag an all-day event onto another day, and it keeps its length. In the example below, "Company holiday", "Product conference" and "Release weekend" are all-day events.
18 Oct 2026 - 24 Oct 2026 Week 4
October 2026
Previous week Next week
Day view Week view Month view
 
All day
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="allDaySelectedDate" @bind-View="allDayView" Editable="true" DialogProperties="_dialogProperties" @bind-Schedules="allDaySchedules" />
CSHARP
@code {
    private DateTime allDaySelectedDate = DateTime.Today.AddDays(1);
    private TwCalendarView allDayView = TwCalendarView.Week;
    
    private List<Schedule<EventDetails>> allDaySchedules = BuildSampleSchedules();
}

Adding & editing events

Set Editable to get the "+" button in the header and let people open events in an edit dialog. Click an empty time slot to create an event there, or click an existing event to change it. An event with ReadOnly set always opens read-only, like "Lunch" and "Payroll run" below, even when Editable is on. Editable events can also be dragged. Move them between time slots (and between days in Week view), or between days in Month view, where the time of day stays the same. A dashed placeholder shows where the event will land, even over a slot that already has an event. The dialog's color picker sets Schedule<T>.Color, which tints the event in every view. Leave it empty to use the theme color.
Sunday, October 18, 2026 Week 4
18 Oct 2026 - 24 Oct 2026
Previous day Next day
Day view Week view Month view
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="editableSelectedDate" @bind-View="editableView" Editable="true" DialogProperties="_dialogProperties" @bind-Schedules="editableSchedules" />
CSHARP
@code {
    private DateTime editableSelectedDate = DateTime.Today;
    private TwCalendarView editableView = TwCalendarView.Day;
    
    private List<Schedule<EventDetails>> editableSchedules = BuildSampleSchedules();
}

Event details in the dialog

Your own data lives in Schedule<T>.Value. To show it in the dialog, pass DialogProperties: a dictionary that maps a property name on your type to the label you want. The read-only dialog lists each value that isn't empty. The edit dialog gives each property an editor that fits its type: a text field for strings, a number field for numbers, a switch for bool, a date and time picker for dates, and a select for enums. A property with no public setter shows as plain text. Names that don't match a property are skipped, and an empty label turns OrderNumber into "Order number". The properties appear in the order you list them, after Name, Start, End and Color.

The dialog finds properties with reflection. Your type needs a public parameterless constructor so new events have something to edit, and the dialog works on a shallow copy of Value, so cancelling never changes the original. If you need something this can't express, such as a field that depends on another, use EventDialogContent to add your own markup. In this example, the empty label for Priority falls back to "Priority" and Attendees is relabeled "Guests".
Sunday, October 18, 2026 Week 4
18 Oct 2026 - 24 Oct 2026
Previous day Next day
Day view Week view Month view
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="detailsSelectedDate" @bind-View="detailsView" Editable="true" DialogProperties="_detailsDialogProperties" @bind-Schedules="detailsSchedules" />
CSHARP
@code {
    private DateTime detailsSelectedDate = DateTime.Today;
    private TwCalendarView detailsView = TwCalendarView.Day;
    
    private List<Schedule<EventDetails>> detailsSchedules = BuildSampleSchedules();
    
    private readonly Dictionary<string, string> _detailsDialogProperties = new()
    {
        [nameof(EventDetails.Notes)] = "Notes",
        [nameof(EventDetails.Location)] = "Location",
        [nameof(EventDetails.Attendees)] = "Guests",
        [nameof(EventDetails.Priority)] = "",
        [nameof(EventDetails.IsOnline)] = "Online meeting"
    };
}

Custom event content

Use EventContentTemplate to change how each event looks in all three views.
18 Oct 2026 - 24 Oct 2026 Week 4
October 2026
Previous week Next week
Day view Week view Month view
 
All day
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="customContentSelectedDate" @bind-View="customContentView" Schedules="_schedules" DialogProperties="_dialogProperties">
    <EventContentTemplate>
        <span class="text-xs font-semibold">@context.Name</span>
        <span class="block text-xs opacity-80">@context.DateTimeStart.ToString("h:mm tt")</span>
    </EventContentTemplate>
</TwCalendar>
CSHARP
@code {
    private DateTime customContentSelectedDate = DateTime.Today;
    private TwCalendarView customContentView = TwCalendarView.Week;
}

Search events

TwCalendar has no search of its own. Set OnSearch and the header shows a search icon that calls your handler. This example opens a small dialog, built like any other TwDialog content, that filters Schedules by name. When you pick a result, it jumps to that event by setting @bind-SelectedDate and @bind-View on the calendar.
18 Oct 2026 - 24 Oct 2026 Week 4
October 2026
Search events Previous week Next week
Day view Week view Month view
 
All day
12 AM
1 AM
2 AM
3 AM
4 AM
5 AM
6 AM
7 AM
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
5 PM
6 PM
7 PM
8 PM
9 PM
10 PM
11 PM
11:25 PM
RAZOR
<TwCalendar T="EventDetails" @bind-SelectedDate="searchSelectedDate" @bind-View="searchView" Schedules="_schedules" DialogProperties="_dialogProperties" OnSearch="OnSearchAsync" />
CSHARP
@code {
    private DateTime searchSelectedDate = DateTime.Today;
    private TwCalendarView searchView = TwCalendarView.Week;
    
    private async Task OnSearchAsync()
    {
        var reference = await DialogService.ShowAsync<CalendarSearchDialog<EventDetails>>("Search events", new TwDialogParameters
        {
            ["Schedules"] = _schedules
        });
    
        var selected = await reference.GetReturnValueAsync<Schedule<EventDetails>>();
        if (selected is not null)
        {
            searchSelectedDate = selected.DateTimeStart.Date;
            searchView = TwCalendarView.Day;
        }
    }
}

Keyboard navigation

Each view is a grid with a single Tab stop for its days or time slots. Events are buttons reached with Tab. A read-only calendar has no time slots to focus, only its events. Dragging an event has a keyboard equivalent: open the event and change its start and end.
Key What it does
Tab or Shift+ plus Tab Moves between the toolbar buttons, the grid, and the events.
Arrow Left or Arrow Right Month view: moves to the previous or next day. Week view: moves to the same time on the previous or next day. The two keys swap in a right to left layout.
Arrow Up or Arrow Down Month view: moves to the same weekday in the previous or next week. Day and week view: moves to the previous or next time slot.
Home or End Month view: moves to the first or last day of the week. Day and week view: moves to the first or last time slot.
Enter or Space On a day: opens that day and moves focus to the calendar's title, which names it. On a time slot: starts a new event there. On an event: opens it.
Escape Closes the event dialog.

Theme configuration

Styling is controlled by TwBlazorTheme.Components (a TwCalendarTheme), configured via the AddTwBlazor() options callback described on the Get Started page. Every property is a set of Tailwind classes, so changes are picked up by your own Tailwind build.
Property Type Controls
Container string
Header string
HeaderDateGroup string
HeaderTitleRow string
HeaderTitle string
HeaderSubtitle string
NavButton string
ViewButtonActive string
AddButton string
SearchButton string
ScrollContainer string
TimeGutter string
TimeGutterLabel string
NowIndicatorLabel string
NowIndicatorLine string
NowIndicatorDot string
SlotRow string
SlotRowHover string
DayColumnHeader string
DayColumnHeaderLabel string
DayColumnHeaderCompact string
DayColumnHeaderWeekday string
DayColumnHeaderDayNumber string
DayColumnHeaderDayNumberToday string
DayColumnDivider string
EventChip string
EventChipTitle string
EventChipTime string
EventChipCompact string
EventChipTitleCompact string
EventChipTimeCompact string
EventChipReadOnly string
EventChipDraggable string
DropPlaceholder string
TodayHighlight string
AllDayRow string
AllDayLabel string
AllDayGrid string
AllDayChipInset string
SegmentSpan string
SegmentContinuesBefore string
SegmentContinuesAfter string
SegmentSpacer string
MonthGrid string
MonthCell string
MonthCellPrevNext string
MonthDayNumber string
MonthEventList string
MonthEventRow string
MonthEventName string
MonthEventTime string
MonthOverflowLabel string
EventDialogFields string
EventDialogActions string
EventDialogAccent string
EventDialogDateRow string
EventDialogSummary string
EventDialogSummaryIcon string
EventDialogReadOnlyField string
EventDialogReadOnlyLabel string
EventDialogReadOnlyValue string

Full reference: TwCalendarTheme on the API docs.

On this page

  • Views
  • Choosing which views to offer
  • All-day and multi-day events
  • Adding & editing events
  • Event details in the dialog
  • Custom event content
  • Search events
  • Keyboard navigation
  • Theme configuration
Discord (opens in a new tab) • GitHub (opens in a new tab) • Documentation (opens in a new tab) • MIT License (opens in a new tab) • llms.txt (opens in a new tab)

twblazor is and always will be free and open source.

An unhandled error has occurred. Reload