# dash-mui-scheduler — MUI X scheduling for Dash — full corpus

## Access policy

- Terms: these documents are free to fetch. A free account unlocks any gated document.
- Identity: agents may present a key by appending `?key=<value>` to any document URL. Get one: https://2plot.ai
- Rate: prefer ONE `/llms-full.txt` fetch over N per-page fetches. On 429, honour `Retry-After` and back off exponentially.
- Coordination: start at https://2plot.dev/llms.txt — one index enumerates every site; do not rediscover the network by crawling it.
- Crawler policy (mirrors /robots.txt): allowed: GPTBot, ClaudeBot, CCBot, Google-Extended, FacebookBot, Omgili, ByteSpider, Amazonbot, Applebot-Extended, meta-externalagent, AI2Bot, Diffbot, Timpibot, ImagesiftBot, ChatGPT-User, Claude-User, Claude-SearchBot, PerplexityBot, OAI-SearchBot, Perplexity-User, Googlebot, Bingbot, Slurp, DuckDuckBot, GoogleOther, Google-InspectionTool, Storebot-Google, AdsBot-Google.
- Accounting: every document read is logged with the requesting vendor (verified against published IP ranges where the operator publishes them). See https://2plot.dev/llms.txt

> Event calendar, resource timeline and radial chart components for Plotly Dash, wrapping the MUI X Scheduler. By Pip Install Python.

Generated from 22 pages. The page index lives at https://muischeduler.2plot.dev/llms.txt and a compact briefing at https://muischeduler.2plot.dev/llms-small.txt; each page also serves its own document at `<page>/llms.txt`.

---

<!-- / — https://muischeduler.2plot.dev/llms.txt -->

# dash-mui-scheduler — MUI X scheduling for Dash

A Plotly Dash component library wrapping the MUI X Scheduler.

Install: `pip install dash-mui-scheduler`

Components: EventCalendar (day/week/month/agenda views, drag & resize, inline editing, resources), EventCalendarPremium (adds the RRULE recurrence engine), EventTimeline (resource lane view), and the polar charts RadialLineChart and RadialBarChart. Premium MUI X features activate via the `licenseKey` prop.

Docs pages (each has its own /<page>/llms.txt): /quickstart, /event-calendar, /playground, /events, /resources, /views, /navigation, /responsive, /drag-resize, /editing, /preferences, /recurrence, /event-timeline, /localization, /radial-lines, /radial-bars, /radial-axes.

---

<!-- /api — https://muischeduler.2plot.dev/api/llms.txt -->

# API reference

## dash_mui_scheduler

### EventCalendar

EventCalendar is a day / week / month / agenda calendar for displaying and
editing events, wrapping the MUI X `EventCalendar` (Community, MIT). Events
cross the Dash boundary as plain dicts with ISO-string dates; user edits
(create / move / resize / delete) round-trip back through the `events` prop.

| prop | type | default | description |
|---|---|---|---|
| `id` | string |  | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | bool |  | Allow drag-to-reschedule. Default true. |
| `areEventsResizable` | bool \| one of 'start', 'end' |  | Allow resize (bool, or restrict to "start"/"end"). Default true. |
| `canDragEventsFromTheOutside` | bool |  | Allow external events to be dragged in. Default false. |
| `canDropEventsToTheOutside` | bool |  | Allow events to be dragged out of the calendar. Default false. |
| `className` | string |  | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict |  | Uncontrolled initial preferences (same shape as `preferences`). |
| `defaultView` | one of 'day', 'week', 'month', 'agenda' |  | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string |  | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | object |  | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string |  | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | one of 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime' … |  | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | bool \| dict |  | Configures event creation. `false` disables it; `true` enables it with defaults; an object sets the interaction and default duration (minutes). |
| `eventDialogTopOffset` | number |  | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | one of 'drawer', 'dialog' | 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dict |  | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. |
| `height` | number \| string | 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict |  | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change",  event: the affected event (or null), event_timestamp}. |
| `localeText` | object |  | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number | 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict |  | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber,  isSidePanelOpen, showEmptyDaysInAgenda}. |
| `preferencesMenuConfig` | one of false \| dict |  | Which items appear in the preferences menu, or `false` to hide the menu. |
| `readOnly` | bool |  | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of object |  | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | bool | true | When true (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | bool | false | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default false. |
| `shouldEventRequireResource` | bool |  | Require every event to be assigned to a resource. Default false. |
| `showCurrentTimeIndicator` | bool |  | Show the current-time indicator line in time views. Default true. |
| `sx` | object |  | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | one of 'day', 'week', 'month', 'agenda' |  | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of one of 'day', 'week', 'month', 'agenda' |  | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string |  | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | object |  | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |

### EventCalendarPremium

EventCalendarPremium is the Event Calendar with the MUI X Premium recurrence
engine: events can carry an `rrule` (RFC-5545 string or object) and
`exDates`, and the edit dialog gains a Recurrence tab. Requires a MUI X
Premium license key (the `licenseKey` prop) to render without a watermark.

| prop | type | default | description |
|---|---|---|---|
| `id` | string |  | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | bool |  | Allow drag-to-reschedule. Default true. |
| `areEventsResizable` | bool \| one of 'start', 'end' |  | Allow resize (bool, or restrict to "start"/"end"). Default true. |
| `canDragEventsFromTheOutside` | bool |  | Allow external events to be dragged in. Default false. |
| `canDropEventsToTheOutside` | bool |  | Allow events to be dragged out of the calendar. Default false. |
| `className` | string |  | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict |  | Uncontrolled initial preferences. |
| `defaultView` | one of 'day', 'week', 'month', 'agenda' |  | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string |  | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | object |  | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string |  | Render timezone: IANA name, or "default"/"locale"/"UTC". Default "default". |
| `eventColor` | one of 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime' … |  | Default color palette for all events (overridable). Default "teal". |
| `eventCreation` | bool \| dict |  | Configures event creation. `false` disables it; `true` enables defaults; an object sets {interaction, duration (minutes)}. |
| `eventDialogTopOffset` | number |  | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | one of 'drawer', 'dialog' | 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dict |  | The events to render. Each event is a dict with at least `id`, `title`, `start`, `end` (ISO strings). Premium events may also carry `rrule` (recurrence) and `exDates`. INPUT + OUTPUT (round-trips on every change). |
| `height` | number \| string | 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict |  | Convenience OUTPUT describing the most recent change to `events`: {type, event, event_timestamp}. |
| `licenseKey` | string |  | MUI X Premium license key. Set once to remove the watermark and unlock Premium features (recurrence). Read from an environment variable on the server and pass it in. |
| `localeText` | object |  | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number | 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict |  | Controlled user preferences. Also an OUTPUT. |
| `preferencesMenuConfig` | one of false \| dict |  | Which items appear in the preferences menu, or `false` to hide it. |
| `readOnly` | bool |  | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of object |  | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | bool | true | When true (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | bool | false | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default false. |
| `shouldEventRequireResource` | bool |  | Require every event to be assigned to a resource. Default false. |
| `showCurrentTimeIndicator` | bool |  | Show the current-time indicator line in time views. Default true. |
| `sx` | object |  | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | one of 'day', 'week', 'month', 'agenda' |  | Controlled active view. Also an OUTPUT. |
| `views` | list of one of 'day', 'week', 'month', 'agenda' |  | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string |  | Controlled visible date (ISO string). Also an OUTPUT. |
| `visibleResources` | object |  | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |

### EventTimeline

EventTimeline is a resource-row, Gantt-style timeline that places events on
the row of the resource they are assigned to, across configurable zoom
presets, wrapping the MUI X `EventTimelinePremium`. Requires a MUI X Premium
license key (the `licenseKey` prop) to render without a watermark.

| prop | type | default | description |
|---|---|---|---|
| `id` | string |  | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | bool |  | Allow drag-to-reschedule allocations. Default true. |
| `areEventsResizable` | bool \| one of 'start', 'end' |  | Allow resize (bool, or restrict to "start"/"end"). Default true. |
| `canDragEventsFromTheOutside` | bool |  | Allow external events to be dragged in. Default false. |
| `canDropEventsToTheOutside` | bool |  | Allow events to be dragged out of the timeline. Default false. |
| `className` | string |  | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict |  | Uncontrolled initial preferences {ampm, weekStartsOn}. Default {ampm: true}. |
| `defaultPreset` | one of 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year' |  | Uncontrolled initial preset. Default "dayAndHour". |
| `defaultVisibleDate` | string |  | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | object |  | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string |  | Render timezone: IANA name, or "default"/"locale"/"UTC". Default "default". |
| `eventColor` | one of 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime' … |  | Default color palette for all events (overridable). Default "teal". |
| `eventCreation` | bool \| dict |  | Configures event creation. `false` disables it; `true` enables defaults; an object sets {interaction, duration (minutes)}. |
| `eventDialogTopOffset` | number |  | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height. Default 0. |
| `eventDialogVariant` | one of 'drawer', 'dialog' | 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive frosted-glass drawer — right-anchored on desktop, an 88%-height bottom sheet on mobile (below `mobileBreakpoint`). "dialog" keeps the library's floating dialog. |
| `events` | list of dict |  | Allocation bars. Each event needs `id`, `title`, `start`, `end` (ISO strings) and usually a `resource` (the row it sits on). INPUT + OUTPUT. |
| `height` | number \| string | 400 | Height of the wrapping container (the timeline fills it). Default 400. |
| `lastAction` | dict |  | Convenience OUTPUT describing the most recent change to `events`. |
| `licenseKey` | string |  | MUI X Premium license key (removes the watermark). |
| `localeText` | object |  | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number | 768 | Width (px) below which the editor uses its mobile layout. Default 768. |
| `preferences` | dict |  | Controlled preferences {ampm, weekStartsOn}. Also an OUTPUT. |
| `preset` | one of 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year' |  | Controlled zoom preset. Also an OUTPUT. One of "dayAndHour" \| "dayAndMonth" \| "dayAndWeek" \| "monthAndYear" \| "year". |
| `presets` | list of one of 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year' |  | The presets available (zoom levels offered). Default is all five, from most zoomed-in to most zoomed-out. |
| `readOnly` | bool |  | Global read-only mode. |
| `resourceColumnLabel` | string |  | Label shown in the resource column header. |
| `resources` | list of object |  | The resource rows. Each event's `resource` points to one of these ids. |
| `shouldEventRequireResource` | bool |  | Require every event to be assigned to a resource. Default true (timeline). |
| `showCurrentTimeIndicator` | bool |  | Show the current-time indicator line. Default true. |
| `sx` | object |  | MUI `sx` styling object applied to the timeline (object form only). |
| `visibleDate` | string |  | Controlled visible date (ISO string) — centers the window. Also OUTPUT. |
| `visibleResources` | object |  | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |

### RadialBarChart

RadialBarChart compares values along periodic categories using polar bars.
Series accept `stack` and `layout` ("vertical" = radius encodes the value,
"horizontal" = rotation encodes it); the band rotation axis accepts
`categoryGapRatio` / `barGapRatio`. Clicking reports the hit item via the
`clickData` output. Premium (preview) — set `licenseKey`.

| prop | type | default | description |
|---|---|---|---|
| `id` | string |  | The id used to identify this component in Dash callbacks. |
| `axisHighlight` | dict |  | Axis highlight behavior: {rotation, radius} where each is one of "none" \| "line" \| "band". Default depends on the layout. |
| `className` | string |  | CSS class applied to the wrapping div. |
| `clickData` | object |  | OUTPUT — set when the user clicks the chart. The clicked axis item and its series values, e.g. {dataIndex, axisValue, seriesValues, event_timestamp}. |
| `colors` | list of string |  | Color palette (list of CSS colors) used for the series. |
| `dataset` | list of object |  | Row-oriented data; series reference columns via `dataKey`. |
| `grid` | dict |  | Show background grid lines: {rotation: bool, radius: bool}. |
| `height` | number \| string | 400 | Chart height in px. Default 400. |
| `hideLegend` | bool |  | Hide the legend. |
| `licenseKey` | string |  | MUI X Premium license key (removes the watermark). |
| `margin` | number \| object |  | Margin around the plot — a number or {top,right,bottom,left}. |
| `radiusAxis` | list of object |  | Radius axis config — replaces the cartesian y-axis. A list of axis dicts. |
| `rotationAxis` | list of object |  | Rotation (angular) axis config — replaces the cartesian x-axis. A list of axis dicts. A band axis accepts `categoryGapRatio` / `barGapRatio`, e.g. [{scaleType:"band", data:["2020","2021"], categoryGapRatio:0.3, barGapRatio:0.1}]. |
| `series` | list of object |  | The bar series to plot. Each item is a dict, e.g. {dataKey, label, stack, layout:"vertical"\|"horizontal", color} or {data: [...], label, ...}. |
| `showToolbar` | bool |  | Show the default chart toolbar. |
| `skipAnimation` | bool |  | Skip the entrance animation. |
| `slotProps` | object |  | MUI X charts `slotProps` (plain-object form only), e.g. {"tooltip": {"trigger": "item"}}. |
| `sx` | object |  | MUI `sx` styling object (object form only). |
| `width` | number \| string |  | Chart width in px (defaults to filling the container). |

### RadialLineChart

RadialLineChart shows trends along periodic values using a polar line (or
area) plot. Pass `series` plus `rotationAxis` / `radiusAxis` to map your data
into polar coordinates; clicking the chart reports the hit axis item via the
`clickData` output. Premium (preview) — set `licenseKey` to remove the
watermark.

| prop | type | default | description |
|---|---|---|---|
| `id` | string |  | The id used to identify this component in Dash callbacks. |
| `axisHighlight` | dict |  | Axis highlight behavior: {rotation, radius} where each is one of "none" \| "line" \| "band". Default {rotation: "line"}. |
| `className` | string |  | CSS class applied to the wrapping div. |
| `clickData` | object |  | OUTPUT — set when the user clicks the chart. The clicked rotation-axis item and its series values, e.g. {dataIndex, axisValue, seriesValues, event_timestamp}. |
| `colors` | list of string |  | Color palette (list of CSS colors) used for the series. |
| `dataset` | list of object |  | Row-oriented data; series reference columns via `dataKey`. |
| `disableLineItemHighlight` | bool |  | Disable the per-item line highlight indicator. |
| `grid` | dict |  | Show background grid lines: {rotation: bool, radius: bool}. |
| `height` | number \| string | 400 | Chart height in px. Default 400. |
| `hideLegend` | bool |  | Hide the legend. |
| `licenseKey` | string |  | MUI X Premium license key (removes the watermark). |
| `margin` | number \| object |  | Margin around the plot — a number or {top,right,bottom,left}. |
| `radiusAxis` | list of object |  | Radius axis config — replaces the cartesian y-axis. A list of axis dicts, e.g. [{disableLine:true, minRadius:10, min:0, position:"none"}]. |
| `rotationAxis` | list of object |  | Rotation (angular) axis config — replaces the cartesian x-axis. A list of axis dicts, e.g. [{scaleType:"point", dataKey:"month", disableLine:true}]. |
| `series` | list of object |  | The line/area series to plot. Each item is a dict, e.g. {dataKey, label, curve, showMark, shape, area, closePath, stack,  highlightScope, color} or {data: [...], label, ...}. |
| `showToolbar` | bool |  | Show the default chart toolbar. |
| `skipAnimation` | bool |  | Skip the entrance animation. |
| `slotProps` | object |  | MUI X charts `slotProps` (plain-object form only). For example {"tooltip": {"trigger": "item"}} makes the tooltip follow the hovered mark/line instead of the whole rotation axis. |
| `sx` | object |  | MUI `sx` styling object (object form only). |
| `width` | number \| string |  | Chart width in px (defaults to filling the container). |

---

<!-- /changelog — https://muischeduler.2plot.dev/changelog/llms.txt -->

# Changelog

> Version history of dash-mui-scheduler. The timeline on this page is rendered from `CHANGELOG.md`, reproduced below.

---

All notable changes to **dash-mui-scheduler** — the Plotly Dash wrapper for the
MUI X Scheduler (EventCalendar, EventCalendarPremium, EventTimeline) and its
Radial chart components (RadialLineChart, RadialBarChart) — are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/).

> The petri-dish evolution game this component grew up alongside now lives in its
> own project (2plot.xyz); its 0.4.x–0.5.x history moved with it.

## [Unreleased]

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0.*

### Fixed
- **The machine-readable description of this site's API is readable again.**
  Asking the site for `/openapi.json` — the document that tells a program what
  this site's endpoints accept and return — returned an error instead of the
  document, and had done since the first release. A single internal route
  described itself in a way the document builder could not interpret, and one
  bad route makes the whole document fail rather than just its own entry. The
  interactive API pages at `/docs` and `/redoc` looked fine because they are
  empty shells that fetch this document in the browser: they loaded, and then
  showed nothing.

## [2026-09-05] — what the site records about you, a Legal section, and a health check that answers

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0. Shipped as `655dfde` and
live the same day.*

### Added

- **The health check now says which version of its documentation engine is
  running, and where this site's traffic record lives.** Two questions that
  could previously only be answered by asking the people who deployed it:
  which release of the AI-readability package a running site actually
  resolved, and whether its record of visits and reads is on storage that
  survives a deploy or on a disk that is wiped every time. Both are now
  readable from the outside, and the storage answer is measured rather than
  taken from the deployment's own description of itself.
- **A Terms of Use page and a Privacy page**, linked from the footer of every
  page and listed in the sidebar under Legal. The privacy page describes what
  this site actually records — it is written from the code that does the
  recording, and a test fails the build if the two ever disagree. Both pages
  are served identically to people and to AI assistants: a site cannot have
  two versions of its own privacy policy.
- **This site no longer stores visitors' IP addresses, and no longer sends
  them to anyone.** Two changes, both about the same thing. The address a
  visitor arrives with is used to tell one reader from another and is then
  discarded — what is kept is a scrambled, keyed fingerprint that cannot be
  turned back into an address. And the third-party lookup service this site
  used to ask "where is this address?" is gone entirely, not switched off:
  location now comes only from what the site's own CDN already reports, which
  is a country, and a city where the network operator has enabled it. Visitor
  and session counts are unaffected, including for days recorded before the
  change.

### Fixed

- **The operator traffic board now says when a crawler was refused, instead
  of counting it as a read.** Pages this site deliberately keeps out of the
  AI-readable corpus — the two owner-only pages — are refused when a crawler
  asks for them, and that refusal is the only evidence the exclusion works.
  It now appears as its own labelled row rather than being added to the
  served totals or dropped from the table.
- **A large crawler sweep no longer erases its own record.** The table of
  which AI crawler read which document was capped at twenty thousand rows, the
  same cap as the visit table — but a single crawler can pull thousands of
  documents in minutes, so a busy day silently deleted rows recording exactly
  the event the table exists to record, and the deletion looked like the
  crawler never having come. Read rows are now kept for the full retention
  window and nothing else. Visit numbers are unchanged.
- **A restriction published by the network hub is now honoured whatever its
  spelling.** If the hub restricted one of this site's pages using a different
  capitalisation than the site expected, the site did not recognise it and
  kept the page's machine-readable copy open. No page is restricted today, so
  nothing was exposed; the check is now spelling-insensitive.
- **The operator traffic board shows which kind of crawler each vendor is.**
  Every vendor's category — training crawler, search engine, traditional bot —
  was blank on the board and in the daily summary this site reports, because
  the documentation engine only started attaching it in a newer release than
  this site pins. The category now comes from that engine's own registry when
  it is not supplied, so it is right today rather than after the next upgrade.
- **The "Other Apps" menu opens from the keyboard.** It responded only to a
  mouse hover, so the one place this site lists its sibling sites could not be
  reached by anyone navigating with a keyboard — focus it, press Enter,
  nothing happened. Hovering still works exactly as before.
- **Links inside documentation text are underlined.** They were told apart
  from the surrounding prose by colour alone, which is invisible to a reader
  with low colour vision and to anyone reading in high-contrast mode. Links in
  the navigation and footer are unchanged.
- **Icon buttons are large enough to tap on a phone.** The header and footer
  controls were 34 pixels square, below the 44-pixel minimum recommended for
  touch. Desktop spacing is untouched.
- **The stylesheet and scripts are now cached by the browser.** They were
  served with no cache instruction at all, so every visitor re-fetched them on
  every page load and the CDN in front of the site stored nothing. They now
  last an hour, with a day of grace while a new copy is fetched in the
  background. Pages themselves are deliberately not cached.
- **Uptime monitors can check this site the way they normally do.** A
  lightweight "are you alive?" request — the default for most monitoring
  services, and the one this site's own deploy check uses — was answered with
  "method not allowed" on the health endpoint rather than a status. Full
  requests always worked, which is why nobody noticed.
- **The version answer reached only half of this site.** The two ways this
  site can be served describe their responses differently, and the stricter
  one silently discarded any field it had not been told about in advance —
  so the new version field existed everywhere except on the lane this site
  actually runs. It now keeps whatever the health check reports, including
  fields added later.


## [2026-09-01] — the read table drops internal traffic, and props a machine can read

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0. Shipped as `2cfc003` and
live on the same day.*

### Fixed
- **This site's own automated checks no longer appear in its traffic figures
  as if they were AI crawlers.** The hourly health check, the post-deploy
  test battery and continuous integration all identify themselves as internal
  machinery, and visitor counts have always ignored them — but the newer
  record of which documents get read did not, so the busiest "reader" of these
  docs was this site checking on itself.
- **Every component's props are now readable by AI assistants and search
  engines, not just in a browser.** The prop tables on each component page
  were drawn by JavaScript after the page loaded, so anything that reads the
  page as text — an AI assistant asking what `EventCalendar` accepts, a
  search crawler indexing the docs — saw the prose and not one of the 33
  props. All three server-rendered versions of every page now carry the full
  table.
- **The pre-deploy check now asks whether the admin pages are really hidden.**
  It checked the not-found page and nothing else, so the two owner-only pages
  were never tested on the live site — they were correctly hidden, but by
  luck rather than by check.
- **The changelog page reads correctly.** Release badges no longer print a
  stray "v" in front of a date, entries whose headings use a long dash are
  dated again, and bold text wrapped around `code` renders as bold instead of
  showing its asterisks.

### Added
- **A dated `/api` page.** Search engines are told when the component
  reference last actually changed, rather than being left to guess.
- **Sign-in-required pages are marked in the sidebar** with a small lock,
  instead of looking like any other page until you click one.
- **Long page names can carry a short sidebar label** — "Localization &
  Timezones" now reads simply "Localization" in the sidebar while keeping its
  full name everywhere else.

## [2026-08-30] — a sidebar you can find things in, and a record of who reads the docs

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0. Shipped as `4e8c00c` and
deployed the same day.*

### Added
- **A navigation bar you can find things in.** The sidebar is now Home ·
  Changelog · Scheduler · Radial Charts · API · Resources, in that order, and
  the order comes from each documentation page's own front matter rather than
  a list kept somewhere else — a new page appears where it says it belongs.
- **A `/changelog` page.** This file, as a timeline, with the same text served
  to AI readers.
- **An `/api` page: every component's props in one table.** Generated from the
  installed `dash_mui_scheduler` package itself, so it cannot fall behind the
  components actually shipped — `EventCalendar`, `EventCalendarPremium`,
  `EventTimeline`, `RadialLineChart` and `RadialBarChart`, with each prop's
  type, default and description.
- **A footer**, and an *Other Apps* menu in the top bar listing the other
  2plot sites — one place, from the network's own registry.
- **This site now keeps a record of which AI crawler read which document.**
  Every time the machine-readable corpus is served — `/llms.txt`, a page's
  own `llms.txt`, the sitemap — the site keeps a row saying who asked, what
  they got, how big it was, and whether the request came from an address the
  vendor publishes. A new operator page at `/admin/traffic` shows it back:
  vendor by day, vendor by document type, and the paths each one pulled, next
  to the visitor numbers for the same day.
- **The daily traffic summary this site reports to 2plot.ai gained a vendor
  breakdown**, sent only on days that actually had machine reads. Nothing
  that was already reported changed shape.

### Changed
- **One answer to "who is this?"** Bot identification now comes entirely from
  the `dash-improve-my-llms` vendor registry — the same registry that decides
  what `robots.txt` says — instead of a hand-kept list of user-agent strings.
  What the site SAYS about a crawler and what it COUNTS can no longer
  disagree.
- **The visitor numbers move on the day this ships, and the new ones are the
  true ones.** Human visits DROP and bot visits RISE, because clients that
  send no user agent at all, and library clients like `httpx` or
  `Go-http-client`, were being counted as people. Anthropic's ClaudeBot was
  also filed as a search crawler when it is a training crawler. Expect a
  visible step in the day-over-day chart; it is the number becoming correct.
- **Resources in the sidebar are now other people's docs only** — Dash Mantine
  Components and the MUI X Scheduler this library wraps. The Dash community
  forum link is gone, and the 2plot links moved to the new *Other Apps* menu
  and footer so nothing is listed twice.
- **Deployments now ship only what the test matrix has already passed.**
  Render builds a `release` branch that continuous integration writes after
  it goes green, so pushing to `main` proposes a deploy rather than
  performing one. A red build can no longer reach the live site while its own
  checks are still running.

### Fixed
- **The GitHub icon in the top bar goes to this project's repository.** It
  pointed at the author's profile page, so the one link a reader clicks to
  find the source took them somewhere they had to search from. The profile
  link now lives in the footer, where it belongs.
- **Long code samples no longer stretch the page sideways on a phone.** A code
  block inside a bulleted list or a quote widened the whole document instead
  of scrolling inside its own box.
- **The mobile navigation drawer always opens.** It was built the moment you
  first tapped the button, and that could leave it empty.
- **Pages without a table of contents now use the full width** instead of
  leaving an empty column down the right-hand side.
- **The sidebar no longer lists admin pages to people who cannot open them.**
  They were shown to everyone and then correctly refused; now they are shown
  only to the owner.
- **Screen readers can name the controls.** The sidebar button, the code
  "copy" button and the icon links all carry proper labels. (One remaining
  audit finding sits in the MUI calendar itself and is upstream.)
- **Link previews of this site no longer carry two conflicting image tags.**
  The page served to crawlers and social-media scrapers was given a preview
  image by the site on top of the one the documentation engine already
  emitted, leaving the scraper to pick one. Each tag is now added only if it
  is missing.
- **The pre-deploy check battery identifies itself as a browser when it is
  checking the browser's page.** It was being classified as a crawler and
  therefore inspecting the wrong document — reporting a missing app manifest
  on a site that has one. The fix was adopted across the other 2plot sites,
  which all had it.

## [2026-08-27] — the same icons for everyone, and a battery that waits

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0. Shipped as `0e00207` and
deployed the same day.*

### Fixed
- **The icons search engines were offered and the icons your browser was
  offered are the same set again.** Google was shown six sizes up to
  512×512; a browser loading the same page was shown three, topping out at
  180×180. Both heads now declare the identical set, so the icon a tab, a
  bookmark or a search result picks comes from one answer rather than two.
- **The post-deploy battery reads a healthy site as healthy from a Mac.**
  Its network calls now verify certificates through the same bundle the rest
  of the tooling uses; without it, every request failed in the TLS handshake
  and the report said the site was down when it was up. (Continuous
  integration never saw this — only laptops.)

### Changed
- **A sleeping site no longer fails its own deployment check.** The battery
  now waits for the host to answer before asserting anything about it, and
  retries a dropped connection or a momentary 5xx instead of recording a
  check that never ran. One unreachable host is reported once, not forty
  times.
- **Deploy checks that only ran in the cloud now run before the push.** The
  crawler-versus-browser identity comparison — the check that caught the
  icon defect above — runs against the app in the test suite, so this class
  of drift is caught locally instead of by a red deployment.

## [2026-08-26] — the kit every site shares, and one Python everywhere

*Documentation site and repository plumbing only; the `dash_mui_scheduler`
package is unchanged — PyPI still carries 1.0.0. Shipped as `b3ecf3f` and
deployed the same day.*

### Added
- **The repo now ships the development kit every 2plot site shares.** Anyone
  who clones it — or points an AI coding assistant at it — inherits the same
  project guide, the same three workflow skills, and the same sandbox
  settings the rest of the network runs, instead of nothing. A companion
  `DIVERGENCES.md` records, with reasons, the five places this repo
  deliberately differs from the template it was forked from, so a future
  sync cannot quietly "fix" a decision that was made on purpose.
- **The container now tells Docker whether it is healthy.** The image had no
  health probe at all, so the platform running it could only see "the
  process is alive", never "the site is answering" — and CI now fails the
  build if that probe is missing or broken, rather than trusting an
  external check that proves something else.
- **`/healthz` reports which Python is serving.** Combined with the checks
  below, an operator can tell from the outside whether the running image is
  the one this repo declares — a mismatch that was previously invisible.

### Changed
- **One Python, everywhere it is written down.** The image moved to 3.14
  on its own; the test matrix, the release lane and the deploy verifier were
  left behind on 3.12, so the version this site was tested and released on
  was not the version it shipped. They now all say 3.14, the supported
  window follows the image rather than sitting still, and a test holds every
  place the interpreter is written down to a single answer.
- **Automatic dependency PRs stop proposing minimum-version bumps.** Those
  minimums encode compatibility and security facts that a bump erases while
  adding nothing — the installer already picks the newest release above
  them. Security advisories are unaffected; they arrive on their own
  channel.
- **Version numbers in the site's AI/LLM home document are now derived, not
  typed.** The docs pages already worked this way; the home document was the
  one served surface that would have published whatever number someone last
  wrote by hand.

### Fixed
- **The sign-in card no longer advertises an AI assistant this site does not
  have.** It promised one to every visitor who hit a gated page — the
  network's credibility spent at its highest-intent moment.
- **A deploy that never happened can no longer be reported as verified.**
  When the deploy step is skipped, the verification job now stands down
  instead of certifying whichever release happened to still be live.
- **The live-site probe reuses one TLS context.** A source-level check now
  holds it there: the variant it replaced fails only on operators' own
  machines, where it reads as a broken sign-in wiring rather than a TLS
  problem.

## [2026-08-23] — one headline per page, and a health probe that answers

*Documentation site only; the `dash_mui_scheduler` package is unchanged —
PyPI still carries 1.0.0. Shipped as `d0f4068` and deployed the same
evening.*

### Fixed
- **Every page served crawlers two headlines instead of one.** The
  no-JavaScript block in the page shell opened with its own `<h1>`, and
  crawlers — which run no JavaScript — parse that block, so alongside each
  page's real heading they read a second, site-wide one competing with it.
  The block now starts a level down, and a new sweep checks every published
  page for exactly one headline rather than trusting the shell.
- **The AI/LLM copy of a page can no longer be broken by a code sample that
  quotes the docs' own syntax.** Inlining an example file is triggered by a
  directive; a directive shown *inside* a fenced code block — teaching the
  syntax rather than using it — used to be inlined anyway, which closed the
  fence early and turned the rest of the sample into headings. Fenced
  examples are now left as the documentation they are. No page in this repo
  had one yet; the fix lands before the first one does.
- **`/healthz` answers per request, and says more.** It now reports which
  satellite answered and — where the guardrail package supports it — whether
  geo-blocking is configured, how many countries are on the list (a count,
  never the list itself), and which country the request resolved to. Two
  things had to change for those answers to be true rather than merely
  present: the probe used to be computed once when the app started, so
  anything configured afterwards would have been reported wrong forever, and
  the country a request resolves to has to be read from *that* request —
  which the FastAPI build this site runs in production cannot do unless the
  route hands its own headers along.

### Changed
- **The AI/SEO package floor rises to dash-improve-my-llms 2.7.1.** It buys
  the fix for the duplicate headline the prerendered copy used to inject, a
  deduplicated `/llms.txt` link in the page footer, hardening against a page
  that merely *mentions* the prerender marker silently losing its prerender,
  and the llms.txt v2 discovery relations — `rel="alternate"` /
  `rel="describedby"` plus matching `Link` headers — that give an agent a
  machine-readable route from any page to that page's prose.
- **Automated dependency checks, aimed at the packages that matter.** The
  repo now runs a weekly check, and it proposes version upgrades only for
  the Dash/Plotly stack, grouped into a single review. The other floors in
  `requirements.txt` record minimum-compatibility facts (a CVE, a rendering
  guarantee) that a mechanical raise would erase, so they are left alone.
  Security updates arrive through their own channel and are unaffected.
- **The deploy gate waits long enough for the slowest build.** Raising a
  dependency floor deliberately rebuilds the whole image, so the most
  important deploy is also the slowest one; the wait is now sized for it,
  and a deploy that nothing actually triggered says so loudly instead of in
  passing.

## [2026-08-22] — the interactive sign-in gate, shipped dark

*Documentation site only; the `dash_mui_scheduler` package is unchanged —
PyPI still carries 1.0.0. Merged as PR #8 and deployed the same evening;
the Clerk 1.0.2 section below shipped earlier the same day and this
supersedes it.*

### Added
- **A sign-in gate, shipped dark.** Every documentation page can now be put
  behind a Clerk sign-in card, and none of them is: every tier ships
  `public`, so the site reads exactly as it did. What changed is that gating
  became a setting rather than a project — `PAGE_DEFAULT_TIER=auth` closes
  the interactive site, setting it back opens it, and neither touches a line
  of code. Pages can also declare their own tier in frontmatter.
- **A control board at `/admin/control-board`** for hiding or gating
  individual pages live, without a deploy. It fails closed: with the auth
  layer unavailable, nobody gets in rather than everybody.
- **Machine surfaces stay open on purpose.** `/llms.txt`, the tiered corpus
  documents and each page's own `/<page>/llms.txt` are governed by a second,
  independent axis — so gating the site for people never silently closes the
  window agents read through. A new `/api/agent-key` turns a signed-in
  browser session into a key that a copied `llms.txt` link carries with it,
  because the assistant you paste that link into has no cookie.
- **Real dates in the sitemap.** Every documentation page now declares the
  date its content actually last changed, taken from this repository's own
  history, and that date is published verbatim. The home page declares none:
  it is a standing index, and any single date would be a guess.

### Changed
- **The Clerk auth hook rises 1.0.2 → 1.0.5 — this one is a security fix.**
  On 1.0.2 signing out never told the server: the browser cleared its own
  session while the server kept honouring the identity cookie it had already
  issued, for up to seven days. Anyone who signed out on a shared machine
  stayed signed in as far as this site was concerned. 1.0.5 revokes properly,
  fixes the sign-in return trip (you now land back on the page you started
  on, signed in, instead of on a stale card), and — specific to this
  service's FastAPI backend — makes the authentication endpoints answerable
  at all: before it, every one of them rejected every request.
- **Sharper icons, and a complete set.** The favicon set was regenerated from
  this site's own artwork and now includes the sizes that were missing, so
  browsers, phone home screens and search results all resolve a real icon
  instead of falling back to a generic globe.
- **The mobile navigation is a real panel.** It now runs full height from
  under the header, with its own search field — previously phones had no way
  to jump to a page by name, only a long scroll, and the menu could not be
  closed with the button that opened it.
- **The dependency floors move up** to the versions this site actually
  needs: the SEO layer to the release that makes page text visible to
  non-JavaScript readers, and the component library to the release where the
  navigation panel renders as a panel.

### Fixed
- **The deploy check was grading the previous release.** After every push the
  post-deploy verification ran against whichever build happened to be
  answering — in practice the one being replaced. It now waits for the
  release it just shipped and verifies that.
- **A false "MCP unavailable" notice on every boot.** The startup log claimed
  the optional integration needed a newer Dash than the one running, on a
  version that was already new enough. It was wired to the wrong entry point
  and could never have worked; it is now wired correctly and silent unless
  enabled.
- **Stylesheet rules aimed at private internals of the component library**
  were removed — four of them, including two that had already caused visible
  layout bugs on sister sites. They would have broken silently, or started
  restyling something else entirely, on any future upgrade.

## [2026-08-22] — Clerk auth hook 1.0.2

*Shipped to the documentation site only; the `dash_mui_scheduler` package is
unchanged — PyPI still carries 1.0.0.*

### Changed
- **The vendored Clerk auth hook rises 0.9.1 → 1.0.2** ahead of the auth
  flip-on: the avatar menu no longer paints signed-out over a valid session
  (it lost a race against Dash mounting the menu), and 1.0.1's widened
  `clerk-backend-api` cap lets pip resolve `cryptography>=50.0.0`, clearing
  four published advisories (GHSA-537c-gmf6-5ccf, PYSEC-2026-3552/3553/3554)
  from the installed environment.

## [2026-08-18] — network instrumentation deploy

*Shipped to the documentation site only; the `dash_mui_scheduler` package is
unchanged — PyPI still carries 1.0.0.*

### Changed
- **Visitor analytics moved to the network's shared instrumentation** (the
  boilerplate 1.3.x trio). The ledger is now buffered, cross-process locked,
  pruned by a retention window, and written atomically — a busy hour no longer
  rewrites the whole file on every hit, and two workers can no longer silently
  overwrite each other's counts. The hourly rollup this site reports to
  2plot.ai is computed by the same shared code every satellite runs, so the
  network's numbers are finally measured with one rule. The Gen-0 reporter
  (`lib/traffic_report.py`) is retired.
