Skip to main content

reflex-schedule-x

Quality Security PyPI Python License: MIT

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, default and shadcn themes.
  • Standalone date picker and time picker components.
  • Imperative API (ScheduleXAPI) for rx.call_script driven 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 reflex>=0.9.11. 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 fields time_range, date_range, calendar_label. Other slots expose date, 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_day too).
  • 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 to on_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 reflex component build   # generates .pyi stubs and builds sdist + wheel

Branches: work happens on develop; main only receives pull requests from develop. The Quality (ruff, pytest on Python 3.10–3.13, build, stub freshness, demo bundle) and Security (CodeQL, Bandit, pip-audit, Gitleaks, dependency review) workflows must pass before merging.

Releasing

  1. Bump version in pyproject.toml, update CHANGELOG.md, and merge develop into main.

  2. Tag main and push the tag:

    git checkout main && git pull
    git tag -a v0.1.0 -m "v0.1.0"
    git push origin v0.1.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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_schedule_x-0.1.0.tar.gz (36.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_schedule_x-0.1.0-py3-none-any.whl (32.6 kB view details)

Uploaded Python 3

File details

Details for the file reflex_schedule_x-0.1.0.tar.gz.

File metadata

  • Download URL: reflex_schedule_x-0.1.0.tar.gz
  • Upload date:
  • Size: 36.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for reflex_schedule_x-0.1.0.tar.gz
Algorithm Hash digest
SHA256 99abc3a9f4b770495d02746ad8561b54a627386b36a2432bdcf51f731ed6b414
MD5 2cdb21f493349256c4b4f77bfccf38e2
BLAKE2b-256 ae247ebfdf070d510ce313668d6bb18d6b3e4b3d20712d9456ae8bee10de5e39

See more details on using hashes here.

Provenance

The following attestation bundles were made for reflex_schedule_x-0.1.0.tar.gz:

Publisher: release.yml on ecrespo/reflex-schedule-x

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reflex_schedule_x-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for reflex_schedule_x-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cab4fb232818a38947c410f1a5cc72efee90e7707dc84e32a2bc98bd70b75a79
MD5 196ef937646941e938ba9c446c2c641a
BLAKE2b-256 9574e296eb799dc87b6f320a2f4688edeb4837e97ef23cd9e510b19f12280ead

See more details on using hashes here.

Provenance

The following attestation bundles were made for reflex_schedule_x-0.1.0-py3-none-any.whl:

Publisher: release.yml on ecrespo/reflex-schedule-x

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page