MUI Scheduler

Event calendars, resource timelines, and radial charts for Dash, wrapping the MUI X Scheduler.

Full documentation: https://muischeduler.2plot.dev — the dedicated dash-mui-scheduler documentation site, with the complete API reference and deeper examples. This page is the quick-start overview.

Installation

Visit GitHub Repo · PyPI

pip install dash-mui-scheduler

Introduction

dash-mui-scheduler is the successor to dash-fullcalendar — it replaces it in this catalogue. It wraps the MUI X Scheduler for Plotly Dash, so you can build event calendars and resource timelines in pure Python.

Events cross the Dash ↔ Python boundary as plain dictionaries with ISO-string dates, and every user interaction (create / move / resize / delete) round-trips straight back into your callbacks. Dark mode is automatic — the components follow the surrounding Dash Mantine Components color scheme.

The suite includes:

ComponentPlanWhat it is
EventCalendarCommunity (MIT)Day / week / month / agenda calendar with drag-and-drop, resizing, resources, and an editing dialog. No license key.
EventCalendarPremiumPremiumThe calendar plus the recurrence engine (RRULE events + exception dates).
EventTimelinePremiumA resource-row, Gantt-style timeline across configurable zoom presets.
RadialLineChartPremium (preview)Polar line/area charts for trends along periodic values.
RadialBarChartPremium (preview)Polar bar charts for comparing values along periodic categories.

The underlying MUI X Scheduler is in beta (@mui/[email protected]); this wrapper pins the beta exactly, so the upstream API may change before its stable release.

Quick Start

Pass a list of event dicts — each needs at least id, title, start, and end (ISO strings). Optional keys include description, color, allDay, resource, and per-event draggable / resizable / readOnly overrides.

# File: docs/dash_mui_scheduler/quickstart_example.py

"""Quick-start EventCalendar example with sample events."""
from datetime import datetime, timedelta

import dash_mantine_components as dmc
import dash_mui_scheduler as dms

# Anchor the sample events to the current week so the calendar
# always opens with something to look at.
_monday = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
_monday -= timedelta(days=_monday.weekday())


def _iso(day_offset: int, hour: int, minute: int = 0) -> str:
    return (_monday + timedelta(days=day_offset, hours=hour, minutes=minute)).isoformat()


# Events are plain dicts with ISO-string dates.
# Required keys: id, title, start, end. Everything else is optional.
events = [
    {
        "id": "1",
        "title": "Team Standup",
        "start": _iso(0, 9),
        "end": _iso(0, 9, 30),
        "color": "blue",
    },
    {
        "id": "2",
        "title": "Design Review",
        "start": _iso(1, 13),
        "end": _iso(1, 14, 30),
        "color": "purple",
        "description": "Review the new dashboard mockups.",
    },
    {
        "id": "3",
        "title": "Client Call",
        "start": _iso(2, 10),
        "end": _iso(2, 11),
        "color": "teal",
    },
    {
        "id": "4",
        "title": "Sprint Demo",
        "start": _iso(3, 15),
        "end": _iso(3, 16),
        "color": "green",
    },
    {
        "id": "5",
        "title": "Conference Day",
        "start": _iso(4, 0),
        "end": _iso(4, 23, 59),
        "allDay": True,
        "color": "orange",
    },
]

component = dmc.Paper(
    dms.EventCalendar(
        id="mui-scheduler-quickstart-cal",
        events=events,
        defaultView="week",
        height=620,
    ),
    withBorder=True,
    p="md",
)

Events & Callbacks

The events prop is both an input and an output. Seed it with your data; the calendar writes the full new array back whenever the user creates, moves, resizes, or deletes an event. For a lighter-weight signal, lastAction reports just the most recent change:

{"type": "create" | "update" | "delete" | "move" | "resize" | "change",
 "event": {...},            # the affected event (or None)
 "event_timestamp": ...}

Other stateful props (view, visibleDate, preferences, visibleResources) each come as a controlled prop plus an uncontrolled defaultX variant, and are written back on change — so you can read the active view or the date the user navigated to in a callback.

# File: docs/dash_mui_scheduler/interactive_example.py

"""Interactive example: round-tripping event edits through callbacks.

`events` is BOTH an input and an output — the calendar writes the full
array back on every create / move / resize / delete. `lastAction` is a
convenience output describing just the most recent change.
"""
import json
from datetime import datetime, timedelta

import dash_mantine_components as dmc
import dash_mui_scheduler as dms
from dash import callback, Input, Output

_monday = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
_monday -= timedelta(days=_monday.weekday())


def _iso(day_offset: int, hour: int, minute: int = 0) -> str:
    return (_monday + timedelta(days=day_offset, hours=hour, minutes=minute)).isoformat()


events = [
    {"id": "a", "title": "Drag me around", "start": _iso(0, 10), "end": _iso(0, 11), "color": "indigo"},
    {"id": "b", "title": "Resize my edges", "start": _iso(2, 13), "end": _iso(2, 15), "color": "pink"},
    {"id": "c", "title": "Click to edit or delete", "start": _iso(4, 9), "end": _iso(4, 10), "color": "amber"},
]