- **A deploy can no longer erase the day's traffic.** The Render blueprint now
  attaches a persistent 1 GB disk and keeps the visitor ledger on it, so a
  mid-day deploy stops resetting the numbers the hub charts.
- **Documentation version claims are derived, never written.** Docs prose can
  state a package version as `{{VERSION:<distribution>}}` and the site
  substitutes the installed version at load — a hardcoded number that drifts
  from the shipped package can no longer appear on any page or llms.txt
  surface. (No page had one; the mechanism now guards all of them.)

### Added
- **Every docs page can declare who may read it.** Pages accept a `tier:` in
  their frontmatter (`public | auth | admin | hidden`), and the two corpus
  documents (`/llms-small.txt`, `/llms-full.txt`) take theirs from
  `LLMS_SMALL_TIER` / `LLMS_FULL_TIER`. Everything is and stays public —
  nothing enforces yet; this records the knobs the network's 402 experiment
  will read.
- **`.env.example`** — the app's whole environment surface documented in one
  file: what turns on when each variable is set, and what the app does
  without it.

### Fixed
- **The AI/SEO package floor rises to dash-improve-my-llms 2.5.1**, picking up
  the crawler-document fixes (the page `<title>` carrying the site name,
  per-page social images reaching crawlers, `/favicon.ico` answered with a
  redirect instead of the app shell).

## [1.0.0] - 2026-08-03

The component API has been stable since the first release and the docs site is
now on the network standard, so this graduates the package out of 0.x. Nothing
in the component itself changed — existing code keeps working untouched.

### Fixed
- **`pip install dash-mui-scheduler` no longer leans on a dependency it never
  declared.** Every component imports `typing_extensions`, which the package
  had been getting for free because current versions of Dash happen to install
  it. Anyone resolving to an older Dash could install this package successfully
  and then have it fail on import. It is now declared outright.

### Added
- **Releasing is now one push of a tag.** Publishing to PyPI used to be a manual
  upload from a laptop. Pushing a `v*` tag now runs the whole test matrix against
  that exact commit, builds the package, proves the built result is a working
  component library, publishes it, and opens a GitHub release whose notes are
  lifted from this file. No PyPI token is stored anywhere — PyPI verifies a
  short-lived identity token that GitHub mints for this repository alone, so
  there is no long-lived secret to leak or rotate. `RELEASING.md` documents the
  flow.
- **Guard rails against the releases that go wrong quietly.** A release stops
  before it can upload if the tag disagrees with the version the package
  declares, if the tagged commit never landed on `main`, or if this changelog
  has no section for the version being cut. The packaging is checked as well:
  both the wheel and the source archive must carry the built component bundle —
  a package that installs cleanly and then renders nothing is otherwise
  indistinguishable from a good one — and that check reads a clean install of
  the built artifact rather than the source tree sitting beside it.

## [0.1.1] - 2026-08-01

*Shipped to the documentation site; superseded on PyPI by 1.0.0, which carries
these changes.*

### Added
- **The docs site is now on the 2plot network standard**, the baseline proven on
  2plot.ai, 2plot.dev and the other satellite documentation sites:
  - **A test suite and CI/CD pipeline, from zero.** Every pull request now runs a
    secretless test suite on both the Flask and FastAPI builds, lints the code and
    the workflows themselves, builds the real production image, checks the shipped
    dependency versions inside it, boots it, and runs the same smoke battery that
    later checks the live site. Merging to `main` deploys and then verifies the
    live domain — waiting for *sustained* health before calling the deploy good.
  - **One identity on every surface.** The site now states what it is —
    *dash-mui-scheduler — MUI X scheduling for Dash* — identically in the browser
    tab, in search results, in shared-link previews, in the machine-readable
    `/llms.txt` index, in its app manifest and atop the README, and a test pins
    each surface so none of them can silently drift.
  - **A proper share card.** Links shared to Slack, Discord, X or LinkedIn will
    unfurl with a purpose-drawn 1200×630 card served from the network CDN (so a
    sleeping free-tier container never blanks a preview) instead of an upscaled
    favicon.
  - **The network bulletin.** The hub's tips and announcements now render in the
    documentation's llms.txt viewer once `NETWORK_BULLETIN_URL` is set on the
    service, so network-wide news reaches this site's readers without a deploy.
  - **Honest analytics.** The network's own machinery — health sweeps, smoke
    batteries, this site's calls to the hub — now identifies itself and is
    dropped from visitor analytics before it is ever written down — however the
    marker is capitalised — and every
    outbound call this site makes carries the same marker for the far side. The
    site reports to the hub under its one short id, `muischeduler`, everywhere.
  - `/healthz` on every backend (previously FastAPI-only), answering the hub's
    hourly health sweep and gating deploys.
  - **The cross-host network directory** — `/llms.txt` now lists the sibling
    documentation sites and the hub, so an agent landing here can discover the
    rest of the network.

- **Walkthrough video** — a video tour of the calendar, resource timeline, and
  radial charts now sits near the top of the Quickstart page **and on the
  documentation home page**, so a reader landing on the docs can watch it
  without going to GitHub first. The README header carries a clickable
  thumbnail linking to the same walkthrough. Both embeds use YouTube's
  no-cookie player, so nothing is set until you press play.
- **Richer search-result data** — the site now describes itself to search
  engines as what it is: an MIT-licensed Python source library with a
  repository, a PyPI download page, a version number read straight from the
  package, and the walkthrough video attached.
- **The docs site now reports its traffic to 2plot.ai**, the analytics home for
  the whole 2plot network. Once an hour it sends a signed daily rollup — page
  hits split human/bot, unique visitors, sessions, median session length, top
  pages and visitor countries — so the network dashboard shows how the
  documentation is actually being read. Reporting only happens when the shared
  network secret is configured; without it the site behaves exactly as before
  and makes no outbound calls.

### Changed
- **The maintainer's home is now [2plot.dev](https://2plot.dev).** Every link
  that pointed at the retired `pip-install-python.com` domain — in the README,
  the sidebar, the site's structured data and the cross-host directory — now
  points at 2plot.dev, and the README opens with the 2plot banner.
- **Fresher, safer dependencies.** The AI/SEO layer (`dash-improve-my-llms`) now
  installs from PyPI at ≥ 2.3.4 instead of a vendored 2.0.0 snapshot; the
  `gunicorn` web server is floored at ≥ 23 (clearing two request-smuggling
  CVEs its old pin was stuck on); and the optional Clerk auth package moves to
  0.9.1, the release that fixes the account chip on satellite domains.
