reflex-schedule-x
Schedule-X event calendar for Reflex — a modern alternative to FullCalendar and react-big-calendar, driven entirely from Python.
- 6 views: day, week, month grid, month agenda, week agenda and list.
- Events in Reflex state: pass a
list[dict]with plain date strings; edits in Python update the running calendar. - Every callback (
on_event_click,on_range_update,on_click_date_time,on_view_change, ...) as typed Reflex event triggers. - Plugins: events service, calendar controls, current time, event modal, scroll controller, event recurrence (RRULE / EXDATE), iCalendar import, timezone select.
- Custom components: fill any Schedule-X slot (
timeGridEvent,monthGridEvent,eventModal, header slots, ...) with Reflex components. - i18n & timezones: 37 locales, translation overrides, IANA timezones, dark mode,
defaultandshadcnthemes. - Standalone date picker and time picker components.
- Imperative API (
ScheduleXAPI) forrx.call_scriptdriven control.
Bundles @schedule-x/* 4.8.0 (open-source packages). Premium plugins (drag & drop, resize, drag-to-create,
resource scheduler, ...) are not included.
Installation
pip install reflex-schedule-x
# or
uv add reflex-schedule-x
Requires Python 3.11+ and reflex>=0.10.0. The npm packages are installed automatically by Reflex.
Quick start
import reflex as rx
from reflex_schedule_x import calendar_type, schedule_x
class State(rx.State):
events: list[dict] = [
{
"id": "1",
"title": "Coffee with John",
"start": "2026-09-17 10:05",
"end": "2026-09-17 10:35",
"calendarId": "work",
},
{"id": "2", "title": "Ski trip", "start": "2026-09-19", "end": "2026-09-21"},
]
selected: dict = {}
@rx.event
def on_event_click(self, event: dict):
self.selected = event
def index():
return schedule_x(
events=State.events,
calendars={"work": calendar_type("work", "#f91c45", "#ffd2dc", "#59000d", label="Work")},
views=["day", "week", "month-grid", "month-agenda"],
default_view="week",
timezone="America/Caracas",
locale="es-ES",
is_dark=rx.color_mode_cond(light=False, dark=True),
event_modal=True,
current_time_indicator=True,
on_event_click=State.on_event_click,
height="800px",
)
app = rx.App()
app.add_page(index)
Dates and times
Schedule-X uses the Temporal API. The component converts strings for you:
| Value you pass | Meaning |
|---|---|
"2026-09-17" |
All-day date (Temporal.PlainDate) |
"2026-09-17 10:00" or "2026-09-17T10:00" |
Wall-clock time in the calendar timezone |
"2026-09-17T10:00:00-04:00" / "...Z" |
Absolute instant, shown in the calendar timezone |
"2026-09-17T10:00:00+02:00[Europe/Berlin]" |
RFC 9557 zoned date-time |
Payloads sent back to Python use "YYYY-MM-DD" for dates and "YYYY-MM-DD HH:mm" (calendar timezone) for
date-times, so they round-trip with your state. Set datetime_format="iso" to receive RFC 9557 strings instead.
Helpers: to_sx_datetime(date | datetime), to_sx_date(...), parse_sx_datetime(str),
calendar_event(...), background_event(...), calendar_type(...).
schedule_x props
Props marked live update the running calendar; changing any other prop re-creates it.
| Prop | Type | Notes |
|---|---|---|
events |
list[dict] |
live. id, start, end, title, description, location, people, calendarId, rrule, exdate, _options, _customContent, any custom key |
background_events |
list[dict] |
live. start, end, style, title, rrule, exdate |
calendars |
dict[str, dict] |
live. colorName, label, lightColors, darkColors |
views |
list[str] |
day, week, month-grid, month-agenda, week-agenda, list |
default_view |
str |
Initial view |
view |
str |
live, controlled view (pair with on_view_change) |
selected_date |
str |
live, YYYY-MM-DD |
min_date / max_date |
str |
live |
locale |
str |
live, see reflex_schedule_x.LOCALES |
timezone |
str |
live, IANA name, default UTC |
first_day_of_week |
int |
live, 1 = Monday … 7 = Sunday |
translations |
dict |
Overrides merged over the built-in texts: {"en-US": {"Week": "4 days"}} |
day_boundaries |
dict |
live, {"start": "06:00", "end": "18:00"} |
week_options |
dict |
live, gridHeight, nDays, eventWidth, gridStep, eventOverlap, timeAxisFormatOptions |
month_grid_options |
dict |
live, {"nEventsPerDay": 4} |
month_agenda_options |
dict |
{"nEventIndicatorsPerDay": 3} |
show_week_numbers, is_responsive, skip_animations, skip_validation |
bool |
|
small_breakpoint |
int |
Width (px) under which small-screen views are used |
date_picker |
dict |
Header date picker options |
theme |
"default" | "shadcn" |
Literal value; use one theme per app |
is_dark |
bool |
live |
current_time_indicator, current_time_full_week_width |
bool |
current-time plugin |
event_modal |
bool |
event-modal plugin |
initial_scroll / scroll_to |
str |
scroll-controller plugin; scroll_to is live |
recurrence |
bool |
event-recurrence plugin |
ical_data |
str |
iCalendar plugin (times are treated as UTC) |
timezone_select |
bool |
timezone-select plugin |
datetime_format |
"naive" | "iso" |
Format of date-times in payloads |
id |
str |
Enables ScheduleXAPI(id) |
The calendar defaults to width="100%", height="800px", max_height="90vh"; override with normal style props.
Event triggers
| Trigger | Payload |
|---|---|
on_event_click, on_double_click_event, on_event_update |
event dict (plus is_all_day) |
on_range_update |
{"start": str, "end": str} |
on_calendar_render |
{"view": str, "date": str, "range": {...}} |
on_view_change |
view name |
on_selected_date_update, on_click_date, on_double_click_date, on_click_agenda_date, on_double_click_agenda_date, on_click_plus_events, on_scroll_day_into_view |
"YYYY-MM-DD" |
on_click_date_time, on_double_click_date_time |
"YYYY-MM-DD HH:mm" |
on_slot_action |
{"action", "slot", "event", "date"} |
on_error |
error message |
Lazy loading: load events in on_range_update (and on_scroll_day_into_view for the list view) and assign them to
the events state var.
Custom components (slots)
Put schedule_x_slot(name, *children) components inside schedule_x. Slot content is normal Reflex UI, so it can use
state. Inside a slot:
schedule_x_field(name, format=None)renders slot data. Event fields:title,description,location,people,start,end,calendarId, custom keys; virtual fieldstime_range,date_range,calendar_label. Other slots exposedate,day,hour,events. Formats:time,date,datetime,weekday,weekday_short,day,month,hour,count,upper,json.schedule_x_show(name, *children, negate=False)renders children when the field is truthy (is_all_daytoo).schedule_x_event_card(*children, variant="container" | "main")paints the event's calendar colors.schedule_x_action(*children, action="delete", close_modal=True)sends clicks toon_slot_action.
schedule_x(
schedule_x_slot(
"timeGridEvent",
schedule_x_event_card(
schedule_x_field("title", tag_name="strong"),
schedule_x_field("time_range"),
schedule_x_show("location", schedule_x_field("location")),
),
),
schedule_x_slot(
"eventModal",
rx.card(
schedule_x_field("title", tag_name="h3"),
schedule_x_action(rx.button("Delete"), action="delete", close_modal=True),
),
),
schedule_x_slot("headerContentRightPrepend", rx.button("New", on_click=State.new_event)),
events=State.events,
event_modal=True,
on_slot_action=State.on_slot_action,
)
Supported slots: timeGridEvent, dateGridEvent, monthGridEvent, monthAgendaEvent, weekAgendaEvent,
monthAgendaDateDots, eventModal, headerContent, headerContentLeftPrepend, headerContentLeftAppend,
headerContentRightPrepend, headerContentRightAppend, weekGridDate, weekGridHour, monthGridDayName,
monthGridDate.
Imperative API
from reflex_schedule_x import ScheduleXAPI
api = ScheduleXAPI("my-calendar") # schedule_x(id="my-calendar")
rx.button("Week", on_click=api.set_view("week"))
rx.button("Today", on_click=api.set_date(datetime.date.today()))
rx.button("Dump", on_click=api.get_events(callback=State.receive_events))
Methods: set_view, set_date, set_theme, add_event, update_event, remove_event, set_events,
close_event_modal, scroll_to, get_view, get_date, get_range, get_events, get_event.
Date and time pickers
schedule_x_date_picker(value=State.date, on_change=State.set_date, locale="es-ES", label="Fecha", min="2026-01-01")
schedule_x_time_picker(value=State.time, on_change=State.set_time, is_12_hour=True, label="Hora")
Demo app
The schedule_x_demo/ folder contains a multi-page demo: playground with every option, events CRUD and callback
log, custom components, recurrence + background events + iCal, language and timezones, imperative API with the list
view, and the pickers.
uv venv && uv pip install -e .
cd schedule_x_demo
uv pip install -r requirements.txt
uv run reflex run
Development
uv sync --extra dev
uv run ruff check . && uv run ruff format --check .
uv run pytest --cov
uv run --python 3.12 python -m reflex_base.utils.pyi_generator custom_components/reflex_schedule_x # regenerate .pyi stubs
uv build # sdist + wheel
Regenerate the stubs with Python 3.12, as CI does: Python 3.13 strips docstring indentation, so stubs generated with it fail the CI freshness check.
Branches: work happens on develop; main only receives pull requests from develop. The Quality (ruff, pytest on
Python 3.11–3.13, build, stub freshness, demo bundle on Node 24) and Security (CodeQL, Bandit, pip-audit, Gitleaks, dependency
review) workflows must pass before merging.
Releasing
-
Bump
versioninpyproject.toml, updateCHANGELOG.md, and mergedevelopintomain. -
Tag
mainand push the tag:git checkout main && git pull git tag -a v0.2.0 -m "v0.2.0" git push origin v0.2.0
The Release workflow checks that the tag matches the version, runs the tests, builds the sdist and wheel, publishes
them to PyPI with Trusted Publishing (no API token) from the pypi environment, and creates the GitHub release with
the files attached.
License
MIT © Ernesto Crespo. Schedule-X is MIT licensed, © Tom Österlund.
Metadata
Release files for reflex-schedule-x 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| reflex_schedule_x-0.2.0.tar.gz | 36.6 kB | With provenance | Details for reflex_schedule_x-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| reflex_schedule_x-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for reflex_schedule_x-0.2.0-py3-none-any.whl |
Total release size: 69.3 kB
Release files / reflex_schedule_x-0.2.0.tar.gz
| Download URL | reflex_schedule_x-0.2.0.tar.gz |
|---|---|
| Size | 36.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a113e84a14e0d2d3335035090bd0d4cb98f5484613451b1da50ba5ebc061d28f
|
|
BLAKE2b-256 checksum How to use checksums |
846cac9ca4a082568d9ebcd5cefdaf37463dbf1f08aa191fd7e99211ff9016e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / reflex_schedule_x-0.2.0-py3-none-any.whl
| Download URL | reflex_schedule_x-0.2.0-py3-none-any.whl |
|---|---|
| Size | 32.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
21239aeefd46c5f1db2029956057c6940b3c4c43f1ee70d31699b612ae5351c3
|
|
BLAKE2b-256 checksum How to use checksums |
77dd539cce3a450f4b63a2b58870caee383c414111aa42960c870877c60b1e5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for reflex-schedule-x 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for reflex_schedule_x-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.