component = dmc.Stack(
    [
        dms.EventCalendar(
            id="mui-scheduler-interactive-cal",
            events=events,
            defaultView="week",
            height=560,
            eventCreation={"interaction": "click", "duration": 60},
        ),
        dmc.Group(
            [
                dmc.Badge("try it", color="teal", variant="light"),
                dmc.Text(
                    "Drag, resize, create (click an empty slot), edit, or delete an event — "
                    "every change lands in a Dash callback.",
                    size="sm",
                    c="dimmed",
                ),
            ],
            gap="xs",
        ),
        dmc.SimpleGrid(
            cols={"base": 1, "md": 2},
            children=[
                dmc.Paper(
                    [
                        dmc.Text("lastAction", fw=600, size="sm", mb="xs"),
                        dmc.Code(
                            "Interact with the calendar…",
                            id="mui-scheduler-last-action-output",
                            block=True,
                        ),
                    ],
                    withBorder=True,
                    p="md",
                ),
                dmc.Paper(
                    [
                        dmc.Text("events (round-tripped)", fw=600, size="sm", mb="xs"),
                        dmc.Text(
                            f"{len(events)} events",
                            id="mui-scheduler-event-count-output",
                            size="sm",
                            c="dimmed",
                        ),
                    ],
                    withBorder=True,
                    p="md",
                ),
            ],
        ),
    ],
    gap="md",
)


@callback(
    Output("mui-scheduler-last-action-output", "children"),
    Input("mui-scheduler-interactive-cal", "lastAction"),
    prevent_initial_call=True,
)
def show_last_action(last_action):
    if not last_action:
        return "Interact with the calendar…"
    # lastAction = {"type": "create"|"update"|"delete"|"move"|"resize"|"change",
    #               "event": {...}, "event_timestamp": ...}
    return json.dumps(
        {"type": last_action.get("type"), "event": last_action.get("event")},
        indent=2,
        default=str,
    )


@callback(
    Output("mui-scheduler-event-count-output", "children"),
    Input("mui-scheduler-interactive-cal", "events"),
    prevent_initial_call=True,
)
def show_event_count(current_events):
    current_events = current_events or []
    titles = ", ".join(e.get("title", "?") for e in current_events)
    return f"{len(current_events)} events — {titles}"

Premium Components

EventCalendarPremium, EventTimeline, and the radial charts wrap MUI X Premium packages. They all accept a licenseKey prop; without a valid MUI X Premium license key they still render, but with a watermark. All Premium components share the same @mui/x-license singleton, so a single key covers everything. The Dash wrapper code in this package is MIT-licensed; the underlying MUI X Premium libraries are not.

EventCalendarPremium — recurrence

Same API as EventCalendar, plus licenseKey and support for recurring events via rrule (an RFC-5545 RRULE string or an object) and exDates (exception dates):

import os
import dash_mui_scheduler as dms

dms.EventCalendarPremium(
    id="mui-scheduler-premium-cal",
    licenseKey=os.environ["MUI_X_LICENSE_KEY"],
    events=[{
        "id": 1, "title": "Standup",
        "start": "2026-07-06T09:00:00", "end": "2026-07-06T09:15:00",
        "rrule": "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
    }],
    defaultVisibleDate="2026-07-06",
)

EventTimeline

A Gantt-style timeline that lays events out in resource rows across configurable zoom presets. It shares the calendar's event/resource model (events, resources, lastAction, drag/resize props) and swaps the view/views props for preset / defaultPreset / presets, plus a resourceColumnLabel for the resource column header.

Radial Charts (Premium preview)

RadialLineChart and RadialBarChart are polar charts from @mui/x-charts-premium. Pass row-oriented dataset rows plus series that reference columns via dataKey; rotationAxis replaces the cartesian x-axis and radiusAxis replaces the y-axis. Clicking the chart reports the hit axis item via the clickData output. They are Unstable_ previews — production-ready, but their API may shift.

# File: docs/dash_mui_scheduler/radial_chart_example.py

"""RadialLineChart example (Premium preview).

Renders with a watermark unless a MUI X Premium `licenseKey` is provided.
Set the MUI_X_LICENSE_KEY environment variable to remove it.
"""
import os

import dash_mantine_components as dmc
import dash_mui_scheduler as dms
from dash import callback, Input, Output

# Row-oriented data; each series references a column via `dataKey`.
dataset = [
    {"month": "Jan", "london": 49, "paris": 51},
    {"month": "Feb", "london": 38, "paris": 41},
    {"month": "Mar", "london": 40, "paris": 47},
    {"month": "Apr", "london": 44, "paris": 52},
    {"month": "May", "london": 49, "paris": 63},
    {"month": "Jun", "london": 45, "paris": 49},
    {"month": "Jul", "london": 45, "paris": 62},
    {"month": "Aug", "london": 50, "paris": 53},
    {"month": "Sep", "london": 49, "paris": 47},
    {"month": "Oct", "london": 69, "paris": 61},
    {"month": "Nov", "london": 59, "paris": 55},
    {"month": "Dec", "london": 56, "paris": 58},
]