- **The documentation has its own home: [muischeduler.2plot.dev](https://muischeduler.2plot.dev).**
  Everything the site publishes about itself — search-engine addresses, shared-link previews,
  the sitemap, the machine-readable pages — now points there, and the README and the PyPI
  listing send readers to the docs rather than back to the repository. The old
  `onrender.com` address keeps working and forwards to the new one, so existing links and
  bookmarks survive the move and search engines are told where the pages went.

### Fixed
- **The browser-tab and home-screen icons now show the actual logo.** Every
  favicon size, the app-install icons and the iOS home-screen icon are drawn
  fresh from the vector logo instead of the blurry upscale they were before.
- **A bad analytics-ledger path can no longer take the whole site down.**
  Pointing `TRAFFIC_ANALYTICS_FILE` at a persistent-disk path before the disk
  existed crashed every worker at boot, so the deploy never went live and the
  old build kept serving. The tracker now creates the directory when it can,
  and when the path is truly unwritable it disables itself with a clear log
  line — the site serves either way, and a test pins both behaviours.
- **Search engines were being told this site is a copy of a site that does not
  exist.** The page template still carried the URL it was built with
  (`dash-mui-scheduler.onrender.com`) rather than the address the docs actually
  live at, and it claimed that one address for all 17 pages at once — the
  fastest way for a site to fall out of the index entirely. Every page now
  declares its own correct address, kept in step as you navigate, and every
  link the site publishes about itself is built from a single setting.
- **Every page was announcing itself as the home page.** The template carried
  its own copy of the title, description and social tags, which overrode the
  per-page ones — so a search result or shared link for, say, *Recurrence*
  showed the site blurb instead of the page's. The per-page text now wins
  everywhere, including for search-engine and link-preview crawlers, and shared
  links unfurl with the project logo and the right page's title.
- **Visitor counts and countries are now measured at the edge, not at the
  proxy.** Behind Render/Cloudflare every request looked like it came from the
  same address, which collapsed all visitors into one and mislabelled where
  readers came from; the site now reads the forwarded client address and the
  edge country header. Visitor geography also no longer costs a lookup on the
  request path.
- **The visitor ledger no longer grows without limit** — it is capped (default
  20,000 hits) so a long-running deployment doesn't slow every page view down.

## [0.1.0] - 2026-07-16

First public release — the component library on PyPI (`pip install
dash-mui-scheduler`) and its documentation site, split out of the 2plot.ai
monolith.

### Added
- **Five Dash components** wrapping MUI X: `EventCalendar`,
  `EventCalendarPremium`, `EventTimeline`, `RadialLineChart` and
  `RadialBarChart` — with recurrence, drag & resize, inline editing, resources,
  preferences, localization/timezones, and MUI X Premium features via a
  `licenseKey` prop.
- **The full 2plot network in the sidebar** — the "2plot network" section now
  lists every application and tool in the network: 2plot.xyz (the game),
  2plot.ai (the hub), 2plot.media (videography), PiratesBargain (commerce), and
  ai-agent.buzz (the infinite AI canvas).
- **`skills.md`** — an agent-facing usage guide for the component library
  (component picker, the events/`lastAction` data boundary, prop cheat sheets,
  radial chart patterns, gotchas). Ships inside the PyPI source distribution
  alongside the README.
- **Documentation site** with live examples: 14 scheduler pages (Quickstart,
  Event Calendar, Playground, Events, Resources, Views, Navigation, Responsive,
  Drag & Resize, Editing, Preferences, Recurrence, Event Timeline,
  Localization & Timezones) and 3 radial chart pages, each with an LLM-friendly
  `/llms.txt` mirror, sitemap, and social cards.
- **Pluggable Dash 4.2 backends** — runs on Flask or FastAPI (`DASH_BACKEND`);
  the FastAPI build ships `/healthz`, `/api/backend`, `/api/pages`, and Swagger UI.
- **Optional sign-in, off by default** — the site runs fully public; setting the
  Clerk environment variables later flips on 2plot-network sign-in with no code
  change.

### Changed
- **Professional README** — rewritten with a centered header, badges, community
  links, and a fuller tour (overview, quick start, data boundary, Premium
  licensing, recurrence and radial chart samples, selected prop reference).
- **Repo hygiene** — the local `.claude/` agent scaffold is no longer tracked
  in git.

### Fixed
- **Chart image export no longer fails.** Exporting a radial chart to PNG (or
  printing it) made the bundle fetch a code-split chunk the package never
  registered with Dash, so the request came back a 500 and the export died. The
  chunk is now registered and served on demand.
- **The header no longer types out "Petri Dish."** A leftover animation script
  from the departed evolution game overwrote the "dash-mui-scheduler" brand text
  in the header on every page load.
- **Premium license key now reaches every live example.** The docs read
  `MUI_X_LICENSE_KEY`, but the environment only provided `MUI_PRO_API_KEY` — so
  every Premium demo (recurrence, timeline, radial charts) rendered the
  "Missing license key" watermark. `.env` now aliases the key and the Render
  blueprint supplies `MUI_X_LICENSE_KEY` directly.
- **Loading screen no longer 404s** — its logo still pointed at the departed
  2plot artwork; it now uses the dash-mui-scheduler logo, and the console is
  clean on every docs page.
- **Render blueprint URL corrected** — `APP_BASE_URL` said
  `dash-mui-scheduler.onrender.com` while the service name resolves to
  `dash-mui-scheduler-docs.onrender.com`; sitemap/llms/social links would have
  pointed at a dead host.

---

<!-- /drag-resize — https://muischeduler.2plot.dev/drag-resize/llms.txt -->

# Drag & Resize

> Toggle and fine-tune drag-and-drop and resizing in the dash-mui-scheduler EventCalendar, including per-event overrides and external drag.

---



### Drag & Resize

The MUI X Scheduler lets users reschedule an event by dragging it to a new
time, and change its duration by dragging its edges. `dms.EventCalendar`
exposes both behaviours through props and they are **on by default** — a
calendar with no callbacks at all is already fully interactive. Every move or
resize is written back to the `events` array (input *and* output) and announced
through `lastAction`, so all you have to do is read those props.


    `events` is both an input and an output. When the user drags or resizes an
    event, the component sends the *entire* new array back to Dash. Dates are
    plain ISO strings (e.g. `"2024-01-15T10:00:00"`), never Python `datetime`
    objects. `lastAction` is output-only and reports the most recent
    interaction — its `type` is one of `create`, `update`, `delete`, `move`,
    `resize`, or `change`.

### Dragging — `areEventsDraggable`

`areEventsDraggable` (bool, default `True`) controls whether events can be
picked up and dropped onto a new time or day. Set it to `False` to pin every
event in place while still allowing creation and deletion. Dragging an event
emits a `lastAction` of type `move`.

### Resizing — `areEventsResizable`

`areEventsResizable` (default `True`) controls whether an event's edges can be
dragged to change its duration. It accepts:

- `True` — both the start and end edges are resizable.
- `False` — the event keeps a fixed duration.
- `'start'` — only the leading (start) edge can be dragged.
- `'end'` — only the trailing (end) edge can be dragged.

Resizing emits a `lastAction` of type `resize`.

### Live toggles

The example below wires a `dmc.Switch` to `areEventsDraggable` and a
`dmc.SegmentedControl` to `areEventsResizable`, so you can feel the difference
between each mode. A read-out below the calendar shows the latest `lastAction`,
which makes it easy to tell a **move** apart from a **resize**.



```python
# File: docs/drag_resize/drag_resize_toggles.py

"""Toggle drag-and-drop and resizing on an EventCalendar at runtime.

A `dmc.Switch` drives `areEventsDraggable` and a `dmc.SegmentedControl` drives
`areEventsResizable` (True / False / 'start' / 'end'). The read-out shows the
latest `lastAction` so you can tell a move apart from a resize.
"""
from dash import html, Input, Output, callback
import dash_mantine_components as dmc
import dash_mui_scheduler as dms

EVENTS = [
    {"id": 1, "title": "Team Meeting", "start": "2024-01-15T10:00:00", "end": "2024-01-15T11:00:00", "color": "blue"},
    {"id": 2, "title": "Project Review", "start": "2024-01-16T14:00:00", "end": "2024-01-16T15:30:00", "color": "purple"},
    {"id": 3, "title": "Client Call", "start": "2024-01-17T09:00:00", "end": "2024-01-17T10:00:00", "color": "green"},
    {"id": 4, "title": "Locked: Sprint Demo", "start": "2024-01-18T13:00:00", "end": "2024-01-18T14:00:00",
     "color": "grey", "draggable": False, "resizable": False},
]

component = dmc.Stack(
    [
        dmc.Group(
            [
                dmc.Switch(
                    id="drag_resize-draggable-switch",
                    label="areEventsDraggable",
                    checked=True,
                ),
                dmc.SegmentedControl(
                    id="drag_resize-resizable-control",
                    data=[
                        {"label": "Both", "value": "true"},
                        {"label": "Off", "value": "false"},
                        {"label": "Start edge", "value": "start"},
                        {"label": "End edge", "value": "end"},
                    ],
                    value="true",
                ),
            ],
            align="center",
            gap="lg",
        ),
        dms.EventCalendar(
            id="drag_resize-cal",
            events=EVENTS,
            defaultVisibleDate="2024-01-15",
            areEventsDraggable=True,
            areEventsResizable=True,
            height=600,
        ),
        dmc.Code(id="drag_resize-readout", block=True),
    ],
    gap="md",
)


@callback(
    Output("drag_resize-cal", "areEventsDraggable"),
    Output("drag_resize-cal", "areEventsResizable"),
    Input("drag_resize-draggable-switch", "checked"),
    Input("drag_resize-resizable-control", "value"),
)
def set_interactions(draggable, resizable):
    # Map the SegmentedControl string back to the prop's bool/'start'/'end' shape.
    resizable_value = {"true": True, "false": False, "start": "start", "end": "end"}[resizable]
    return draggable, resizable_value


@callback(
    Output("drag_resize-readout", "children"),
    Input("drag_resize-cal", "lastAction"),
)
def show_last_action(last_action):
    if not last_action:
        return "Drag an event to move it, or drag its edge to resize it — lastAction shows up here."
    event = last_action.get("event") or {}
    return (
        f"lastAction.type: {last_action.get('type')}\n"
        f"event: {event.get('title', '-')}  "
        f"{event.get('start', '-')} -> {event.get('end', '-')}"
    )
```


### Per-event and per-resource overrides

The calendar-wide props set the default, but individual events and resources
can opt in or out:

- **Per event:** add `draggable` (bool) and/or `resizable` (`bool | 'start' | 'end'`)
  to an event dict. A locked event might use `{"draggable": False, "resizable": False}`
  even while the rest of the calendar stays movable. (`readOnly: True` locks an
  event completely.)
- **Per resource:** a resource dict accepts `areEventsDraggable`,
  `areEventsResizable`, and `areEventsReadOnly`, applying that policy to every
  event assigned to the resource.

These overrides are read straight off the `events` / `resources` data — there
is no extra prop to enable them.

### External drag — drag in and out of the calendar

Two props control whether events can cross the calendar's boundary:

- `canDragEventsFromTheOutside` (bool, default `False`) — allow an item from
  outside the calendar to be dropped onto it as a new event.
- `canDropEventsToTheOutside` (bool, default `False`) — allow an event to be
  dragged out of the calendar (for example, to remove it or hand it to another
  surface).

These enable the cross-boundary drag *targets*; wiring an actual external
drag source (a palette of draggable items elsewhere on the page) is left to
your own layout. Both default to `False`, so the calendar is self-contained
until you opt in.


    The MUI X Scheduler is in beta. Drag-and-drop and resizing work, but
    finer external-drag integration may change in future releases.

### EventCalendar props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /drag-resize*

---

<!-- /editing — https://muischeduler.2plot.dev/editing/llms.txt -->

# Editing

> Create and edit events in dash-mui-scheduler — the edit dialog, event creation config, and read-only calendars or events.

---



### Editing events

`dms.EventCalendar` is interactive out of the box. With no callback at all, a
user can create, drag, resize, and delete events — Dash's own `setProps`
round-trip persists each change into the `events` prop. The component treats
`events` as **both input and output**: on every create, move, resize, or
delete it writes the *full new array* back. Dates inside `events` are always
**ISO strings** (e.g. `"2024-01-15T10:00:00"` for wall time, or a trailing
`Z` for UTC) — never Python `datetime` objects.

Add a callback only when you want to *observe* what changed: read `events` for
the new state, or read `lastAction` — an output-only dict shaped
`{type, event, event_timestamp}` where `type` is one of `create`, `update`,
`delete`, `move`, `resize`, or `change`.

### The edit dialog

Clicking an existing event opens the built-in edit dialog where the title,
time, color, and description can be changed; saving writes the updated event
back into `events`. Interacting with an empty slot starts event creation (see
below). You do not wire any of this up yourself — it is part of the component.


    The MUI X Scheduler is in beta. The edit dialog's exact fields and styling
    may change in future releases. The Dash data boundary described here
    (`events` in/out, `lastAction`, ISO strings) is stable.

### Configuring event creation

The `eventCreation` prop controls how new events are drawn:

- `eventCreation=True` (default) — creation is enabled with the default gesture.
- `eventCreation=False` — creation is **disabled** entirely; users can still
  move or resize existing events (unless those are locked too).
- `eventCreation={"interaction": "double-click", "duration": 45}` — an object
  where `interaction` is `"click"` or `"double-click"`, and `duration` is the
  new event's length in **minutes**.

In the example below, double-clicking an empty slot creates a 45-minute event.
The `lastAction` output is echoed underneath so you can see each change.



```python
# File: docs/editing/event_creation.py

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

# Dates are ISO strings (wall time, no "Z"). The component writes the
# full new array back to `events` on every create / move / resize / delete.
events = [
    {
        "id": "kickoff",
        "title": "Project kickoff",
        "start": "2024-01-15T10:00:00",
        "end": "2024-01-15T11:30:00",
        "color": "indigo",
    },
    {
        "id": "review",
        "title": "Design review",
        "start": "2024-01-17T14:00:00",
        "end": "2024-01-17T15:00:00",
        "color": "teal",
    },
]

component = dmc.Stack(
    [
        dmc.Text(
            "Double-click an empty slot to create a 45-minute event. "
            "Existing events stay draggable and resizable.",
            size="sm",
            c="dimmed",
        ),
        dms.EventCalendar(
            id="editing-creation-cal",
            events=events,
            # interaction: 'click' | 'double-click'; duration is in minutes.
            eventCreation={"interaction": "double-click", "duration": 45},
            defaultVisibleDate="2024-01-15",
            defaultView="week",
            height=600,
        ),
        dmc.Code(id="editing-creation-action", block=True),
    ],
    gap="sm",
)


@callback(
    Output("editing-creation-action", "children"),
    Input("editing-creation-cal", "lastAction"),
)
def show_action(last_action):
    if not last_action:
        return "No action yet — double-click an empty slot to create an event."
    event = last_action.get("event") or {}
    return f"{last_action.get('type')}: {event.get('title', '(none)')}"
```


### Read-only: whole calendar vs. one event

Locking works at two levels:

- **Globally** — `readOnly=True` on the calendar disables all creating,
  moving, resizing, and deleting. The calendar becomes a pure display.
- **Per event** — add `"readOnly": True` to a single event dict to freeze
  just that event while the rest of the calendar stays editable.

The example shows both: a fully read-only calendar on the left, and an
editable calendar on the right where only the red all-hands event is locked.



```python
# File: docs/editing/editing_readonly.py

import dash_mantine_components as dmc
import dash_mui_scheduler as dms

# Even with readOnly=False, one event is locked via its own `readOnly` key.
events = [
    {
        "id": "standup",
        "title": "Daily standup",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T09:30:00",
        "color": "blue",
    },
    {
        "id": "frozen",
        "title": "Locked: company all-hands (readOnly)",
        "start": "2024-01-16T13:00:00",
        "end": "2024-01-16T14:00:00",
        "color": "red",
        "readOnly": True,
    },
    {
        "id": "demo",
        "title": "Sprint demo",
        "start": "2024-01-18T11:00:00",
        "end": "2024-01-18T12:00:00",
        "color": "green",
    },
]

component = dmc.Stack(
    [
        dmc.Text(
            "Left: a fully read-only calendar (readOnly=True) — no create, move, "
            "resize, or delete. Right: an editable calendar where only the red "
            "all-hands event is locked via its per-event readOnly key.",
            size="sm",
            c="dimmed",
        ),
        dmc.SimpleGrid(
            cols={"base": 1, "md": 2},
            spacing="md",
            children=[
                dms.EventCalendar(
                    id="editing-readonly-cal",
                    events=events,
                    readOnly=True,
                    defaultVisibleDate="2024-01-15",
                    defaultView="week",
                    height=580,
                ),
                dms.EventCalendar(
                    id="editing-perevent-cal",
                    events=events,
                    defaultVisibleDate="2024-01-15",
                    defaultView="week",
                    height=580,
                ),
            ],
        ),
    ],
    gap="sm",
)
```



    To keep events editable but stop new ones from being drawn, set
    `eventCreation=False` rather than `readOnly=True`. `readOnly` locks
    everything; `eventCreation=False` locks creation alone.

### EventCalendar props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /editing*

---

<!-- /event-calendar — https://muischeduler.2plot.dev/event-calendar/llms.txt -->

# Event Calendar

> The EventCalendar component — a day/week/month/agenda calendar whose events round-trip across the Dash boundary as plain dicts with ISO-string dates.

---



### Overview

`dms.EventCalendar` wraps the **MUI X Event Calendar** (Community, MIT — no
license key required). It renders a day / week / month / agenda calendar that
your users can read and edit: create, drag-to-reschedule, resize and delete
events directly in the UI.

```python
import dash_mui_scheduler as dms
```

The calendar is **interactive on its own**. A standalone `dms.EventCalendar`
with no callback already persists every create / move / resize / delete,
because each edit round-trips through Dash's own `setProps`. You only add a
callback when you want to *observe* the result — to render the current events,
react to the last action, or sync `view` / `visibleDate` elsewhere.


    The MUI X Scheduler is in beta. APIs may shift between releases. The props
    documented here are the ones the Dash wrapper exposes today.

### The data boundary

Everything the calendar shows and everything the user changes crosses the Dash
boundary as **plain, JSON-serializable values** — no Python `datetime` objects,
no functions. Dates are **ISO strings**.

#### Events in, events out

`events` is a list of dicts and is **both an input and an output**. You pass
events in; the calendar writes the *full new array* back to the same prop on
every create, move, resize and delete. Read it from a callback to keep your
own state in sync.

Each event dict has four **required** keys:

| Key     | Type        | Notes                                                        |
| ------- | ----------- | ------------------------------------------------------------ |
| `id`    | str \| int  | Unique within the array.                                     |
| `title` | str         | Shown on the event block.                                    |
| `start` | ISO str     | e.g. `"2024-01-15T09:00:00"`. A trailing `Z` means UTC.      |
| `end`   | ISO str     | Same format as `start`.                                      |

Common **optional** keys: `description`, `resource` (a resource id), `allDay`,
`color` (one of the 11 palette names), `timezone` (IANA name), `draggable`,
`resizable` (`bool | 'start' | 'end'`), `readOnly` and `className`. On
`EventCalendarPremium`, events may also carry `rrule` and `exDates` for
recurrence.


    Always pass dates as strings. `"2024-01-15T10:00:00"` (no `Z`) is *wall
    time*; `"2024-01-15T10:00:00Z"` is a UTC instant. `displayTimezone`
    controls only how events are *rendered* — it never rewrites their data.

#### lastAction (output only)

`lastAction` is a convenience **output** describing the most recent change to
`events`. It is a dict:

```python
{"type": "move", "event": { ... }, "event_timestamp": 1705312800000}
```

`type` tells you what just happened:

| `type`   | When it fires                                            | `event`            |
| -------- | ------------------------------------------------------- | ------------------ |
| `create` | A new event was added in the UI.                        | the new event      |
| `update` | An event's fields were edited (e.g. via the dialog).    | the updated event  |
| `delete` | An event was removed.                                   | the removed event  |
| `move`   | An event was dragged to a new time / day.               | the moved event    |
| `resize` | An event's start or end edge was dragged.               | the resized event  |
| `change` | A bulk / unclassified change to the array.              | `None`             |

`event_timestamp` is an epoch-milliseconds integer — use it as a callback
trigger that changes on every action, even when the same event is touched
twice in a row.

#### Visible date and other controlled props

`visibleDate` is an ISO **date** string (e.g. `"2024-01-15"`) and is
controlled both in and out — it is written back when the user navigates. Its
uncontrolled twin is `defaultVisibleDate`. Several props follow the same
controlled / uncontrolled pattern: `view` / `defaultView`,
`preferences` / `defaultPreferences`, and
`visibleResources` / `defaultVisibleResources`.


    The calendar follows the surrounding Mantine color scheme. Do not write a
    theme callback — toggle your app's color scheme and the calendar follows.

### A controlled calendar

The example below keeps the calendar uncontrolled (it manages its own edits)
and adds a single callback that mirrors the two output sides of the boundary:
the live `events` array and the latest `lastAction`, both rendered as JSON.

Create, drag, resize or delete an event in the calendar and watch both panels
update. The events pinned around **2024-01-15** are visible on load because the
calendar starts at `defaultVisibleDate="2024-01-15"`.



```python
# File: docs/event_calendar/overview.py

"""A controlled EventCalendar: the calendar is fully interactive on its own,
and a callback mirrors the live `events` array and the most recent
`lastAction` back to the page as JSON."""

import json

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

# Events cross the Dash boundary as plain dicts with ISO-string dates.
# "...:00" with no "Z" is wall time; a trailing "Z" would mean a UTC instant.
events = [
    {
        "id": "kickoff",
        "title": "Project kickoff",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:30:00",
        "color": "blue",
        "description": "Align on scope for the quarter.",
    },
    {
        "id": "design-review",
        "title": "Design review",
        "start": "2024-01-16T13:00:00",
        "end": "2024-01-16T14:00:00",
        "color": "purple",
    },
    {
        "id": "team-lunch",
        "title": "Team lunch",
        "start": "2024-01-17T12:00:00",
        "end": "2024-01-17T13:00:00",
        "color": "green",
    },
    {
        "id": "retro",
        "title": "Sprint retro",
        "start": "2024-01-18T16:00:00",
        "end": "2024-01-18T17:00:00",
        "color": "amber",
    },
]

component = dmc.Stack(
    [
        dms.EventCalendar(
            id="event_calendar-overview-cal",
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            eventColor="teal",
            height=600,
        ),
        dmc.Group(
            [
                dmc.Stack(
                    [
                        dmc.Text("Live events array", fw=600, size="sm"),
                        dmc.Code(
                            id="event_calendar-overview-events",
                            block=True,
                            style={"maxHeight": 320, "overflow": "auto"},
                        ),
                    ],
                    gap=4,
                    style={"flex": 1, "minWidth": 280},
                ),
                dmc.Stack(
                    [
                        dmc.Text("Most recent lastAction", fw=600, size="sm"),
                        dmc.Code(
                            id="event_calendar-overview-action",
                            block=True,
                            style={"maxHeight": 320, "overflow": "auto"},
                        ),
                    ],
                    gap=4,
                    style={"flex": 1, "minWidth": 280},
                ),
            ],
            grow=True,
            align="flex-start",
        ),
    ],
    gap="md",
)


@callback(
    Output("event_calendar-overview-events", "children"),
    Output("event_calendar-overview-action", "children"),
    Input("event_calendar-overview-cal", "events"),
    Input("event_calendar-overview-cal", "lastAction"),
)
def show_boundary(current_events, last_action):
    """Render the two OUTPUT sides of the boundary as pretty JSON."""
    events_json = json.dumps(current_events or [], indent=2)
    action_json = json.dumps(last_action or {}, indent=2)
    return events_json, action_json
```


### Props

Full prop reference for `dms.EventCalendar`. Use only these props — do not
invent new ones.


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /event-calendar*

---

<!-- /event-timeline — https://muischeduler.2plot.dev/event-timeline/llms.txt -->

# Event Timeline

> Build a resource-row, Gantt-style timeline in dash-mui-scheduler with EventTimeline — resources as rows, multi-day allocation bars and zoom presets.

---



### Event Timeline (Premium)

`dms.EventTimeline` is a resource-row, Gantt-style timeline. Where the calendar
lays events out on a day/week/month grid, the timeline turns each **resource
into a row** and draws every event as a horizontal allocation bar on its row,
across a configurable zoom `preset`. It wraps the MUI X `EventTimelinePremium`
and, like every component here, is currently **beta**.


    `EventTimeline` requires a MUI X Premium license key. Pass it via
    `licenseKey=os.environ.get("MUI_X_LICENSE_KEY", "")`. Without a valid key
    the timeline still renders and stays fully interactive, but a MUI watermark
    is drawn over it. That is expected — supply a real key to remove it.

The data boundary is the same as the calendar. `events` is a list of plain
dicts and is **both input and output**: the component writes the full array
back on every create, move, resize or delete. Dates are ISO **strings** (for
example `"2024-01-15T09:00:00"` for wall time, or a trailing `Z` for UTC),
never Python `datetime` objects. The read-only `lastAction` output is
`{type, event, event_timestamp}`, where `type` is one of `create`, `update`,
`delete`, `move`, `resize` or `change`.

### Resources as rows

A timeline needs `resources` — they are the rows. Each resource is a dict with
at least an `id` and `title`, plus an optional `eventColor`:

```python
resources = [
    {"id": "team-a", "title": "Team A", "eventColor": "blue"},
    {"id": "team-b", "title": "Team B", "eventColor": "green"},
]
```

Each event names its row through the `resource` key, which must match one of
the resource ids. On the timeline events should almost always have a
`resource` — `shouldEventRequireResource` defaults to `True` here. The column
that lists the row labels is titled with `resourceColumnLabel` (for example
`"Team"`). The example below places several multi-day allocation bars across
three rows and opens at the `dayAndWeek` zoom level.



```python
# File: docs/event_timeline/timeline_basic.py

import os

from dash import html
import dash_mui_scheduler as dms

# On a timeline, resources are the ROWS. Each event sits on the row whose id
# matches its `resource` key. Resources carry their own eventColor.
resources = [
    {"id": "team-a", "title": "Team A", "eventColor": "blue"},
    {"id": "team-b", "title": "Team B", "eventColor": "green"},
    {"id": "team-c", "title": "Team C", "eventColor": "orange"},
]

# Allocation bars span days. Dates are ISO strings (never Python datetime),
# and `events` is both input and output — the component writes the full array
# back on every create, move, resize or delete.
events = [
    {
        "id": "alloc-1",
        "title": "Discovery",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-17T17:00:00",
        "resource": "team-a",
    },
    {
        "id": "alloc-2",
        "title": "Build phase",
        "start": "2024-01-18T09:00:00",
        "end": "2024-01-23T17:00:00",
        "resource": "team-a",
    },
    {
        "id": "alloc-3",
        "title": "API integration",
        "start": "2024-01-16T09:00:00",
        "end": "2024-01-20T17:00:00",
        "resource": "team-b",
    },
    {
        "id": "alloc-4",
        "title": "QA & hardening",
        "start": "2024-01-22T09:00:00",
        "end": "2024-01-25T17:00:00",
        "resource": "team-b",
    },
    {
        "id": "alloc-5",
        "title": "Launch prep",
        "start": "2024-01-19T09:00:00",
        "end": "2024-01-24T17:00:00",
        "resource": "team-c",
    },
]

component = html.Div(
    dms.EventTimeline(
        id="event_timeline-basic",
        licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
        events=events,
        resources=resources,
        resourceColumnLabel="Team",
        defaultPreset="dayAndWeek",
        defaultVisibleDate="2024-01-15",
        height=400,
    )
)
```


### Zoom presets

A preset controls how much time one screen of the timeline spans and how the
header is divided. There are five: `dayAndHour` (the default), `dayAndMonth`,
`dayAndWeek`, `monthAndYear` and `year`. Two props drive this:

- `defaultPreset` / `preset` — the active zoom level. `defaultPreset` is
  uncontrolled (set it once at load); `preset` is controlled **in + out**, so a
  callback can both set it and read the user's changes back.
- `presets` — the list of presets offered in the timeline's own zoom switcher.

To drive the zoom from your own UI, wire a `dmc.SegmentedControl` to the
`preset` prop. Because `preset` is controlled in + out, the callback below sets
the zoom whenever the segmented control changes:

```python
@callback(
    Output("event_timeline-presets", "preset"),
    Input("event_timeline-presets-control", "value"),
)
def set_preset(value):
    return value
```



```python
# File: docs/event_timeline/timeline_presets.py

import os

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

# Rows.
resources = [
    {"id": "mixer", "title": "Mixer", "eventColor": "purple"},
    {"id": "studio", "title": "Studio", "eventColor": "teal"},
]

# A few multi-day allocations so the effect of each zoom preset is visible.
events = [
    {
        "id": "preset-1",
        "title": "Album mixdown",
        "start": "2024-01-15T10:00:00",
        "end": "2024-01-19T18:00:00",
        "resource": "mixer",
    },
    {
        "id": "preset-2",
        "title": "Tracking sessions",
        "start": "2024-01-16T09:00:00",
        "end": "2024-01-22T20:00:00",
        "resource": "studio",
    },
    {
        "id": "preset-3",
        "title": "Mastering",
        "start": "2024-01-23T10:00:00",
        "end": "2024-01-25T16:00:00",
        "resource": "mixer",
    },
]

# `preset` is controlled in + out: the SegmentedControl drives the zoom level.
# The five presets are dayAndHour, dayAndMonth, dayAndWeek, monthAndYear, year.
presets = ["dayAndHour", "dayAndWeek", "dayAndMonth", "monthAndYear", "year"]

component = dmc.Stack(
    [
        dmc.SegmentedControl(
            id="event_timeline-presets-control",
            data=[
                {"label": "Day / Hour", "value": "dayAndHour"},
                {"label": "Day / Week", "value": "dayAndWeek"},
                {"label": "Day / Month", "value": "dayAndMonth"},
                {"label": "Month / Year", "value": "monthAndYear"},
                {"label": "Year", "value": "year"},
            ],
            value="dayAndWeek",
        ),
        dms.EventTimeline(
            id="event_timeline-presets",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            events=events,
            resources=resources,
            resourceColumnLabel="Resource",
            presets=presets,
            preset="dayAndWeek",
            defaultVisibleDate="2024-01-15",
            height=400,
        ),
    ],
    gap="sm",
)


@callback(
    Output("event_timeline-presets", "preset"),
    Input("event_timeline-presets-control", "value"),
)
def set_preset(value):
    return value
```


### EventTimeline props

`EventTimeline` shares most of its props with the calendar (events, resources,
visibility, drag/resize flags, preferences, timezone) and adds the timeline
specifics: `resourceColumnLabel`, `preset` / `defaultPreset` and `presets`. It
needs a `licenseKey`.


#### `EventTimeline` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule allocations. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the timeline. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences {ampm, weekStartsOn}. Default {ampm: True}. `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) |
| `defaultPreset` | a value equal to: 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year'; optional | Uncontrolled initial preset. Default "dayAndHour". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Render timezone: IANA name, or "default"/"locale"/"UTC". Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | Default color palette for all events (overridable). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables defaults; an object sets {interaction, duration (minutes)}. `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive frosted-glass drawer — right-anchored on desktop, an 88%-height bottom sheet on mobile (below `mobileBreakpoint`). "dialog" keeps the library's floating dialog. |
| `events` | list of dicts; optional | Allocation bars. Each event needs `id`, `title`, `start`, `end` (ISO strings) and usually a `resource` (the row it sits on). INPUT + OUTPUT. `events` is a list of dicts with keys: - id (string \| number; required) - title (string; required) - start (string; required) - end (string; required) - description (string; optional) - timezone (string; optional) - resource (string \| number; optional) - rrule (string \| dict; optional) - exDates (list of strings; optional) - allDay (boolean; optional) - readOnly (boolean; optional) - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional) - draggable (boolean; optional) - resizable (boolean \| a value equal to: 'start', 'end'; optional) - className (string; optional) - extractedFromId (string \| number; optional) |
| `height` | number \| string; default 400 | Height of the wrapping container (the timeline fills it). Default 400. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `licenseKey` | string; optional | MUI X Premium license key (removes the watermark). |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the editor uses its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled preferences {ampm, weekStartsOn}. Also an OUTPUT. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) |
| `preset` | a value equal to: 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year'; optional | Controlled zoom preset. Also an OUTPUT. One of "dayAndHour" \| "dayAndMonth" \| "dayAndWeek" \| "monthAndYear" \| "year". |
| `presets` | list of a value equal to: 'dayAndHour', 'dayAndMonth', 'dayAndWeek', 'monthAndYear', 'year's; optional | The presets available (zoom levels offered). Default is all five, from most zoomed-in to most zoomed-out. |
| `readOnly` | boolean; optional | Global read-only mode. |
| `resourceColumnLabel` | string; optional | Label shown in the resource column header. |
| `resources` | list of dicts; optional | The resource rows. Each event's `resource` points to one of these ids. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default True (timeline). |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the timeline (object form only). |
| `visibleDate` | string; optional | Controlled visible date (ISO string) — centers the window. Also OUTPUT. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /event-timeline*

---

<!-- /events — https://muischeduler.2plot.dev/events/llms.txt -->

# Events

> Define calendar events as plain dictionaries with ISO-string dates — including colors, all-day events, and creation controls.

---



### Events

An event is just a Python `dict`. You pass a list of them to the `events` prop, and
the calendar renders one block per event. Dates cross the boundary as **ISO strings**,
never Python `datetime` objects.

The `events` prop is both an **input and an output**: when a user creates, moves,
resizes, or deletes an event, the component writes the *entire* new array back to
`events`. You don't diff anything yourself — read `events` for the full picture, or
read `lastAction` for just-what-changed.



```python
# File: docs/events/events_basic.py

import dash_mui_scheduler as dms
from dash import html, dcc, Input, Output, callback

# Each event is a plain dict. Dates are ISO strings (no "Z" = wall time).
# Required keys: id, title, start, end. Everything else is optional.
events = [
    {
        "id": "kickoff",
        "title": "Project Kickoff",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:00:00",
        "description": "Align on goals for the quarter.",
    },
    {
        "id": "standup",
        "title": "Daily Standup",
        "start": "2024-01-16T09:30:00",
        "end": "2024-01-16T09:45:00",
    },
    {
        "id": "review",
        "title": "Design Review",
        "start": "2024-01-17T14:00:00",
        "end": "2024-01-17T15:30:00",
        "description": "Walk through the new dashboard mockups.",
    },
    {
        "id": "1on1",
        "title": "1:1",
        "start": "2024-01-18T11:00:00",
        "end": "2024-01-18T11:30:00",
    },
]

component = html.Div(
    [
        dms.EventCalendar(
            id="events-basic-cal",
            events=events,
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
        dcc.Markdown(id="events-basic-out", style={"marginTop": "0.75rem"}),
    ]
)


@callback(
    Output("events-basic-out", "children"),
    Input("events-basic-cal", "events"),
)
def show_count(current_events):
    return f"**{len(current_events or [])}** events currently on the calendar."
```


The callback above only *reads* `events` to display a count. The calendar is fully
interactive **without any callback** — drag, create, and delete all persist on their
own through Dash's normal `setProps` round-trip. Add a callback only when you want to
*display* an output (`events`, `lastAction`, `view`, or `visibleDate`).

### Event fields

Every event needs four required keys; the rest are optional.

| Key | Type | Notes |
|---|---|---|
| `id` | str \| int | Unique per event. **Required.** |
| `title` | str | Shown on the event block. **Required.** |
| `start` | ISO str | e.g. `"2024-01-15T10:00:00"`. **Required.** |
| `end` | ISO str | e.g. `"2024-01-15T11:00:00"`. **Required.** |
| `description` | str | Free-text shown in the event dialog. |
| `resource` | str | Id of a resource this event belongs to. |
| `allDay` | bool | Render in the all-day row (see below). |
| `color` | str | One of the 11 palette names (see below). |
| `timezone` | str | IANA name, e.g. `"America/New_York"`. |
| `draggable` | bool | Override drag for this one event. |
| `resizable` | bool \| `'start'` \| `'end'` | Override resize for this one event. |
| `readOnly` | bool | Make this event non-editable. |
| `className` | str | CSS class on the event element. |


    Dates must be ISO **strings**. A string without a `Z` suffix
    (`"2024-01-15T10:00:00"`) is treated as wall-clock time; a string with `Z`
    (`"2024-01-15T10:00:00Z"`) is UTC. Never pass a Python `datetime` — it is not
    JSON-serializable across the Dash boundary.

### Colors

Set a per-event `color` to any of the **11 palette names**:

`red` · `pink` · `purple` · `indigo` · `blue` · `teal` *(default)* · `green` · `lime` · `amber` · `orange` · `grey`

The calendar-wide `eventColor` prop sets the default color for any event that does
**not** carry its own `color` key. A per-event `color` always overrides `eventColor`.



```python
# File: docs/events/event_colors.py

import dash_mui_scheduler as dms
from dash import html

# The 11-color palette. Set a per-event "color" with any of these names.
PALETTE = [
    "red", "pink", "purple", "indigo", "blue",
    "teal", "green", "lime", "amber", "orange", "grey",
]

# One event per palette color, laid across the week so each is visible.
events = []
for i, name in enumerate(PALETTE):
    day = 15 + (i % 5)          # Mon–Fri of the week of 2024-01-15
    hour = 8 + (i // 5) * 3      # stagger rows so they don't overlap
    events.append(
        {
            "id": f"color-{name}",
            "title": name.capitalize(),
            "start": f"2024-01-{day:02d}T{hour:02d}:00:00",
            "end": f"2024-01-{day:02d}T{hour:02d}:45:00",
            "color": name,
        }
    )

# This event has no "color" of its own, so it falls back to eventColor below.
events.append(
    {
        "id": "color-default",
        "title": "Uses eventColor",
        "start": "2024-01-19T13:00:00",
        "end": "2024-01-19T14:00:00",
    }
)

component = html.Div(
    dms.EventCalendar(
        id="events-colors-cal",
        events=events,
        # eventColor is the calendar-wide default for any event without its
        # own "color" key. Per-event "color" always wins over this.
        eventColor="purple",
        defaultVisibleDate="2024-01-15",
        height=620,
    )
)
```


### All-day events

Mark an event `allDay: True` to place it in the calendar's all-day row, spanning
whole days instead of a time slot. All-day and timed events mix freely in the same
`events` list.



```python
# File: docs/events/all_day.py

import dash_mui_scheduler as dms
from dash import html

# allDay events render in the calendar's all-day row, spanning whole days.
# Mix them freely with ordinary timed events.
events = [
    # All-day events: allDay=True. The time portion of start/end is ignored,
    # but keep them as valid ISO strings. end is exclusive of its last instant,
    # so a single-day all-day event ends the same day.
    {
        "id": "allday-holiday",
        "title": "Company Holiday",
        "start": "2024-01-15T00:00:00",
        "end": "2024-01-15T23:59:59",
        "allDay": True,
        "color": "green",
    },
    {
        "id": "allday-conference",
        "title": "Conference (3 days)",
        "start": "2024-01-17T00:00:00",
        "end": "2024-01-19T23:59:59",
        "allDay": True,
        "color": "indigo",
    },
    # Ordinary timed events sit in the day grid below the all-day row.
    {
        "id": "allday-call",
        "title": "Client Call",
        "start": "2024-01-16T10:00:00",
        "end": "2024-01-16T10:30:00",
        "color": "blue",
    },
    {
        "id": "allday-lunch",
        "title": "Team Lunch",
        "start": "2024-01-18T12:00:00",
        "end": "2024-01-18T13:00:00",
        "color": "amber",
    },
]

component = html.Div(
    dms.EventCalendar(
        id="events-allday-cal",
        events=events,
        defaultVisibleDate="2024-01-15",
        height=600,
    )
)
```



    For an all-day event the time portion of `start`/`end` is ignored, but keep them
    as valid ISO strings. A multi-day span (`"2024-01-17"` → `"2024-01-19"`) shows as
    a single bar across those days.

### Controlling creation

The `eventCreation` prop decides whether — and how — users can create new events by
interacting with empty space.

- **`eventCreation=True`** *(default)* — creation is on with the component's default
  gesture.
- **`eventCreation=False`** — disable click/drag-to-create entirely (existing events
  can still be edited unless you also set `readOnly=True`).
- **`eventCreation={...}`** — fine-tune the gesture with two keys:
  - `interaction`: `'click'` or `'double-click'` — how a new event is started.
  - `duration`: minutes (int) — the length of an event created by a single click.

```python
# A double-click creates a 30-minute event in empty space.
dms.EventCalendar(
    id="events-creation-cal",
    events=events,
    eventCreation={"interaction": "double-click", "duration": 30},
    defaultVisibleDate="2024-01-15",
    height=600,
)
```

When a user creates an event, the new block is appended to `events` and
`lastAction` reports `{"type": "create", "event": {...}, "event_timestamp": ...}`.

### EventCalendar props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /events*

---

<!-- /localization — https://muischeduler.2plot.dev/localization/llms.txt -->

# Localization & Timezones

> Set the display timezone, 12h/24h clock, week start day, and translate UI labels in the dash-mui-scheduler calendar.

---



### Localization & Timezones

The scheduler can render the same events for users in different timezones and
locales without ever touching the event data itself. Four knobs cover almost
everything:

- **`displayTimezone`** — which timezone the grid is drawn in (render-only).
- **`preferences.ampm`** — 12-hour vs 24-hour clock.
- **`preferences.weekStartsOn`** — which weekday a week begins on.
- **`localeText`** — overrides for individual UI label strings.

The most important idea on this page is the **data boundary**: an event's
`start` / `end` are ISO strings that you own. Localization changes how those
times are *displayed*, not what they *are*.

### Display timezone

`displayTimezone` controls the timezone the calendar grid renders in. It accepts
an IANA timezone name like `"America/New_York"` or `"Asia/Tokyo"`, or one of the
special values:

| Value | Meaning |
| --- | --- |
| `"default"` | The component's default behavior (the initial value) |
| `"UTC"` | Render everything in UTC |
| `"locale"` | Use the browser's local timezone |
| IANA name | Render in that specific zone, e.g. `"Europe/Paris"` |


    Changing `displayTimezone` only changes **where event blocks are drawn** on
    the grid. It does **not** rewrite your `events`. The ISO strings you pass in
    stay exactly as they are, and the array the component writes back keeps the
    same wall-clock / UTC strings. Think of it as a viewport over fixed instants.

How the strings are interpreted matters here:

- `"2024-01-17T18:00:00Z"` — the trailing `Z` means UTC. It refers to one fixed
  instant, so it lands at different wall-clock positions in New York vs Tokyo.
- `"2024-01-15T09:00:00"` — no `Z` is a wall-clock time with no zone attached.

The example below renders **one shared list of events** in two calendars — the
only difference is `displayTimezone`. Note how the "Release window (UTC)" event
(which uses a `Z` suffix) shifts between the two grids, while the rest stay put.



```python
# File: docs/localization/timezones.py

from dash import html
import dash_mantine_components as dmc

import dash_mui_scheduler as dms

# One shared set of events. Each `start`/`end` is an ISO string.
# These carry no trailing "Z", so they are wall-clock times — the same
# instant the component then re-renders in whatever displayTimezone is set.
events = [
    {
        "id": "1",
        "title": "Morning sync",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:00:00",
        "color": "blue",
    },
    {
        "id": "2",
        "title": "Design review",
        "start": "2024-01-16T13:00:00",
        "end": "2024-01-16T14:30:00",
        "color": "purple",
    },
    {
        "id": "3",
        "title": "Release window (UTC)",
        "start": "2024-01-17T18:00:00Z",
        "end": "2024-01-17T19:00:00Z",
        "color": "green",
    },
]

# Two calendars, identical events, different `displayTimezone`.
# displayTimezone is render-only: it shifts where blocks appear on the grid,
# but the underlying event data (the ISO strings above) never changes.
component = dmc.Stack(
    [
        dmc.Text("New York (America/New_York)", fw=600, size="sm"),
        dms.EventCalendar(
            id="localization-tz-ny-cal",
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            displayTimezone="America/New_York",
            height=560,
        ),
        dmc.Text("Tokyo (Asia/Tokyo)", fw=600, size="sm"),
        dms.EventCalendar(
            id="localization-tz-tokyo-cal",
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            displayTimezone="Asia/Tokyo",
            height=560,
        ),
    ],
    gap="sm",
)
```


### 12-hour vs 24-hour clock

The clock format lives in `preferences.ampm`:

- `ampm=True` → 12-hour clock (`2:00 PM`).
- `ampm=False` → 24-hour clock (`14:00`).

Set it via `defaultPreferences` (uncontrolled, initial value) or `preferences`
(controlled IN+OUT, also readable in a callback). Users can flip it themselves
from the preferences menu unless you disable that toggle. See the
[Preferences page](/preferences) for the full preferences dict and menu config.

### Week start

`preferences.weekStartsOn` is an integer from **0 to 6** that picks the first day
of the week:

| Value | Day |
| --- | --- |
| 0 | Sunday |
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
| 6 | Saturday |

The example below seeds a Monday-first, 24-hour calendar with week numbers via
`defaultPreferences`. Because these are preferences, they apply to the week,
month, and agenda layouts consistently.



```python
# File: docs/localization/week_start.py

from dash import html

import dash_mui_scheduler as dms

events = [
    {
        "id": "1",
        "title": "Sprint planning",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:30:00",
        "color": "indigo",
    },
    {
        "id": "2",
        "title": "Saturday demo",
        "start": "2024-01-20T11:00:00",
        "end": "2024-01-20T12:00:00",
        "color": "amber",
    },
    {
        "id": "3",
        "title": "Sunday on-call",
        "start": "2024-01-21T08:00:00",
        "end": "2024-01-21T09:00:00",
        "color": "red",
    },
]

# weekStartsOn (0 = Sunday … 6 = Saturday) and ampm live inside preferences.
# Here defaultPreferences seeds a Monday-first, 24-hour calendar on load.
component = html.Div(
    dms.EventCalendar(
        id="localization-week-start-cal",
        events=events,
        defaultView="week",
        defaultVisibleDate="2024-01-15",
        defaultPreferences={
            "weekStartsOn": 1,
            "ampm": False,
            "showWeekends": True,
            "showWeekNumber": True,
        },
        height=560,
    )
)
```



    Both `ampm` and `weekStartsOn` are keys *inside* the preferences dict — there
    are no top-level `ampm` / `weekStartsOn` props. Pass them through
    `defaultPreferences={...}` or the controlled `preferences={...}`.

### Label overrides

`localeText` is a dict of overrides for the calendar's built-in UI label strings
— button captions, menu items, placeholders, and so on. Pass it as a dict where
each value replaces the default text for that key:

```python
dms.EventCalendar(
    id="my-cal",
    events=events,
    localeText={
        "today": "Aujourd'hui",
        "week": "Semaine",
        "month": "Mois",
    },
)
```


    The MUI X Scheduler's locale text includes both plain strings and *functions*
    (for example, labels that format a date range). Because props cross the
    Dash boundary as JSON, **only the string-valued keys can be overridden from
    Python** — function-valued entries can't be passed and will fall back to their
    defaults. Use `localeText` for static caption text, not for dynamic formatters.

For full locale coverage (including the function-valued formatters), you would
provide a translation at the React/JS layer. From Dash, `localeText` is best
treated as a way to retitle the static buttons and menu items.

### Timezones and the data you store

Because `displayTimezone` never edits your events, your storage stays
predictable:

- Store events as ISO strings. Add a `Z` (or an explicit offset) when a value
  is a fixed UTC instant; omit it when it's a floating wall-clock time.
- An event may also carry its own `timezone` (IANA) key in its dict to anchor it
  to a specific zone independent of the display timezone.
- When the component writes `events` back after a drag/create/resize, it returns
  ISO strings in the same shape — so a round-trip through Dash doesn't silently
  re-localize your data.


    The MUI X Scheduler is in beta. Timezone and locale behavior can change
    between releases; verify edge cases (DST boundaries, cross-zone all-day
    events) against your target version.

### Component reference


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /localization*

---

<!-- /navigation — https://muischeduler.2plot.dev/navigation/llms.txt -->

# Navigation

> Navigate the dash-mui-scheduler EventCalendar through time by setting visibleDate, with Prev / Today / Next buttons computed in Python.

---



### Navigation

Every scheduler component shows a single window of time — a day, a week, a
month. Which window is on screen is governed by one prop: **`visibleDate`**, an
ISO date **string** such as `"2024-01-15"`. To move forwards or backwards in
time you simply hand the component a *new* `visibleDate`.


    In the React MUI X Scheduler you would call methods on an `apiRef` to jump
    around. Dash has no imperative ref — you navigate **declaratively** by
    setting `visibleDate`. Compute the next date in Python (with `datetime` /
    `timedelta`), return it as an ISO string, and the calendar re-renders on
    that window. Dates crossing the boundary are always plain strings, never
    Python `datetime` objects.

### Default visible date — `defaultVisibleDate`

`defaultVisibleDate` is the **uncontrolled** way to pick the opening window. Set
it once and the component manages the visible date from then on — the built-in
navigation arrows move it and Dash never hears about the changes. Use this when
you only need to land the user on the right week at load time:

```python
dms.EventCalendar(
    id="my-cal",
    events=events,
    defaultVisibleDate="2024-01-15",
    defaultView="week",
)
```

Because the events in these docs cluster around the week of **15 Jan 2024**,
seeding the visible date there means they are on screen the moment the page
loads.

### Controlling the visible date — `visibleDate`

`visibleDate` is the **controlled** counterpart and it is wired **IN and OUT**:

- **IN** — whatever ISO string you pass becomes the window the component shows.
- **OUT** — when the user clicks the component's own ‹ › arrows (or the Today
  button in its toolbar), the component writes the new ISO date *back* to Dash.

Pick `visibleDate` (not `defaultVisibleDate`) whenever Python needs to drive or
observe the date — for example to build your own navigation chrome, sync two
calendars, or display the current window elsewhere on the page. Don't mix the
two on the same component: a controlled `visibleDate` wins.

### Prev / Today / Next in Python

The example below replaces the toolbar arrows with three `dmc.Button`s. Each
click reads the current `visibleDate`, computes a new one with Python's
`datetime` module, and returns it:

- **Prev** / **Next** parse the current ISO string with `date.fromisoformat`,
  shift it by `timedelta(weeks=1)`, and re-serialise with `.isoformat()`.
- **Today** returns `date.today().isoformat()`.

A `dmc.Text` below the buttons reads `visibleDate` back out, so you can watch the
value change whether you click your own buttons *or* the calendar's built-in
arrows — both feed the same controlled prop.


    The navigate callback takes `visibleDate` as an `Input` as well as an
    `Output`. That lets the *same* prop carry the calendar's built-in arrow
    presses through to Python: when the trigger is the calendar itself we just
    echo the value back, otherwise we compute the shifted date. The callback is
    `prevent_initial_call=True` so the seeded `"2024-01-15"` is left untouched
    on load.



```python
# File: docs/navigation/navigation_buttons.py

from datetime import date, timedelta

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

# `visibleDate` is an ISO date STRING (e.g. "2024-01-15"). It is controlled
# IN *and* OUT: we hand the calendar a value, and the calendar also writes the
# date back when the user navigates with the built-in arrows. Because there is
# no apiRef in Dash, you move through time purely by *setting* visibleDate.
events = [
    {
        "id": "1",
        "title": "Sprint planning",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:30:00",
        "color": "blue",
    },
    {
        "id": "2",
        "title": "Design review",
        "start": "2024-01-16T13:00:00",
        "end": "2024-01-16T14:00:00",
        "color": "purple",
    },
    {
        "id": "3",
        "title": "Retro",
        "start": "2024-01-18T15:00:00",
        "end": "2024-01-18T16:00:00",
        "color": "green",
    },
]

# Seed the controlled value so the calendar opens on the week of the events.
INITIAL_DATE = "2024-01-15"

component = dmc.Stack(
    [
        dmc.Group(
            [
                dmc.Button("‹ Prev", id="navigation-prev", variant="default"),
                dmc.Button("Today", id="navigation-today", variant="light"),
                dmc.Button("Next ›", id="navigation-next", variant="default"),
            ],
            gap="xs",
        ),
        dmc.Text(id="navigation-readout", size="sm", c="dimmed"),
        dms.EventCalendar(
            id="navigation-cal",
            events=events,
            visibleDate=INITIAL_DATE,
            defaultView="week",
            height=600,
        ),
    ],
    gap="sm",
)


@callback(
    Output("navigation-cal", "visibleDate"),
    Input("navigation-prev", "n_clicks"),
    Input("navigation-next", "n_clicks"),
    Input("navigation-today", "n_clicks"),
    Input("navigation-cal", "visibleDate"),
    prevent_initial_call=True,
)
def navigate(prev_clicks, next_clicks, today_clicks, visible_date):
    from dash import ctx

    trigger = ctx.triggered_id

    # The calendar's own arrows already updated visibleDate -> nothing to do.
    if trigger == "navigation-cal":
        return visible_date

    if trigger == "navigation-today":
        return date.today().isoformat()

    # Parse the current ISO date string, shift by one week, re-serialize.
    current = date.fromisoformat((visible_date or INITIAL_DATE)[:10])
    if trigger == "navigation-prev":
        return (current - timedelta(weeks=1)).isoformat()
    if trigger == "navigation-next":
        return (current + timedelta(weeks=1)).isoformat()
    return visible_date


@callback(
    Output("navigation-readout", "children"),
    Input("navigation-cal", "visibleDate"),
)
def show_visible_date(visible_date):
    return f"Current visibleDate: {visible_date or INITIAL_DATE}"
```


### EventCalendar props

`visibleDate`, `defaultVisibleDate`, `view`, and `defaultView` follow the same
controlled / uncontrolled pattern. The full prop reference:


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /navigation*

---

<!-- /playground — https://muischeduler.2plot.dev/playground/llms.txt -->

# Playground

> Drive the major Event Calendar props live with Dash Mantine Components inputs.

---



### Try the props live

Every control below is a plain **Dash Mantine Components** input wired to an
`EventCalendar` prop with a normal Dash callback — change one and the calendar
updates instantly. It doubles as a demonstration of how `dash-mui-scheduler`
**pairs with DMC**: the calendar follows the Mantine color scheme (toggle dark
mode in the header), and DMC `Select` / `Switch` / `SegmentedControl` inputs map
cleanly onto the scheduler's props.



    Each input drives a prop through a callback — e.g. a `dmc.SegmentedControl`
    sets `view`, a `dmc.Select` sets `eventColor` / `displayTimezone`, and the
    six preference `dmc.Switch`es are collected into the `preferences` dict.
    `view` is two-way bound, so clicking the calendar's own view buttons keeps
    the control in sync. The `lastAction` output feeds the readout under the
    calendar.

### Source


```python
# File: docs/playground/playground.py

import datetime

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

# A live playground: every Dash Mantine Components input below is wired to a
# major EventCalendar prop, so you can see exactly how each one behaves. This is
# also a demonstration of how dash-mui-scheduler pairs with DMC — the calendar
# follows the Mantine color scheme, and DMC inputs drive its props.

# Anchor events to *today* (with UTC "Z" instants) so the Display controls have
# something to act on:
#   - the current-time line is visible (we are looking at this week),
#   - `displayTimezone` shifts the rendered times (Z instants are re-projected),
#   - and with NO per-event `color`, `eventColor` recolours every event live
#     (a per-event color would otherwise override the component default).
_TODAY = datetime.date.today()


def _ev(eid, title, day_offset, hour, minute, dur_min):
    start = datetime.datetime.combine(
        _TODAY + datetime.timedelta(days=day_offset), datetime.time(hour, minute)
    )
    end = start + datetime.timedelta(minutes=dur_min)
    fmt = "%Y-%m-%dT%H:%M:%SZ"  # trailing Z = UTC instant
    return {"id": eid, "title": title, "start": start.strftime(fmt), "end": end.strftime(fmt)}


EVENTS = [
    _ev("1", "Standup", 0, 13, 0, 30),
    _ev("2", "Design review", 0, 15, 30, 60),
    _ev("3", "Workshop", 1, 14, 0, 90),
    _ev("4", "1:1", 2, 16, 0, 30),
    _ev("5", "Retro", -1, 17, 0, 60),
]

COLORS = ["red", "pink", "purple", "indigo", "blue", "teal", "green", "lime", "amber", "orange", "grey"]
WEEKDAYS = [
    {"label": "Sunday", "value": "0"}, {"label": "Monday", "value": "1"}, {"label": "Tuesday", "value": "2"},
    {"label": "Wednesday", "value": "3"}, {"label": "Thursday", "value": "4"}, {"label": "Friday", "value": "5"},
    {"label": "Saturday", "value": "6"},
]
TIMEZONES = ["default", "UTC", "America/New_York", "Europe/Paris", "Asia/Tokyo"]


def _labeled(label, control):
    return dmc.Stack([dmc.Text(label, size="sm", fw=600), control], gap=4)


_controls = dmc.SimpleGrid(
    cols={"base": 1, "md": 2},
    spacing="lg",
    children=[
        dmc.Fieldset(
            legend="View & creation",
            children=dmc.Stack([
                _labeled("View", dmc.SegmentedControl(id="pg-view", value="week", fullWidth=True, data=[
                    {"label": "Day", "value": "day"}, {"label": "Week", "value": "week"},
                    {"label": "Month", "value": "month"}, {"label": "Agenda", "value": "agenda"}])),
                _labeled("Event editor", dmc.SegmentedControl(id="pg-dialog", value="drawer", data=[
                    {"label": "Drawer", "value": "drawer"}, {"label": "Dialog", "value": "dialog"}])),
                dmc.Switch(id="pg-creation", label="Allow event creation", checked=True),
            ], gap="sm"),
        ),
        dmc.Fieldset(
            legend="Interaction",
            children=dmc.Stack([
                dmc.Switch(id="pg-drag", label="Events draggable", checked=True),
                _labeled("Resizable", dmc.SegmentedControl(id="pg-resize", value="true", data=[
                    {"label": "Both", "value": "true"}, {"label": "Start", "value": "start"},
                    {"label": "End", "value": "end"}, {"label": "Off", "value": "false"}])),
                dmc.Switch(id="pg-readonly", label="Read-only", checked=False),
            ], gap="sm"),
        ),
        dmc.Fieldset(
            legend="Display",
            children=dmc.Stack([
                dmc.Select(id="pg-color", label="Default event color", value="teal", data=COLORS, allowDeselect=False),
                dmc.Select(id="pg-tz", label="Display timezone", value="default", data=TIMEZONES, allowDeselect=False),
                dmc.Switch(id="pg-nowline", label="Current-time line", checked=True),
                dmc.Switch(id="pg-scrollnow", label="Scroll to current time on load", checked=False),
            ], gap="sm"),
        ),
        dmc.Fieldset(
            legend="Preferences",
            children=dmc.Stack([
                dmc.Group([
                    dmc.Switch(id="pg-ampm", label="12-hour clock", checked=True),
                    dmc.Switch(id="pg-weekends", label="Weekends", checked=True),
                ], gap="lg"),
                dmc.Group([
                    dmc.Switch(id="pg-weeknum", label="Week numbers", checked=False),
                    dmc.Switch(id="pg-sidepanel", label="Side panel", checked=True),
                ], gap="lg"),
                dmc.Switch(id="pg-emptyagenda", label="Empty days in agenda", checked=True),
                dmc.Select(id="pg-weekstart", label="Week starts on", value="0", data=WEEKDAYS, allowDeselect=False),
            ], gap="sm"),
        ),
    ],
)

component = dmc.Stack(
    [
        dmc.Paper(_controls, withBorder=True, p="md", radius="md"),
        dms.EventCalendar(
            id="playground-cal",
            events=EVENTS,
            defaultVisibleDate=_TODAY.isoformat(),
            height=620,
            view="week",
            eventColor="teal",
            displayTimezone="default",
            areEventsDraggable=True,
            areEventsResizable=True,
            readOnly=False,
            eventCreation=True,
            showCurrentTimeIndicator=True,
            scrollToCurrentTime=False,
            eventDialogVariant="drawer",
            preferences={"ampm": True, "weekStartsOn": 0, "showWeekends": True,
                         "showWeekNumber": False, "isSidePanelOpen": True, "showEmptyDaysInAgenda": True},
        ),
        dmc.Code(id="pg-readout", block=True),
    ],
    gap="md",
)


# --- View (two-way: control <-> the calendar's own view buttons) ------------
@callback(Output("playground-cal", "view"), Input("pg-view", "value"), prevent_initial_call=True)
def set_view(value):
    return value


@callback(Output("pg-view", "value"), Input("playground-cal", "view"), prevent_initial_call=True)
def read_view(value):
    return value


# --- Display / interaction / editor settings --------------------------------
@callback(
    Output("playground-cal", "eventColor"),
    Output("playground-cal", "displayTimezone"),
    Output("playground-cal", "showCurrentTimeIndicator"),
    Output("playground-cal", "scrollToCurrentTime"),
    Output("playground-cal", "areEventsDraggable"),
    Output("playground-cal", "areEventsResizable"),
    Output("playground-cal", "readOnly"),
    Output("playground-cal", "eventCreation"),
    Output("playground-cal", "eventDialogVariant"),
    Input("pg-color", "value"),
    Input("pg-tz", "value"),
    Input("pg-nowline", "checked"),
    Input("pg-scrollnow", "checked"),
    Input("pg-drag", "checked"),
    Input("pg-resize", "value"),
    Input("pg-readonly", "checked"),
    Input("pg-creation", "checked"),
    Input("pg-dialog", "value"),
)
def settings(color, tz, nowline, scroll_now, drag, resize, read_only, creation, dialog):
    resize_val = {"true": True, "false": False, "start": "start", "end": "end"}[resize]
    return color, tz, nowline, scroll_now, drag, resize_val, read_only, creation, dialog


# --- Preferences ------------------------------------------------------------
@callback(
    Output("playground-cal", "preferences"),
    Input("pg-ampm", "checked"),
    Input("pg-weekstart", "value"),
    Input("pg-weekends", "checked"),
    Input("pg-weeknum", "checked"),
    Input("pg-sidepanel", "checked"),
    Input("pg-emptyagenda", "checked"),
)
def preferences(ampm, week_start, weekends, week_number, side_panel, empty_agenda):
    return {
        "ampm": ampm,
        "weekStartsOn": int(week_start),
        "showWeekends": weekends,
        "showWeekNumber": week_number,
        "isSidePanelOpen": side_panel,
        "showEmptyDaysInAgenda": empty_agenda,
    }


# --- Live readout -----------------------------------------------------------
@callback(
    Output("pg-readout", "children"),
    Input("playground-cal", "events"),
    Input("playground-cal", "lastAction"),
)
def readout(events, last_action):
    count = len(events or [])
    if not last_action:
        return f"{count} events · interact with the calendar to see the last action"
    title = (last_action.get("event") or {}).get("title")
    suffix = f" — {title}" if title else ""
    return f"{count} events · last action: {last_action.get('type')}{suffix}"
```

    :defaultExpanded: false
    :withExpandedButton: true

### All Event Calendar props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /playground*

---

<!-- /preferences — https://muischeduler.2plot.dev/preferences/llms.txt -->

# Preferences

> Control the EventCalendar's default display preferences and the user-facing preferences menu in dash-mui-scheduler.

---



### Preferences

Every calendar exposes a small set of **user preferences** — am/pm clock, which day the
week starts on, whether weekends and week numbers are shown, and so on. These are surfaced
through a built-in preferences menu (the gear button in the calendar header) so users can
tweak the view themselves.

In `dash_mui_scheduler` you control three related things:

- **`defaultPreferences`** — the initial, uncontrolled preference values applied on load.
- **`preferences`** — the controlled IN+OUT version. It seeds the UI *and* reports the
  current values back to Dash whenever the user changes a setting.
- **`preferencesMenuConfig`** — which entries appear in the preferences menu (or whether the
  menu shows at all).

All of these work the same way on `EventCalendar`, `EventCalendarPremium`, and
(a smaller subset) on `EventTimeline`.

### Default preferences

`defaultPreferences` is a dict. Set only the keys you care about — anything omitted falls
back to the component's own defaults.

| Key | Type | Meaning |
| --- | --- | --- |
| `ampm` | bool | 12-hour (`True`) vs 24-hour (`False`) clock |
| `weekStartsOn` | int 0–6 | First day of the week (0 = Sunday … 1 = Monday) |
| `showWeekends` | bool | Show Saturday/Sunday columns |
| `showWeekNumber` | bool | Show the ISO week number |
| `isSidePanelOpen` | bool | Whether the date/resource side panel starts open |
| `showEmptyDaysInAgenda` | bool | Keep empty days visible in the agenda view |


    Use `defaultPreferences` when you just want a starting configuration and don't need to
    read changes back. Use `preferences` when you want the current values in a callback —
    it is both an input and an output, so the component writes the full preferences dict
    back on every toggle.

### Reading preferences

Because `preferences` is IN+OUT, you can attach a callback whose **only** job is to read the
current values. The example below seeds the calendar with a Monday week start, 24-hour
clock, and visible week numbers via `defaultPreferences`, then echoes the live `preferences`
dict each time the user changes something in the menu.



```python
# File: docs/preferences/preferences_default.py

import json

import dash_mantine_components as dmc
from dash import Input, Output, State, callback, html

import dash_mui_scheduler as dms

events = [
    {"id": "1", "title": "Design review", "start": "2024-01-15T09:00:00", "end": "2024-01-15T10:30:00", "color": "blue"},
    {"id": "2", "title": "Saturday standup", "start": "2024-01-20T11:00:00", "end": "2024-01-20T12:00:00", "color": "green"},
]

# `preferences` is controlled IN + OUT. We seed it, drive it from the switches
# below, and read it back in a callback. (The calendar also writes it back when
# the user toggles a setting in its own gear menu.)
INITIAL = {
    "ampm": False,
    "weekStartsOn": 1,
    "showWeekends": True,
    "showWeekNumber": True,
    "isSidePanelOpen": True,
    "showEmptyDaysInAgenda": True,
}

component = html.Div(
    [
        dmc.Group(
            [
                dmc.Switch(id="pref-ampm", label="12-hour clock", checked=INITIAL["ampm"]),
                dmc.Switch(id="pref-weekends", label="Show weekends", checked=INITIAL["showWeekends"]),
                dmc.Switch(id="pref-weeknum", label="Week numbers", checked=INITIAL["showWeekNumber"]),
            ],
            mb="md",
        ),
        dms.EventCalendar(
            id="preferences-default-cal",
            height=560,
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            preferences=INITIAL,
        ),
        dmc.Code(id="preferences-default-readout", block=True, mt="sm"),
    ]
)


@callback(
    Output("preferences-default-cal", "preferences"),
    Input("pref-ampm", "checked"),
    Input("pref-weekends", "checked"),
    Input("pref-weeknum", "checked"),
    State("preferences-default-cal", "preferences"),
    prevent_initial_call=True,
)
def set_preferences(ampm, weekends, week_number, current):
    prefs = dict(current or INITIAL)
    prefs.update({"ampm": ampm, "showWeekends": weekends, "showWeekNumber": week_number})
    return prefs


@callback(
    Output("preferences-default-readout", "children"),
    Input("preferences-default-cal", "preferences"),
)
def show_preferences(preferences):
    # `preferences` flows back out whenever it changes — from the switches above
    # or from the calendar's own gear menu.
    return json.dumps(preferences or INITIAL, indent=2, sort_keys=True)
```


The readout updates as you toggle items in the calendar's preferences menu — no extra
plumbing required, since the component pushes the new dict back through Dash's normal
`setProps` round-trip.

### The preferences menu

`preferencesMenuConfig` controls the menu itself:

- Pass **`False`** to hide the entire preferences menu (useful for a locked-down, read-only
  display).
- Pass a **dict** to show or hide individual entries. Each key is a boolean:
  `toggleWeekendVisibility`, `toggleWeekNumberVisibility`, `toggleAmpm`,
  `toggleEmptyDaysInAgenda`, and `toggleWeekStartsOn`.

The first calendar below keeps the weekend, week-number, and am/pm toggles but removes the
"empty days in agenda" and "week starts on" entries. The second passes
`preferencesMenuConfig=False`, so its menu button disappears entirely.



```python
# File: docs/preferences/preferences_menu.py

import dash_mantine_components as dmc
from dash import html

import dash_mui_scheduler as dms

events = [
    {"id": "1", "title": "Sprint planning", "start": "2024-01-15T13:00:00", "end": "2024-01-15T14:00:00", "color": "indigo"},
    {"id": "2", "title": "Retro", "start": "2024-01-18T15:00:00", "end": "2024-01-18T16:00:00", "color": "amber"},
]

# preferencesMenuConfig prunes the gear/preferences menu.
# Pass a dict to toggle individual items, or False to hide the whole menu.
component = dmc.Stack(
    [
        dmc.Text("Custom menu — only weekends, week number, and AM/PM toggles", fw=600, size="sm"),
        dms.EventCalendar(
            id="preferences-menu-cal",
            height=480,
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            preferencesMenuConfig={
                "toggleWeekendVisibility": True,
                "toggleWeekNumberVisibility": True,
                "toggleAmpm": True,
                "toggleEmptyDaysInAgenda": False,
                "toggleWeekStartsOn": False,
            },
        ),
        dmc.Text("Menu hidden — preferencesMenuConfig=False (no gear button)", fw=600, size="sm", mt="md"),
        dms.EventCalendar(
            id="preferences-menu-hidden-cal",
            height=480,
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            preferencesMenuConfig=False,
        ),
    ],
    gap="xs",
)
```



    `preferencesMenuConfig` only decides which controls are *available* to the user. It does
    not change the actual preference values — set those with `defaultPreferences` /
    `preferences`. Hiding a toggle simply means the user can't change that setting from the
    UI; you can still set it programmatically.

### Component reference


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /preferences*

---

<!-- /privacy — https://muischeduler.2plot.dev/privacy/llms.txt -->

# Privacy

> What dash-mui-scheduler stores about a visit, what it does not store, and where the numbers go — described from the code that does it.

This page describes what the code in this repository actually does. Each
claim below corresponds to something readable in `lib/analytics_tracker.py`,
and the test suite holds the two together.

## What is stored about a visit

Every request that is not network machinery records one row:

- the **time** of the request;
- the **path** requested;
- a **device type** (desktop, mobile, tablet, bot);
- the **User-Agent** string your browser or client sent;
- a **visitor key** — a keyed one-way hash of your network address and
  User-Agent, truncated to sixteen characters, used to tell one visitor from
  another within the retention window;
- a **location**, if and only if the network edge in front of this site sent
  one (see below).

Crawler rows additionally carry the vendor identity the classifier
determined — which bot it was, which class of bot, and whether it verified
against the address ranges that vendor publishes.

## What is NOT stored

- **Your IP address.** It is read from the request so the site can tell one
  visitor from another, and so a crawler can be checked against the ranges
  its vendor publishes, and it is then reduced to the visitor key and
  discarded. It is not written to disk. (An operator running their own copy
  of this software can set `ANALYTICS_KEEP_CLIENT_IP=1` to keep it. This site
  does not.)
- **Anything from a third-party lookup service.** Earlier versions of this
  site sent visitor addresses to a geolocation API to turn them into a city.
  That code was REMOVED — not disabled — in release 1.6.44. This app makes no
  outbound request about you.
- **Cookies for analytics.** The visitor key is computed per request from
  what your client already sent. Nothing is stored in your browser to track
  you. Signing in sets a session cookie, which is what keeps you signed in.

## Where location comes from

From the network edge, or not at all. Cloudflare sits in front of this site
and adds headers describing where a request entered its network:
`CF-IPCountry` always, and `CF-IPCity`, `CF-Region`, `CF-IPLatitude` and
`CF-IPLongitude` when the zone is configured to send them. Whatever arrives
is stored; whatever does not is simply absent. There is no lookup and no
fallback to one.

You can see which of those headers this host is actually receiving — they are
listed in the `geo.headers_seen` field of
[https://muischeduler.2plot.dev/healthz](https://muischeduler.2plot.dev/healthz).

## Network machinery is counted nowhere

The 2plot network's own traffic — hourly health checks, post-deploy test
batteries, continuous integration — carries a marker in its User-Agent and is
dropped before anything is recorded, in both of this site's tables. It is not
in these numbers, by design.

## How long it is kept, and where it goes

Rows are pruned on a retention window and the visit table is capped in size.
A daily summary — counts by day, by page, by country, by crawler vendor — is
sent to the 2plot network hub. The summary carries no visitor keys, no
addresses and no User-Agent strings: it is counts.

## Signing in

Sign-in is handled by Clerk. What Clerk stores about an account is governed
by Clerk's own privacy policy. This site keeps the identifier it needs in
order to decide what you may see.

## Questions

The [Discord](https://discord.gg/e5s5uHWUHH), or an issue on
[the repository](https://github.com/pip-install-python/dash-mui-scheduler).

---

<!-- /quickstart — https://muischeduler.2plot.dev/quickstart/llms.txt -->

# Quickstart

> Install dash-mui-scheduler and render your first MUI X Scheduler calendar and timeline in Dash.

---



### Walkthrough

A quick video tour of `dash-mui-scheduler` — the calendar, the resource timeline,
and the radial charts in action.


<!-- component rendered from docs/quickstart/video.py; source withheld by `:code: false` -->


### Installation

`dash-mui-scheduler` wraps the [MUI X Scheduler](https://mui.com/x/react-scheduler/) for Dash.
Install it from PyPI:

```bash
pip install dash-mui-scheduler
```


    The MUI X Scheduler is in beta, and so are these wrappers. Props and behaviour may
    change between releases. Pin your version if you need stability.

There are three components:

- `dms.EventCalendar` — Community (MIT). Day / week / month / agenda calendar. No license key.
- `dms.EventCalendarPremium` — adds recurrence (`rrule` / `exDates`). Needs a `licenseKey`.
- `dms.EventTimeline` — Premium resource-row timeline. Needs a `licenseKey`.

This page covers the two you will reach for first: `EventCalendar` and `EventTimeline`.

### Rendering an Event Calendar

Import the package as `dms`, hand it a list of events, and point it at a date. A standalone
calendar is **already fully interactive** — drag, resize, create, and delete all work and
persist through Dash's own `setProps` round-trip, with no callback required.

The `dcc.Markdown` readout below is wired to the calendar's `lastAction` output so you can
watch each edit as it happens (see [Reading changes](#reading-changes)).



```python
# File: docs/quickstart/render_calendar.py

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

# events are plain dicts; dates are ISO strings (no Z = wall time).
# Required keys: id, title, start, end. Everything else is optional.
events = [
    {
        "id": "1",
        "title": "Team Standup",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T09:30:00",
        "color": "blue",
    },
    {
        "id": "2",
        "title": "Design Review",
        "start": "2024-01-16T13:00:00",
        "end": "2024-01-16T14:30:00",
        "color": "purple",
    },
    {
        "id": "3",
        "title": "Sprint Demo",
        "start": "2024-01-18T15:00:00",
        "end": "2024-01-18T16:00:00",
        "color": "green",
    },
]

component = dmc.Stack(
    [
        dms.EventCalendar(
            id="quickstart-cal",
            events=events,
            defaultVisibleDate="2024-01-15",
            defaultView="week",
            height=600,
        ),
        dmc.Text("Last action", fw=600, size="sm"),
        dcc.Markdown(id="quickstart-cal-readout"),
    ],
    gap="sm",
)


@callback(
    Output("quickstart-cal-readout", "children"),
    Input("quickstart-cal", "lastAction"),
)
def show_last_action(last_action):
    if not last_action:
        return "_Drag, create, or delete an event to see the last action._"
    action_type = last_action.get("type")
    event = last_action.get("event") or {}
    title = event.get("title", "—")
    return f"**{action_type}** → `{title}`"
```


### The events model

`events` is a list of plain dicts. **Dates are ISO strings**, never Python `datetime`
objects. A string with no trailing `Z` (e.g. `"2024-01-15T10:00:00"`) is wall time; a
trailing `Z` (e.g. `"2024-01-15T10:00:00Z"`) is UTC.

Every event requires four keys:

| Key | Type | Example |
| --- | --- | --- |
| `id` | str or int | `"1"` |
| `title` | str | `"Team Standup"` |
| `start` | ISO str | `"2024-01-15T09:00:00"` |
| `end` | ISO str | `"2024-01-15T09:30:00"` |

Common optional keys include `color` (one of the 11 palette names: `red`, `pink`, `purple`,
`indigo`, `blue`, `teal`, `green`, `lime`, `amber`, `orange`, `grey`), `description`,
`allDay`, `resource`, `draggable`, `resizable`, and `readOnly`.


    The component writes the **entire new array** back to `events` on every create, move,
    resize, or delete. Read `events` in a callback to get the current state, or read
    `lastAction` to get just the change that triggered it.

### Reading changes

You never need a callback to make the calendar work — only to **display** what changed.
`lastAction` is an output-only prop shaped like
`{type, event, event_timestamp}`, where `type` is one of
`create`, `update`, `delete`, `move`, `resize`, or `change`, and `event` is the affected
event dict (or `None` for some actions).

The example above attaches this minimal callback to surface it:

```python
@callback(
    Output("quickstart-cal-readout", "children"),
    Input("quickstart-cal", "lastAction"),
)
def show_last_action(last_action):
    if not last_action:
        return "_Drag, create, or delete an event to see the last action._"
    action_type = last_action.get("type")
    event = last_action.get("event") or {}
    return f"**{action_type}** → `{event.get('title', '—')}`"
```

Create or drag an event in the calendar above and the readout updates in place.

### Rendering an Event Timeline

`EventTimeline` is the Premium resource-row view — a Gantt-style layout where each
**resource** is a row and each event sits on the row named by its `resource` id. Resources
are dicts like `{"id": "team-a", "title": "Team A"}`. The timeline shares the same events
model (ISO strings, `events` in/out, `lastAction`); it just adds zoom `preset`s such as
`dayAndHour` (default), `dayAndWeek`, and `monthAndYear`.


    `EventTimeline` (and `EventCalendarPremium`) are Premium components. Without a valid
    `licenseKey` they render with a watermark — that is expected. Set
    `MUI_X_LICENSE_KEY` in your environment to remove it. The component still functions
    either way.



```python
# File: docs/quickstart/render_timeline.py

import os

import dash_mantine_components as dmc
import dash_mui_scheduler as dms

# Resources are the rows of the timeline. Each event points at a resource id.
resources = [
    {"id": "team-a", "title": "Team A"},
    {"id": "team-b", "title": "Team B"},
]

events = [
    {
        "id": "1",
        "title": "Migration",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T15:00:00",
        "resource": "team-a",
        "color": "indigo",
    },
    {
        "id": "2",
        "title": "QA Pass",
        "start": "2024-01-15T11:00:00",
        "end": "2024-01-15T18:00:00",
        "resource": "team-b",
        "color": "amber",
    },
]

component = dmc.Stack(
    [
        dms.EventTimeline(
            id="quickstart-timeline",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            resources=resources,
            events=events,
            resourceColumnLabel="Teams",
            defaultVisibleDate="2024-01-15",
            defaultPreset="dayAndHour",
            height=400,
        ),
    ],
    gap="sm",
)
```


### EventCalendar props

The full, auto-generated prop reference for the Community `EventCalendar`:


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /quickstart*

---

<!-- /radial-axes — https://muischeduler.2plot.dev/radial-axes/llms.txt -->

# Radial Axes

> Configure the grid, radius axis, rotation axis, and axis highlight for the radial charts.

---



### Overview

Both radial charts share the same polar axes, configured through the
**`rotationAxis`** (angular), **`radiusAxis`** (radial), **`grid`**, and
**`axisHighlight`** props. Each is a plain dict (or list of dicts), so you set
them straight from Python.


    The radial charts are MUI X **Premium** (preview). Set `MUI_X_LICENSE_KEY`
    to remove the watermark.

### Grid and axis geometry

The rotation axis spans `startAngle` → `endAngle`; the radius axis spans
`minRadius` → `maxRadius`. `grid={"rotation": ..., "radius": ...}` toggles the
background spokes and rings. Try the controls:



```python
# File: docs/radial_axes/axes.py

import os

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

dataset = [
    {"month": "Jan", "london": 49}, {"month": "Feb", "london": 38},
    {"month": "Mar", "london": 40}, {"month": "Apr", "london": 44},
    {"month": "May", "london": 49}, {"month": "Jun", "london": 45},
    {"month": "Jul", "london": 45}, {"month": "Aug", "london": 50},
    {"month": "Sep", "london": 49}, {"month": "Oct", "london": 69},
    {"month": "Nov", "london": 59}, {"month": "Dec", "london": 56},
]

# The rotation axis spans startAngle -> endAngle; the radius axis spans
# minRadius -> maxRadius. `grid` toggles the background rings/spokes.
component = html.Div(
    [
        dmc.Group(
            [
                dmc.NumberInput(id="ax-start", label="startAngle", value=-90, min=-360, max=360, w=110),
                dmc.NumberInput(id="ax-end", label="endAngle", value=180, min=-360, max=360, w=110),
                dmc.NumberInput(id="ax-minr", label="minRadius", value=30, min=0, max=200, w=110),
                dmc.NumberInput(id="ax-maxr", label="maxRadius", value=130, min=0, max=200, w=110),
                dmc.Switch(id="ax-grot", label="rotation grid", checked=True, mt="lg"),
                dmc.Switch(id="ax-grad", label="radius grid", checked=True, mt="lg"),
            ],
            mb="md",
            align="flex-end",
        ),
        dms.RadialLineChart(
            id="radial-axes-chart",
            height=420,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            dataset=dataset,
            series=[{"dataKey": "london", "label": "London precipitation (mm)", "curve": "natural", "showMark": True}],
            rotationAxis=[{"scaleType": "point", "dataKey": "month", "startAngle": -90, "endAngle": 180}],
            radiusAxis=[{"minRadius": 30, "maxRadius": 130}],
            grid={"rotation": True, "radius": True},
        ),
    ]
)


@callback(
    Output("radial-axes-chart", "rotationAxis"),
    Output("radial-axes-chart", "radiusAxis"),
    Output("radial-axes-chart", "grid"),
    Input("ax-start", "value"),
    Input("ax-end", "value"),
    Input("ax-minr", "value"),
    Input("ax-maxr", "value"),
    Input("ax-grot", "checked"),
    Input("ax-grad", "checked"),
)
def update_axes(start, end, min_r, max_r, g_rot, g_rad):
    return (
        [{"scaleType": "point", "dataKey": "month", "startAngle": start, "endAngle": end}],
        [{"minRadius": min_r, "maxRadius": max_r}],
        {"rotation": g_rot, "radius": g_rad},
    )
```


Common axis options include `scaleType` (`"point"`, `"band"`, `"linear"`),
`disableLine`, `disableTicks`, `tickNumber`, `position`, and `valueFormatter`
(the last is a function and so is not settable from Python — use the chart's
defaults or a `dataKey` instead).

### Axis highlight

`axisHighlight` highlights data based on the pointer position. Each of
`rotation` and `radius` can be `"none"`, `"line"`, or `"band"`:



```python
# File: docs/radial_axes/highlight.py

import os

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

_rows = [
    ("Jan", 49, 51, 78), ("Feb", 38, 41, 72), ("Mar", 40, 48, 95),
    ("Apr", 44, 47, 90), ("May", 49, 63, 92), ("Jun", 45, 56, 87),
    ("Jul", 45, 62, 100), ("Aug", 50, 54, 96), ("Sep", 49, 48, 90),
    ("Oct", 69, 60, 90), ("Nov", 59, 53, 85), ("Dec", 56, 56, 90),
]
DATASET_STR = [{"month": m, "london": lo, "paris": pa, "newYork": ny} for (m, lo, pa, ny) in _rows]
# A linear scale needs a numeric axis key, so swap the month label for its index.
DATASET_NUM = [{**d, "month": i + 1} for i, d in enumerate(DATASET_STR)]

SERIES = [
    {"dataKey": "london", "curve": "linear", "label": "London", "showMark": True},
    {"dataKey": "paris", "curve": "linear", "label": "Paris", "showMark": True},
    {"dataKey": "newYork", "curve": "linear", "label": "New York", "showMark": True},
]

# `axisHighlight` highlights data based on the pointer: each of `rotation` and
# `radius` can be "none", "line", or "band".
component = html.Div(
    [
        dmc.Group(
            [
                dmc.Stack([dmc.Text("scale type", size="sm", fw=600),
                           dmc.SegmentedControl(id="rax-scale", value="point",
                                                data=["band", "point", "linear"])], gap=2),
                dmc.Stack([dmc.Text("rotation highlight", size="sm", fw=600),
                           dmc.SegmentedControl(id="rax-rot", value="band",
                                                data=["none", "line", "band"])], gap=2),
                dmc.Stack([dmc.Text("radius highlight", size="sm", fw=600),
                           dmc.SegmentedControl(id="rax-rad", value="none",
                                                data=["none", "line"])], gap=2),
            ],
            mb="md",
        ),
        dms.RadialLineChart(
            id="radial-axes-highlight",
            height=400,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            dataset=DATASET_STR,
            series=SERIES,
            rotationAxis=[{"dataKey": "month", "scaleType": "point"}],
            radiusAxis=[{"minRadius": 10, "min": 0}],
            grid={"rotation": True, "radius": True},
            axisHighlight={"rotation": "band", "radius": "none"},
        ),
    ]
)


@callback(
    Output("radial-axes-highlight", "dataset"),
    Output("radial-axes-highlight", "rotationAxis"),
    Output("radial-axes-highlight", "axisHighlight"),
    Input("rax-scale", "value"),
    Input("rax-rot", "value"),
    Input("rax-rad", "value"),
)
def update_highlight(scale, rotation, radius):
    dataset = DATASET_NUM if scale == "linear" else DATASET_STR
    return dataset, [{"dataKey": "month", "scaleType": scale}], {"rotation": rotation, "radius": radius}
```


---

*Source: /radial-axes*

---

<!-- /radial-bars — https://muischeduler.2plot.dev/radial-bars/llms.txt -->

# Radial Bars

> Polar bar charts for comparing values along periodic categories, wrapping MUI X RadialBarChart (Premium, preview).

---



### Overview

`RadialBarChart` is the polar counterpart of the bar chart: the x/y axes become
**`rotationAxis`** (a `band` axis of categories around the circle) and
**`radiusAxis`** (the radial value scale). It accepts the same display options
as a cartesian bar chart — `stack` and `layout` on each series, and
`categoryGapRatio` / `barGapRatio` on the band axis.


    Premium, preview (`Unstable_`) — set `MUI_X_LICENSE_KEY` to remove the
    watermark.

### Basic radial bars



```python
# File: docs/radial_bars/basic.py

import os

from dash import html
import dash_mui_scheduler as dms

# Polar bars: a `band` rotation axis defines the categories around the circle,
# and the radius encodes each value.
component = html.Div(
    dms.RadialBarChart(
        id="radial-bars-basic",
        height=400,
        licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
        series=[{"data": [12, 19, 8, 15], "label": "Revenue ($M)"}],
        rotationAxis=[{"scaleType": "band", "data": ["Q1", "Q2", "Q3", "Q4"]}],
        grid={"rotation": True, "radius": True},
    )
)
```


### Stacking and layout

Series with the same `stack` value are stacked together. `layout` swaps which
axis encodes the value: `"vertical"` (default) uses the radius, `"horizontal"`
uses the rotation. Toggle the controls below:



```python
# File: docs/radial_bars/stacked.py

import os

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

# Netflix balance sheet ($B). Series reference columns by dataKey.
dataset = [
    {"year": "2020", "totAss": 39.3, "totLia": 28.2, "totEq": 11.1},
    {"year": "2021", "totAss": 44.6, "totLia": 28.7, "totEq": 15.8},
    {"year": "2022", "totAss": 48.6, "totLia": 27.8, "totEq": 20.8},
    {"year": "2023", "totAss": 48.7, "totLia": 28.0, "totEq": 20.6},
]


def _series(layout, stack):
    # Total Assets stands alone; Liabilities + Equity stack into "passive".
    stk = "passive" if stack else None
    out = [{"dataKey": "totAss", "label": "Total Assets", "layout": layout}]
    for key, label in [("totLia", "Total Liabilities"), ("totEq", "Total Equity")]:
        s = {"dataKey": key, "label": label, "layout": layout}
        if stk:
            s["stack"] = stk
        out.append(s)
    return out


def _rotation(gap_cat, gap_bar):
    return [{
        "scaleType": "band",
        "dataKey": "year",
        "categoryGapRatio": gap_cat,
        "barGapRatio": gap_bar,
    }]


component = html.Div(
    [
        dmc.Group(
            [
                dmc.SegmentedControl(
                    id="radial-bars-layout",
                    value="vertical",
                    data=[{"label": "vertical", "value": "vertical"}, {"label": "horizontal", "value": "horizontal"}],
                ),
                dmc.Switch(id="radial-bars-stack", label="stack liabilities + equity", checked=True),
            ],
            mb="md",
        ),
        dms.RadialBarChart(
            id="radial-bars-stacked",
            height=400,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            dataset=dataset,
            series=_series("vertical", True),
            rotationAxis=_rotation(0.3, 0.1),
            grid={"radius": True},
        ),
    ]
)


@callback(
    Output("radial-bars-stacked", "series"),
    Input("radial-bars-layout", "value"),
    Input("radial-bars-stack", "checked"),
)
def update(layout, stack):
    return _series(layout, stack)
```


### Reading clicks



```python
# File: docs/radial_bars/click.py

import json
import os

from dash import html, dcc, Input, Output, callback
import dash_mui_scheduler as dms

# Like the line chart, `onAxisClick` surfaces as the `clickData` output.
component = html.Div(
    [
        dms.RadialBarChart(
            id="radial-bars-click",
            height=380,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            series=[
                {"data": [3, 4, 1, 6, 5], "label": "A", "stack": "total"},
                {"data": [4, 3, 1, 5, 8], "label": "B", "stack": "total"},
                {"data": [4, 2, 5, 4, 1], "label": "C", "stack": "total"},
            ],
            rotationAxis=[{"scaleType": "band", "data": ["Mon", "Tue", "Wed", "Thu", "Fri"]}],
            grid={"radius": True},
        ),
        dcc.Markdown(id="radial-bars-click-out"),
    ]
)


@callback(
    Output("radial-bars-click-out", "children"),
    Input("radial-bars-click", "clickData"),
)
def show_click(data):
    if not data:
        return "_Click anywhere on the chart to see the clicked axis data._"
    return f"```json\n{json.dumps(data, indent=2)}\n```"
```


### Props


#### `RadialBarChart` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `axisHighlight` | dict; optional | Axis highlight behavior: {rotation, radius} where each is one of "none" \| "line" \| "band". Default depends on the layout. `axisHighlight` is a dict with keys: - rotation (a value equal to: 'none', 'line', 'band'; optional) - radius (a value equal to: 'none', 'line', 'band'; optional) |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `clickData` | dict; optional | OUTPUT — set when the user clicks the chart. The clicked axis item and its series values, e.g. {dataIndex, axisValue, seriesValues, event_timestamp}. |
| `colors` | list of strings; optional | Color palette (list of CSS colors) used for the series. |
| `dataset` | list of dicts; optional | Row-oriented data; series reference columns via `dataKey`. |
| `grid` | dict; optional | Show background grid lines: {rotation: bool, radius: bool}. `grid` is a dict with keys: - rotation (boolean; optional) - radius (boolean; optional) |
| `height` | number \| string; default 400 | Chart height in px. Default 400. |
| `hideLegend` | boolean; optional | Hide the legend. |
| `licenseKey` | string; optional | MUI X Premium license key (removes the watermark). |
| `margin` | number \| dict; optional | Margin around the plot — a number or {top,right,bottom,left}. |
| `radiusAxis` | list of dicts; optional | Radius axis config — replaces the cartesian y-axis. A list of axis dicts. |
| `rotationAxis` | list of dicts; optional | Rotation (angular) axis config — replaces the cartesian x-axis. A list of axis dicts. A band axis accepts `categoryGapRatio` / `barGapRatio`, e.g. [{scaleType:"band", data:["2020","2021"], categoryGapRatio:0.3, barGapRatio:0.1}]. |
| `series` | list of dicts; optional | The bar series to plot. Each item is a dict, e.g. {dataKey, label, stack, layout:"vertical"\|"horizontal", color} or {data: [...], label, ...}. |
| `showToolbar` | boolean; optional | Show the default chart toolbar. |
| `skipAnimation` | boolean; optional | Skip the entrance animation. |
| `slotProps` | dict; optional | MUI X charts `slotProps` (plain-object form only), e.g. {"tooltip": {"trigger": "item"}}. |
| `sx` | dict; optional | MUI `sx` styling object (object form only). |
| `width` | number \| string; optional | Chart width in px (defaults to filling the container). |


---

*Source: /radial-bars*

---

<!-- /radial-lines — https://muischeduler.2plot.dev/radial-lines/llms.txt -->

# Radial Lines

> Polar line and area charts for showing trends along periodic values, wrapping MUI X RadialLineChart (Premium, preview).

---



### Overview

`RadialLineChart` plots line (and area) series in **polar coordinates** — a
preview component from `@mui/x-charts-premium`, bundled here alongside the
scheduler. The cartesian x-axis is replaced by a **`rotationAxis`** (angular)
and the y-axis by a **`radiusAxis`** (radial). Data crosses the Dash boundary
as plain dicts and lists, so series, dataset, and axes are just Python objects.


    `RadialLineChart` and `RadialBarChart` come from MUI X **Premium** and are
    **preview** (`Unstable_`) — production-ready, but the API may shift in minor
    releases. Without a `licenseKey` they render a watermark; set
    `MUI_X_LICENSE_KEY` in your environment to remove it.

### Basic radial line

Pass `series` plus a `rotationAxis` / `radiusAxis`. Here a `point` rotation axis
maps each month around the circle, and `grid` draws the background rings/spokes.



```python
# File: docs/radial_lines/basic.py

import os

from dash import html
import dash_mui_scheduler as dms

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

# rotationAxis is the angular (x-like) axis; radiusAxis is the radial (y-like) one.
component = html.Div(
    dms.RadialLineChart(
        id="radial-lines-basic",
        height=400,
        licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
        dataset=dataset,
        series=[{"dataKey": "london", "label": "London precipitation (mm)", "curve": "natural", "showMark": True}],
        rotationAxis=[{"scaleType": "point", "dataKey": "month", "disableLine": True}],
        radiusAxis=[{"disableLine": True}],
        grid={"rotation": True, "radius": True},
    )
)
```


### Marks

Set `showMark: True` on a series to draw marks, and `shape` to pick one of seven
shapes: `circle`, `square`, `diamond`, `cross`, `star`, `triangle`, `wye`.



```python
# File: docs/radial_lines/marks.py

import os

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

dataset = [
    {"month": "Jan", "london": 49}, {"month": "Feb", "london": 38},
    {"month": "Mar", "london": 40}, {"month": "Apr", "london": 44},
    {"month": "May", "london": 49}, {"month": "Jun", "london": 45},
    {"month": "Jul", "london": 45}, {"month": "Aug", "london": 50},
    {"month": "Sep", "london": 49}, {"month": "Oct", "london": 69},
    {"month": "Nov", "london": 59}, {"month": "Dec", "london": 56},
]
SHAPES = ["circle", "square", "diamond", "cross", "star", "triangle", "wye"]


def _series(shape):
    return [{"dataKey": "london", "label": "London precipitation (mm)", "curve": "natural", "showMark": True, "shape": shape}]


# `showMark: True` draws marks; `shape` picks one of 7 mark shapes.
component = html.Div(
    [
        dmc.Select(id="radial-lines-shape", label="Mark shape", value="circle", data=SHAPES, maw=200, mb="md"),
        dms.RadialLineChart(
            id="radial-lines-marks",
            height=400,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            dataset=dataset,
            series=_series("circle"),
            rotationAxis=[{"scaleType": "point", "dataKey": "month", "disableLine": True}],
            radiusAxis=[{"disableLine": True}],
            grid={"rotation": True, "radius": True},
        ),
    ]
)


@callback(
    Output("radial-lines-marks", "series"),
    Input("radial-lines-shape", "value"),
    prevent_initial_call=True,
)
def set_shape(shape):
    return _series(shape)
```


### Continuous rotation axis

The rotation axis can use any scale type. Here a numeric axis spans a full turn
(0 → 2π) and `cos(3θ)` traces a three-petal rose:



```python
# File: docs/radial_lines/continuous.py

import math
import os

from dash import html
import dash_mui_scheduler as dms

# A continuous (numeric) rotation axis. cos(3θ) traces a three-petal rose.
SAMPLES = 200
angles = [i * 2 * math.pi / SAMPLES for i in range(SAMPLES + 1)]
cardioid = [100 * (1 + math.cos(a * 3)) for a in angles]

# `tickInterval` places ticks at the quarter angles; `min`/`max` bound the axis
# to a full turn. (The React demo's `valueFormatter`/`domainLimit` are JS
# functions, which cannot cross the Dash boundary — so ticks show radians and we
# bound the axis with `min`/`max` instead.)
component = html.Div(
    dms.RadialLineChart(
        id="radial-lines-continuous",
        height=400,
        licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
        series=[{"data": cardioid, "label": "Cardioid", "curve": "linear"}],
        rotationAxis=[{
            "data": angles,
            "min": 0,
            "max": 2 * math.pi,
            "tickInterval": [0, math.pi / 2, math.pi, 3 * math.pi / 2, 2 * math.pi],
        }],
        radiusAxis=[{"position": "none"}],
        grid={"rotation": True, "radius": True},
    )
)
```


### Reading clicks

`onAxisClick` is surfaced as the **`clickData`** output — it reports the clicked
rotation-axis item and the series values at that index. Wire it to a callback:



```python
# File: docs/radial_lines/axis_click.py

import json
import os

from dash import html, dcc, Input, Output, callback
import dash_mui_scheduler as dms

# `onAxisClick` surfaces as the `clickData` output — the clicked rotation-axis
# item and the series values at that index.
component = html.Div(
    [
        dms.RadialLineChart(
            id="radial-lines-click",
            height=380,
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            series=[
                {"id": "a", "data": [3, 4, 1, 6, 5], "label": "A", "stack": "total", "highlightScope": {"highlight": "item"}},
                {"id": "b", "data": [4, 3, 1, 5, 8], "label": "B", "stack": "total", "highlightScope": {"highlight": "item"}},
                {"id": "c", "data": [4, 2, 5, 4, 1], "label": "C", "highlightScope": {"highlight": "item"}},
            ],
            rotationAxis=[{"data": [0, 3, 6, 9, 12], "disableLine": True, "disableTicks": True}],
            radiusAxis=[{"position": "none"}],
            grid={"rotation": True, "radius": True},
        ),
        dcc.Markdown(id="radial-lines-click-out"),
    ]
)


@callback(
    Output("radial-lines-click-out", "children"),
    Input("radial-lines-click", "clickData"),
)
def show_click(data):
    if not data:
        return "_Click anywhere on the chart to see the clicked axis data._"
    return f"```json\n{json.dumps(data, indent=2)}\n```"
```


### Props


#### `RadialLineChart` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `axisHighlight` | dict; optional | Axis highlight behavior: {rotation, radius} where each is one of "none" \| "line" \| "band". Default {rotation: "line"}. `axisHighlight` is a dict with keys: - rotation (a value equal to: 'none', 'line', 'band'; optional) - radius (a value equal to: 'none', 'line', 'band'; optional) |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `clickData` | dict; optional | OUTPUT — set when the user clicks the chart. The clicked rotation-axis item and its series values, e.g. {dataIndex, axisValue, seriesValues, event_timestamp}. |
| `colors` | list of strings; optional | Color palette (list of CSS colors) used for the series. |
| `dataset` | list of dicts; optional | Row-oriented data; series reference columns via `dataKey`. |
| `disableLineItemHighlight` | boolean; optional | Disable the per-item line highlight indicator. |
| `grid` | dict; optional | Show background grid lines: {rotation: bool, radius: bool}. `grid` is a dict with keys: - rotation (boolean; optional) - radius (boolean; optional) |
| `height` | number \| string; default 400 | Chart height in px. Default 400. |
| `hideLegend` | boolean; optional | Hide the legend. |
| `licenseKey` | string; optional | MUI X Premium license key (removes the watermark). |
| `margin` | number \| dict; optional | Margin around the plot — a number or {top,right,bottom,left}. |
| `radiusAxis` | list of dicts; optional | Radius axis config — replaces the cartesian y-axis. A list of axis dicts, e.g. [{disableLine:True, minRadius:10, min:0, position:"none"}]. |
| `rotationAxis` | list of dicts; optional | Rotation (angular) axis config — replaces the cartesian x-axis. A list of axis dicts, e.g. [{scaleType:"point", dataKey:"month", disableLine:True}]. |
| `series` | list of dicts; optional | The line/area series to plot. Each item is a dict, e.g. {dataKey, label, curve, showMark, shape, area, closePath, stack, highlightScope, color} or {data: [...], label, ...}. |
| `showToolbar` | boolean; optional | Show the default chart toolbar. |
| `skipAnimation` | boolean; optional | Skip the entrance animation. |
| `slotProps` | dict; optional | MUI X charts `slotProps` (plain-object form only). For example {"tooltip": {"trigger": "item"}} makes the tooltip follow the hovered mark/line instead of the whole rotation axis. |
| `sx` | dict; optional | MUI `sx` styling object (object form only). |
| `width` | number \| string; optional | Chart width in px (defaults to filling the container). |


---

*Source: /radial-lines*

---

<!-- /recurrence — https://muischeduler.2plot.dev/recurrence/llms.txt -->

# Recurrence

> Build recurring events in dash-mui-scheduler with EventCalendarPremium using RRULE strings, RRULE objects and exception dates.

---



### Recurrence (Premium)

Recurring events are a **Premium** feature. Use `dms.EventCalendarPremium`
instead of `dms.EventCalendar` — it is identical to the Community calendar but
adds a recurrence engine, a Recurrence tab in the edit dialog, and two new
per-event keys: `rrule` and `exDates`.


    `EventCalendarPremium` requires a MUI X Premium license key. Pass it via
    `licenseKey=os.environ.get("MUI_X_LICENSE_KEY", "")`. Without a valid key
    the calendar still renders and is fully interactive, but a MUI watermark is
    shown over it. That is expected — supply a real key to remove it.

The data boundary is unchanged from the Community calendar. `events` is a list
of plain dicts and is **both input and output**: the component writes the full
array back on every create, move, resize or delete. Dates are ISO **strings**
(for example `"2024-01-15T10:00:00"` for wall time, or a trailing `Z` for UTC),
never Python `datetime` objects. Recurring series are stored as a *single*
event dict carrying an `rrule`; the calendar expands it into occurrences for
display only.

### Recurrence as an RRULE string

The simplest form sets `event["rrule"]` to an RFC-5545 RRULE string. The event
below repeats every week on Monday, Wednesday and Friday:

```text
FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR
```

`FREQ` is one of `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. `BYDAY` codes are
`MO TU WE TH FR SA SU` (for monthly rules you may prefix an ordinal, e.g.
`2TU` = second Tuesday, `-1FR` = last Friday). You can also add `COUNT`,
`UNTIL` (an ISO string), `BYMONTHDAY` (1–31) and `BYMONTH` (1–12).



```python
# File: docs/recurrence/recurrence_string.py

import os

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

# Premium events may carry an `rrule`. The RRULE string form follows RFC-5545:
# this event recurs every week on Monday, Wednesday and Friday at 10:00.
events = [
    {
        "id": "standup",
        "title": "Team Standup",
        "start": "2024-01-15T10:00:00",
        "end": "2024-01-15T10:30:00",
        "color": "blue",
        "rrule": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR",
    },
    {
        "id": "review",
        "title": "Sprint Review",
        "start": "2024-01-19T15:00:00",
        "end": "2024-01-19T16:00:00",
        "color": "purple",
    },
]

component = dmc.Stack(
    [
        dms.EventCalendarPremium(
            id="recurrence-string-cal",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
        dmc.Text("Last action:", fw=600, size="sm"),
        dmc.Code(id="recurrence-string-action", block=True),
    ],
    gap="sm",
)


@callback(
    Output("recurrence-string-action", "children"),
    Input("recurrence-string-cal", "lastAction"),
)
def show_action(last_action):
    if not last_action:
        return "No action yet — drag, create, resize or delete an occurrence."
    return f"{last_action.get('type')} @ {last_action.get('event_timestamp')}"
```


### Recurrence as an object + exception dates

Instead of a string, `rrule` may be an object. This is convenient when you are
building the rule programmatically:

```python
{"freq": "WEEKLY", "interval": 1, "byDay": ["MO", "WE", "FR"], "count": 10}
```

The keys mirror the RRULE parts: `freq`, `interval`, `byDay`, `byMonthDay`,
`byMonth`, `count` and `until`. To remove individual occurrences from a series
without breaking the rule, add `exDates` — a list of ISO strings naming the
start times to skip:

```python
"exDates": ["2024-01-17T08:00:00", "2024-01-19T08:00:00"]
```

The example below recurs every weekday for ten occurrences, with two dates
excluded. Note that even though the calendar shows many occurrences, the
`events` output still contains a single definition dict.



```python
# File: docs/recurrence/recurrence_object.py

import os

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

# `rrule` may also be an object instead of a string. Here the event recurs
# every weekday morning for a total of 10 occurrences, but two specific
# dates are removed from the series via `exDates` (ISO strings).
events = [
    {
        "id": "yoga",
        "title": "Morning Yoga",
        "start": "2024-01-15T08:00:00",
        "end": "2024-01-15T08:45:00",
        "color": "green",
        "rrule": {
            "freq": "WEEKLY",
            "interval": 1,
            "byDay": ["MO", "TU", "WE", "TH", "FR"],
            "count": 10,
        },
        "exDates": [
            "2024-01-17T08:00:00",
            "2024-01-19T08:00:00",
        ],
    },
]

component = dmc.Stack(
    [
        dms.EventCalendarPremium(
            id="recurrence-object-cal",
            licenseKey=os.environ.get("MUI_X_LICENSE_KEY", ""),
            events=events,
            defaultView="week",
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
        dmc.Text("Occurrence count in events output:", fw=600, size="sm"),
        dmc.Code(id="recurrence-object-count", block=True),
    ],
    gap="sm",
)


@callback(
    Output("recurrence-object-count", "children"),
    Input("recurrence-object-cal", "events"),
)
def show_count(events_out):
    # The component writes the full event array back on every edit. The
    # recurring definition stays a single dict with `rrule`/`exDates`;
    # the UI expands it into occurrences for display only.
    return f"{len(events_out or [])} stored event definition(s)"
```


### Reading edits back

As with the Community calendar, you do not need a callback for the calendar to
be interactive — drags, creates and deletes round-trip through Dash on their
own. Add a callback only to *display* outputs. The `lastAction` output is
`{type, event, event_timestamp}`, where `type` is one of `create`, `update`,
`delete`, `move`, `resize` or `change`. The first example above wires
`lastAction` into a code block so you can watch edits as they happen.

### EventCalendarPremium props

`EventCalendarPremium` accepts every `EventCalendar` prop plus `licenseKey`,
and its `events` may include `rrule` and `exDates`.


#### `EventCalendarPremium` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences. `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Render timezone: IANA name, or "default"/"locale"/"UTC". Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | Default color palette for all events (overridable). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables defaults; an object sets {interaction, duration (minutes)}. `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start`, `end` (ISO strings). Premium events may also carry `rrule` (recurrence) and `exDates`. INPUT + OUTPUT (round-trips on every change). `events` is a list of dicts with keys: - id (string \| number; required) - title (string; required) - start (string; required) - end (string; required) - description (string; optional) - timezone (string; optional) - resource (string \| number; optional) - rrule (string \| dict; optional): Recurrence rule — RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq:"DAILY\|WEEKLY\|MONTHLY\|YEARLY", interval, byDay, byMonthDay, byMonth, count, until}. - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional) - readOnly (boolean; optional) - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional) - draggable (boolean; optional) - resizable (boolean \| a value equal to: 'start', 'end'; optional) - className (string; optional) - extractedFromId (string \| number; optional) |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type, event, event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `licenseKey` | string; optional | MUI X Premium license key. Set once to remove the watermark and unlock Premium features (recurrence). Read from an environment variable on the server and pass it in. |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide it. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT. |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Also an OUTPUT. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /recurrence*

---

<!-- /resources — https://muischeduler.2plot.dev/resources/llms.txt -->

# Resources

> Organize EventCalendar events by resource — define resources with colors, assign events, control which resources are visible, and require a resource on creation.

---



### Resources

A **resource** is a thing you schedule against — a person, a room, a machine, a team.
In `dms.EventCalendar` you declare resources once, then tag each event with a
`resource` id. The calendar colors events by their resource and lets you show or
hide a subset of resources without touching the underlying `events` array.

Resources are passed as a list of dicts through the `resources` prop. A resource dict
accepts:

| key | type | meaning |
| --- | --- | --- |
| `id` | str | unique resource id, referenced by `event["resource"]` (required) |
| `title` | str | label shown in the UI (required) |
| `eventColor` | palette name | color for this resource's events (default `teal`) |
| `children` | list | nested child resources |
| `areEventsDraggable` | bool | override drag for this resource's events |
| `areEventsResizable` | bool | override resize for this resource's events |
| `areEventsReadOnly` | bool | make this resource's events read-only |

The 11 palette names for `eventColor` are: `red`, `pink`, `purple`, `indigo`,
`blue`, `teal`, `green`, `lime`, `amber`, `orange`, `grey`.


    `events` is both an input and an output. Dates are ISO strings
    (e.g. `"2024-01-15T10:00:00"`). Each event links to a resource through its
    `resource` key, which must match a resource `id`. When a user creates, moves,
    resizes, or deletes an event, the component writes the whole new array back to
    `events` — no callback required for that round-trip.

### Defining resources and assigning events

Give each resource an `id`, a `title`, and an `eventColor`. Then set
`event["resource"]` to the resource's `id`. The calendar paints each event with its
resource color automatically — you do not need to set a per-event `color`.



```python
# File: docs/resources/resources_basic.py

from dash import html
import dash_mui_scheduler as dms

# Resources are the categories you schedule against. Each one carries its own
# eventColor; events inherit that color by pointing `resource` at the id.
resources = [
    {"id": "design", "title": "Design", "eventColor": "purple"},
    {"id": "engineering", "title": "Engineering", "eventColor": "blue"},
    {"id": "marketing", "title": "Marketing", "eventColor": "amber"},
]

# Dates are ISO strings. Each event names its resource via the `resource` key,
# which must match one of the resource ids above.
events = [
    {
        "id": "1",
        "title": "Wireframe review",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T10:30:00",
        "resource": "design",
    },
    {
        "id": "2",
        "title": "API sprint planning",
        "start": "2024-01-15T11:00:00",
        "end": "2024-01-15T12:00:00",
        "resource": "engineering",
    },
    {
        "id": "3",
        "title": "Launch campaign sync",
        "start": "2024-01-16T14:00:00",
        "end": "2024-01-16T15:00:00",
        "resource": "marketing",
    },
    {
        "id": "4",
        "title": "Component build",
        "start": "2024-01-17T10:00:00",
        "end": "2024-01-17T13:00:00",
        "resource": "engineering",
    },
]

component = html.Div(
    dms.EventCalendar(
        id="resources-basic-cal",
        events=events,
        resources=resources,
        defaultView="week",
        defaultVisibleDate="2024-01-15",
        height=600,
    )
)
```


### Resource colors

Color comes from the resource's `eventColor`. Any event whose `resource` points at
that resource inherits the color, which keeps a whole category visually consistent.
An individual event can still override with its own `color` key, but leaving it off
lets the resource drive the palette — change the resource color in one place and
every matching event follows.

### Showing and hiding resources

`visibleResources` is a mapping of resource id to a boolean. A resource is shown
unless it is explicitly set to `False`, so you only list the ones you want to hide
(or list all of them and flip values). It comes in two forms:

- `defaultVisibleResources` — **uncontrolled**: an initial value the component then
  manages on its own.
- `visibleResources` — **controlled** in and out: drive it from a callback and read
  user changes back.

The example below wires a `dmc.ChipGroup` to `visibleResources`. Each chip toggles
one resource on or off; the calendar updates immediately and the events for hidden
resources disappear without being removed from `events`.



```python
# File: docs/resources/resource_visibility.py

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

resources = [
    {"id": "room-a", "title": "Room A", "eventColor": "teal"},
    {"id": "room-b", "title": "Room B", "eventColor": "orange"},
    {"id": "room-c", "title": "Room C", "eventColor": "indigo"},
]

events = [
    {
        "id": "1",
        "title": "Standup",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T09:30:00",
        "resource": "room-a",
    },
    {
        "id": "2",
        "title": "Design crit",
        "start": "2024-01-15T13:00:00",
        "end": "2024-01-15T14:00:00",
        "resource": "room-b",
    },
    {
        "id": "3",
        "title": "Client call",
        "start": "2024-01-16T11:00:00",
        "end": "2024-01-16T12:00:00",
        "resource": "room-c",
    },
    {
        "id": "4",
        "title": "Retro",
        "start": "2024-01-17T15:00:00",
        "end": "2024-01-17T16:00:00",
        "resource": "room-a",
    },
]

# Start with every room visible. `visibleResources` is a {resource_id: bool} map.
all_ids = [r["id"] for r in resources]

component = html.Div(
    dmc.Stack(
        [
            dmc.Text("Toggle rooms — hidden rooms keep their events in `events`."),
            dmc.ChipGroup(
                id="resources-visibility-chips",
                multiple=True,
                value=all_ids,
                children=dmc.Group(
                    [dmc.Chip(r["title"], value=r["id"]) for r in resources]
                ),
            ),
            dms.EventCalendar(
                id="resources-visibility-cal",
                events=events,
                resources=resources,
                defaultView="week",
                defaultVisibleDate="2024-01-15",
                # Controlled in + out: the callback drives which rooms show.
                visibleResources={rid: True for rid in all_ids},
                height=600,
            ),
        ],
        gap="sm",
    )
)


@callback(
    Output("resources-visibility-cal", "visibleResources"),
    Input("resources-visibility-chips", "value"),
)
def toggle_resources(checked):
    checked = checked or []
    # Explicitly mark each room True/False so unchecked rooms hide.
    return {rid: (rid in checked) for rid in all_ids}
```



    Because `visibleResources` is controlled, a user toggling resources through the
    calendar's own side panel would also flow back into your callback's input. Keep a
    single source of truth (here, the `ChipGroup` value) so the two stay in sync.

### Requiring a resource

Set `shouldEventRequireResource=True` to force every event to belong to a resource.
With it on, the create flow will not let a user save an event without picking a
resource — useful when "unassigned" is not a valid state (for example, a room-booking
calendar where every booking needs a room).

### Props reference


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /resources*

---

<!-- /responsive — https://muischeduler.2plot.dev/responsive/llms.txt -->

# Responsive

> Mobile-aware UX for the Event Calendar — a responsive event drawer, an auto-collapsing side panel, and panning the time grid to the current time.

---



### Overview

These props are Dash-wrapper additions on top of the MUI X Scheduler. They make
`EventCalendar` (and `EventCalendarPremium`) behave better on phones and small
screens, and let the day/week grid open already scrolled to "now".

| Prop | Default | What it does |
| --- | --- | --- |
| `eventDialogVariant` | `"drawer"` | Presents the event editor as a responsive drawer instead of a floating dialog. |
| `responsiveSidePanel` | `True` | Side panel starts open on wide screens, collapsed on phones. |
| `mobileBreakpoint` | `768` | Pixel width below which the mobile layout kicks in. |
| `scrollToCurrentTime` | `False` | Pan the day/week grid so the current-time line is centered on first render. |

### The event drawer

The Scheduler renders its event editor as a floating, draggable dialog that is
positioned next to the event you clicked — which tends not to "snap" anywhere
and reads poorly on a phone. With `eventDialogVariant="drawer"` (the default),
the wrapper restyles that editor into a drawer:

- **Desktop** (≥ `mobileBreakpoint`): a right-anchored, full-height panel.
- **Mobile** (< `mobileBreakpoint`): a bottom sheet taking 80% of the viewport
  height, with the form body scrolling and the title/Save/Delete pinned.

Click any event in the calendar below (try it at a narrow window width too) to
see the drawer:


Set `eventDialogVariant="dialog"` to restore the library's original floating
dialog. If your app has a fixed header, set `eventDialogTopOffset` to its height
(in px) so the desktop drawer starts below it and lines up with your sidebar
instead of covering the header — this documentation site uses `70`.


    The MUI X Scheduler beta does not expose a slot to swap the editor, so this
    is a CSS restyle of the built-in dialog (which is portaled to `<body>` with
    stable `MuiEventCalendar-eventDialog*` classes) — not a separate
    `dmc.Drawer`. It keeps all of the Scheduler's own validation and recurrence
    UI while fixing the placement and mobile layout. A fully custom editor would
    require forking the component and is intentionally avoided while the
    Scheduler is in beta.

### The side panel

`responsiveSidePanel` decides the side panel's initial state from the viewport
width: open at `mobileBreakpoint` and above, collapsed below it. This only sets
the **initial** state — the user can still toggle it, and you can override it
entirely by pinning `isSidePanelOpen` in `preferences` / `defaultPreferences`.

```python
dms.EventCalendar(
    id="cal",
    events=events,
    responsiveSidePanel=True,   # default
    mobileBreakpoint=768,       # phones (< 768px) start collapsed
)
```

### Panning to the current time

In the `day` and `week` views the grid normally opens at midnight. Set
`scrollToCurrentTime=True` and the grid scrolls so the red current-time line is
centered on first render (and again when you switch into a time view). It works
together with `showCurrentTimeIndicator` (on by default):


```python
# File: docs/responsive/now_indicator.py

import datetime

from dash import html
import dash_mui_scheduler as dms

_today = datetime.date.today()


def _iso(day, hour, minute=0):
    return datetime.datetime.combine(
        day, datetime.time(hour, minute)
    ).strftime("%Y-%m-%dT%H:%M:%S")


events = [
    {"id": "rni-1", "title": "Morning sync", "start": _iso(_today, 9), "end": _iso(_today, 9, 30), "color": "blue"},
    {"id": "rni-2", "title": "Focus block", "start": _iso(_today, 11), "end": _iso(_today, 12, 30), "color": "teal"},
    {"id": "rni-3", "title": "1:1", "start": _iso(_today, 15), "end": _iso(_today, 15, 45), "color": "purple"},
]

# `scrollToCurrentTime` pans the week grid so the red "now" line is centered on
# first render — no more scrolling up from midnight. It pairs with the default
# `showCurrentTimeIndicator`.
component = html.Div(
    dms.EventCalendar(
        id="responsive-now-cal",
        events=events,
        defaultView="week",
        defaultVisibleDate=_today.isoformat(),
        scrollToCurrentTime=True,
        showCurrentTimeIndicator=True,
        height=600,
    )
)
```


```python
dms.EventCalendar(
    id="cal",
    events=events,
    defaultView="week",
    scrollToCurrentTime=True,        # pan to now on load
    showCurrentTimeIndicator=True,   # the red "now" line (default)
)
```

In `month` and `agenda` views there is no time grid, so the prop is simply
ignored.

### Props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /responsive*

---

<!-- /terms — https://muischeduler.2plot.dev/terms/llms.txt -->

# Terms of Use

> Terms of use for dash-mui-scheduler: what this site is, what it is not, and the terms the documentation, the component library and its code examples are offered under.

## What this site is

dash-mui-scheduler is documentation, published by Pip Install Python LLC. It describes a
component library for [Plotly Dash](https://dash.plotly.com/) that wraps the
MUI X Scheduler, and it is reference material: it is not advice, it is not a
service, and nothing on it is a commitment to keep any particular behaviour
working in your project.

## The documentation, the library, and the code in it

The prose and the code examples are published so you can read them, run them
and copy them. The source repository states the licence the code is offered
under, and that licence — not this page — is what governs your use of it:

- [https://github.com/pip-install-python/dash-mui-scheduler](https://github.com/pip-install-python/dash-mui-scheduler)

Everything here is offered **as is**, without warranty of any kind. Running a
code example against your own data, in your own deployment, is your decision
and your responsibility.

## The components this library wraps

`dash-mui-scheduler` is a Dash wrapper. The scheduler and chart components it
renders come from MUI X, which has its own licence terms — the Community
components are MIT, and the Pro and Premium components require a licence from
MUI. Installing this package does not grant you one, and the examples on this
site that use Premium features run against a licence key held by this site
and not distributed with the package. Whether your use of the underlying MUI
components is licensed is between you and MUI.

## Accounts

Some pages can be gated behind a sign-in. An account exists so the site can
tell whether you may see a page; it is not a subscription and carries no
entitlement. Accounts may be ended at any time, by you or by us, and the
[Privacy](/privacy) page describes what is kept while one exists.

## Links to other sites

This site links to other sites in the 2plot network, to MUI, and to
third-party projects. Those sites have their own terms and their own privacy
practices, and this page does not speak for them.

## Changes

These terms change when the site does. The change history for the whole site,
including this page, is the [Changelog](/changelog) and the repository's
commit history — there is no separate archive of previous versions, because
the repository already is one.

## Contact

Questions about these terms: the [Discord](https://discord.gg/e5s5uHWUHH) or an issue on
[the repository](https://github.com/pip-install-python/dash-mui-scheduler).

---

<!-- /views — https://muischeduler.2plot.dev/views/llms.txt -->

# Views

> Switch, restrict, and control the day, week, month, and agenda views of the dash-mui-scheduler EventCalendar.

---



### Calendar views

`dms.EventCalendar` ships four built-in views: **day**, **week**, **month**, and
**agenda**. By default all four are available and the calendar opens on
`"week"`. A standalone calendar already lets the user switch views with its
built-in view switcher — you only need a callback when you want to *restrict*,
*preset*, or *programmatically drive* the view from elsewhere in your layout.

The two props that govern this are `view` (the currently active view) and
`views` (the list of views the user is allowed to choose from).


    `view` is a plain string — one of `"day"`, `"week"`, `"month"`,
    `"agenda"`. Keep `defaultVisibleDate` an ISO date string (here
    `"2024-01-15"`) so the seeded events land on screen at load.

### Available views

The four view names are fixed:

- `"day"` — a single day, hour by hour.
- `"week"` — seven day columns (the default).
- `"month"` — a month grid.
- `"agenda"` — a chronological list of upcoming events.

You set the starting view with `defaultView` (uncontrolled) or `view`
(controlled). `defaultView` defaults to `"week"`.

### Restricting views

Pass `views=[...]` to limit which view buttons the calendar renders. Anything
omitted from the list is hidden from the switcher. Combine it with
`defaultView` to choose which of the allowed views shows first — just make sure
`defaultView` is one of the values in `views`.



```python
# File: docs/views/views_basic.py

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

# Dates are ISO strings (wall time, no Z). The component writes the full
# `events` array back to Dash on every create / move / resize / delete.
events = [
    {"id": "1", "title": "Sprint planning", "start": "2024-01-15T09:00:00", "end": "2024-01-15T10:30:00", "color": "blue"},
    {"id": "2", "title": "Design review", "start": "2024-01-16T13:00:00", "end": "2024-01-16T14:00:00", "color": "purple"},
    {"id": "3", "title": "1:1", "start": "2024-01-17T11:00:00", "end": "2024-01-17T11:30:00", "color": "green"},
]

ALL_VIEWS = ["day", "week", "month", "agenda"]

# `views` controls which view buttons the calendar offers. The CheckboxGroup
# below drives it live — uncheck a view and its button disappears from the
# calendar's toolbar. `view` (controlled) is kept on a value that still exists.
component = html.Div(
    [
        dmc.CheckboxGroup(
            id="views-restrict-group",
            label="View buttons to offer",
            description="Toggle which views appear in the calendar's toolbar.",
            value=ALL_VIEWS,
            mb="md",
            children=dmc.Group(
                [dmc.Checkbox(label=v.capitalize(), value=v) for v in ALL_VIEWS],
                mt="xs",
            ),
        ),
        dms.EventCalendar(
            id="views-restrict-cal",
            events=events,
            views=ALL_VIEWS,
            view="week",
            defaultVisibleDate="2024-01-15",
            height=560,
        ),
    ]
)


@callback(
    Output("views-restrict-cal", "views"),
    Output("views-restrict-cal", "view"),
    Input("views-restrict-group", "value"),
    State("views-restrict-cal", "view"),
    prevent_initial_call=True,
)
def restrict_views(selected, current_view):
    # Keep the canonical order; always leave at least one view available.
    views = [v for v in ALL_VIEWS if v in selected] or ["week"]
    # If the active view was just removed, fall back to the first remaining one.
    view = current_view if current_view in views else views[0]
    return views, view
```


### Controlling the active view

To drive the view from your own controls, treat `view` as a controlled
prop: it is both an input and an output. The example below wires a
`dmc.SegmentedControl` to the calendar's `view` prop. The first callback pushes
the segmented control's value into the calendar; the second reads `view` back
out — so when the user clicks the calendar's own view buttons, the segmented
control follows along. Both callbacks use `prevent_initial_call=True` to avoid a
fight on load.



```python
# File: docs/views/controlled_view.py

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

events = [
    {
        "id": "1",
        "title": "Standup",
        "start": "2024-01-15T09:00:00",
        "end": "2024-01-15T09:15:00",
        "color": "teal",
    },
    {
        "id": "2",
        "title": "Workshop",
        "start": "2024-01-16T14:00:00",
        "end": "2024-01-16T16:00:00",
        "color": "orange",
    },
    {
        "id": "3",
        "title": "Retro",
        "start": "2024-01-18T15:00:00",
        "end": "2024-01-18T16:00:00",
        "color": "pink",
    },
]

# `view` is controlled IN + OUT. The SegmentedControl writes it, and the
# calendar writes it back when the user clicks a built-in view button —
# the second callback keeps the SegmentedControl in sync.
component = dmc.Stack(
    [
        dmc.SegmentedControl(
            id="views-controlled-segmented",
            value="week",
            data=[
                {"label": "Day", "value": "day"},
                {"label": "Week", "value": "week"},
                {"label": "Month", "value": "month"},
                {"label": "Agenda", "value": "agenda"},
            ],
        ),
        dms.EventCalendar(
            id="views-controlled-cal",
            events=events,
            view="week",
            defaultVisibleDate="2024-01-15",
            height=600,
        ),
    ],
    gap="sm",
)


@callback(
    Output("views-controlled-cal", "view"),
    Input("views-controlled-segmented", "value"),
    prevent_initial_call=True,
)
def set_view(value):
    return value


@callback(
    Output("views-controlled-segmented", "value"),
    Input("views-controlled-cal", "view"),
    prevent_initial_call=True,
)
def read_view(view):
    return view
```



    `view` has an uncontrolled twin, `defaultView`. Use `defaultView` for a
    fixed starting view you never read back, and `view` when you need
    two-way binding like the example above. Don't set both.

### EventCalendar props


#### `EventCalendar` props

| prop | type | description |
|---|---|---|
| `id` | string; optional | The id used to identify this component in Dash callbacks. |
| `areEventsDraggable` | boolean; optional | Allow drag-to-reschedule. Default True. |
| `areEventsResizable` | boolean \| a value equal to: 'start', 'end'; optional | Allow resize (bool, or restrict to "start"/"end"). Default True. |
| `canDragEventsFromTheOutside` | boolean; optional | Allow external events to be dragged in. Default False. |
| `canDropEventsToTheOutside` | boolean; optional | Allow events to be dragged out of the calendar. Default False. |
| `className` | string; optional | CSS class applied to the wrapping div. |
| `defaultPreferences` | dict; optional | Uncontrolled initial preferences (same shape as `preferences`). `defaultPreferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `defaultView` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Uncontrolled initial view. Default "week". |
| `defaultVisibleDate` | string; optional | Uncontrolled initial visible date (ISO string). Default today. |
| `defaultVisibleResources` | dict; optional | Uncontrolled initial resource visibility map. Default {} (all visible). |
| `displayTimezone` | string; optional | Timezone used to render events: an IANA name ("America/New_York"), or "default" / "locale" / "UTC". Render-only — events keep their own data timezone. Default "default". |
| `eventColor` | a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional | The default color palette used for all events. Overridden per resource (`eventColor`) and per event (`color`). Default "teal". |
| `eventCreation` | dict; optional | Configures event creation. `False` disables it; `True` enables it with defaults; an object sets the interaction and default duration (minutes). `eventCreation` is a boolean \| dict with keys: - interaction (a value equal to: 'click', 'double-click'; optional) - duration (number; optional) |
| `eventDialogTopOffset` | number; optional | On desktop, inset the event drawer this many px from the top — e.g. set it to your fixed app header's height so the drawer lines up with a sidebar instead of covering the header. Default 0. |
| `eventDialogVariant` | a value equal to: 'drawer', 'dialog'; default 'drawer' | How the event editor is presented. "drawer" (default) restyles the built-in dialog into a responsive drawer — right-anchored on desktop, an 80%-height bottom sheet on mobile (below `mobileBreakpoint`), with a scrollable body and pinned header/actions. "dialog" keeps the library's default floating, draggable dialog. |
| `events` | list of dicts; optional | The events to render. Each event is a dict with at least `id`, `title`, `start` and `end` (ISO strings). This is BOTH an input and an output: the calendar writes the full array back on every create / edit / move / resize / delete. `events` is a list of dicts with keys: - id (string \| number; required): Unique id (string or number). - title (string; required): Event title. - start (string; required): Start date-time, ISO string. "Z" suffix = UTC instant. - end (string; required): End date-time, ISO string. "Z" suffix = UTC instant. - description (string; optional): Optional longer description (shown in the event dialog). - timezone (string; optional): IANA timezone the wall-time start/end are interpreted in. - resource (string \| number; optional): Id of the resource this event belongs to. - rrule (string \| dict; optional): Recurrence rule — an RFC-5545 RRULE string ("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") or an object {freq, interval, byDay, byMonthDay, byMonth, count, until}. Recurrence is a Premium feature (use EventCalendarPremium). - exDates (list of strings; optional): Exception dates (ISO strings) excluded from the recurrence. - allDay (boolean; optional): Whether the event spans the whole day. - readOnly (boolean; optional): Whether the event cannot be edited / dragged / resized. - color (a value equal to: 'red', 'pink', 'purple', 'indigo', 'blue', 'teal', 'green', 'lime', 'amber', 'orange', 'grey'; optional): Event color (overrides resource + component color). - draggable (boolean; optional): Per-event drag override. - resizable (boolean \| a value equal to: 'start', 'end'; optional): Per-event resize override (bool or which edge). - className (string; optional): Custom CSS class for the event element. - extractedFromId (string \| number; optional): Id of the event this one was split from. |
| `height` | number \| string; default 600 | Height of the wrapping container (the calendar fills it). Default 600. |
| `lastAction` | dict; optional | Convenience OUTPUT describing the most recent change to `events`: {type: "create"\|"update"\|"delete"\|"move"\|"resize"\|"change", event: the affected event (or None), event_timestamp}. `lastAction` is a dict with keys: - type (string; optional) - event (dict; optional) - event_timestamp (number; optional) |
| `localeText` | dict; optional | Override UI label strings (a partial map of translation keys). |
| `mobileBreakpoint` | number; default 768 | Width (px) below which the UI switches to its mobile layout. Default 768. |
| `preferences` | dict; optional | Controlled user preferences. Also an OUTPUT. {ampm, weekStartsOn (0=Sun..6=Sat), showWeekends, showWeekNumber, isSidePanelOpen, showEmptyDaysInAgenda}. `preferences` is a dict with keys: - ampm (boolean; optional) - weekStartsOn (a value equal to: 0, 1, 2, 3, 4, 5, 6; optional) - showWeekends (boolean; optional) - showWeekNumber (boolean; optional) - isSidePanelOpen (boolean; optional) - showEmptyDaysInAgenda (boolean; optional) |
| `preferencesMenuConfig` | dict; optional | Which items appear in the preferences menu, or `False` to hide the menu. `preferencesMenuConfig` is a a value equal to: false \| dict with keys: - toggleWeekendVisibility (boolean; optional) - toggleWeekNumberVisibility (boolean; optional) - toggleAmpm (boolean; optional) - toggleEmptyDaysInAgenda (boolean; optional) - toggleWeekStartsOn (boolean; optional) |
| `readOnly` | boolean; optional | Global read-only mode (disables create / drag / resize / dialog). |
| `resources` | list of dicts; optional | Resources events can be assigned to (supports nested `children`). |
| `responsiveSidePanel` | boolean; default True | When True (default), the side panel starts open on wide screens and collapsed below `mobileBreakpoint` on first render — unless you pin `isSidePanelOpen` via `preferences` / `defaultPreferences`. |
| `scrollToCurrentTime` | boolean; default False | In the day / week views, scroll the time grid on first render (and on view change) so the current-time indicator is centered in view. Pairs with `showCurrentTimeIndicator`. Default False. |
| `shouldEventRequireResource` | boolean; optional | Require every event to be assigned to a resource. Default False. |
| `showCurrentTimeIndicator` | boolean; optional | Show the current-time indicator line in time views. Default True. |
| `sx` | dict; optional | MUI `sx` styling object applied to the calendar (object form only). |
| `view` | a value equal to: 'day', 'week', 'month', 'agenda'; optional | Controlled active view. Also an OUTPUT (updated on view change). |
| `views` | list of a value equal to: 'day', 'week', 'month', 'agenda's; optional | Which views are offered. Default ["day","week","month","agenda"]. |
| `visibleDate` | string; optional | Controlled visible date (ISO string). Drives which date range is shown. Also an OUTPUT — written back (ISO string) when the user navigates. |
| `visibleResources` | dict; optional | Controlled resource visibility map {resourceId: bool}. Also an OUTPUT. |


---

*Source: /views*