component = dmc.Paper(
    dmc.Stack(
        [
            dms.RadialLineChart(
                id="mui-scheduler-radial-lines",
                height=420,
                licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
                dataset=dataset,
                # rotationAxis = angular (x-like) axis, radiusAxis = radial (y-like) axis
                series=[
                    {"dataKey": "london", "label": "London (mm)", "curve": "natural", "showMark": True},
                    {"dataKey": "paris", "label": "Paris (mm)", "curve": "natural", "showMark": True},
                ],
                rotationAxis=[{"scaleType": "point", "dataKey": "month", "disableLine": True}],
                radiusAxis=[{"disableLine": True}],
                grid={"rotation": True, "radius": True},
            ),
            dmc.Text(
                "Click the chart to inspect an axis item.",
                id="mui-scheduler-radial-click-output",
                size="sm",
                c="dimmed",
                ta="center",
            ),
        ],
        gap="xs",
    ),
    withBorder=True,
    p="md",
)


@callback(
    Output("mui-scheduler-radial-click-output", "children"),
    Input("mui-scheduler-radial-lines", "clickData"),
    prevent_initial_call=True,
)
def show_click(click_data):
    if not click_data:
        return "Click the chart to inspect an axis item."
    return (
        f"Clicked {click_data.get('axisValue')} — "
        f"series values: {click_data.get('seriesValues')}"
    )

EventCalendar Properties

PropertyTypeDefaultDescription
idstring-Component ID for Dash callbacks.
eventslist of dicts-The events to render. Each needs id, title, start, end (ISO strings); optional description, timezone, resource, rrule, exDates, allDay, readOnly, color, draggable, resizable, className. Input AND output — written back on every edit.
lastActiondict-Output. The most recent change: {type, event, event_timestamp}.
view'day' \'week' \'month' \'agenda'-Controlled active view. Also an output (updated on view change).
defaultViewstring'week'Uncontrolled initial view.
viewslist of strings['day','week','month','agenda']Which views are offered.
visibleDatestring-Controlled visible date (ISO string). Also an output on navigation.
defaultVisibleDatestringtodayUncontrolled initial visible date.
eventColorstring'teal'Default event palette: red, pink, purple, indigo, blue, teal, green, lime, amber, orange, grey. Overridden per resource and per event.
eventCreationbool \dict-False disables creation; a dict sets `{interaction: 'click'\'double-click', duration}` (minutes).
resourceslist of dicts-Resources events can be assigned to (supports nested children).
visibleResourcesdict-Controlled resource visibility map {resourceId: bool}. Also an output.
defaultVisibleResourcesdict{}Uncontrolled initial resource visibility (all visible).
shouldEventRequireResourcebooleanFalseRequire every event to be assigned to a resource.
areEventsDraggablebooleanTrueAllow drag-to-reschedule.
areEventsResizablebool \'start' \'end'TrueAllow resize (or restrict to one edge).
canDragEventsFromTheOutsidebooleanFalseAllow external events to be dragged in.
canDropEventsToTheOutsidebooleanFalseAllow events to be dragged out of the calendar.
readOnlyboolean-Global read-only mode (disables create / drag / resize / dialog).
showCurrentTimeIndicatorbooleanTrueShow the current-time indicator line in time views.
scrollToCurrentTimebooleanFalseScroll day/week views so the current time is centered on first render.
displayTimezonestring'default'Render timezone: IANA name, 'default', 'locale', or 'UTC'. Render-only.
preferencesdict-Controlled user preferences {ampm, weekStartsOn, showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. Also an output.
defaultPreferencesdict-Uncontrolled initial preferences (same shape).
preferencesMenuConfigFalse \dict-Which items appear in the preferences menu, or False to hide it.
localeTextdict-Override UI label strings (partial map of translation keys).
eventDialogVariant'drawer' \'dialog''drawer'Event editor presentation: responsive drawer or floating dialog.
eventDialogTopOffsetnumber0Desktop drawer inset from the top (px) — e.g. your fixed header height.
responsiveSidePanelbooleanTrueSide panel opens on wide screens, collapses below mobileBreakpoint.
mobileBreakpointnumber768Width (px) below which the UI switches to its mobile layout.
heightnumber \string600Height of the wrapping container.
classNamestring-CSS class applied to the wrapping div.
sxdict-MUI sx styling object (object form only).

Note: EventCalendarPremium accepts all of the above plus licenseKey. EventTimeline accepts licenseKey, preset / defaultPreset / presets, and resourceColumnLabel in place of the view props; the radial charts take dataset, series, rotationAxis, radiusAxis, grid, axisHighlight, colors, hideLegend, margin, showToolbar, slotProps, and the clickData output. See the component docstrings for full details.

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: