Calendar / Scheduler
ProMonth, week, day and agenda views (plus a planner week time-grid) with color-coded event chips, keyboard navigation, a today highlight and opt-in drag-to-create. Emits the visible date range so you fetch only what is on screen, and `view` / `focusedDate` are two-way bindable for deep links.
Complexity tier: Application The interactive bar, plus a stated data-volume ceiling, a persistence contract, a bundle budget and an accessible parity surface.
import { SvEventCalendarComponent } from '@styloviz-pro/scheduler';Demo & usage
Basic usage
ProUsage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Wired to an API — fetch only what is on screen
ProWeek view, Monday start
ProPlanner with drag-to-create
ProWhen to use
- Showing scheduled events across month, week, day or agenda views.
- You want color-coded categories and click-to-create/inspect.
- For activity history (not scheduling), use the Timeline.
Async states
- loading
- error
- retry
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| events | readonly CalendarEvent[] | [] | Calendar events for the currently visible range. Fetch them from `rangeChange` rather than loading a year at a time. |
| view | CalendarView (two-way) | 'month' | Active view. Two-way bindable — `[(view)]` keeps it in the URL so a shared link reopens the week the sender was looking at. Still emits `viewChange`. |
| variant | CalendarVariant | 'default' | Visual variant. |
| weekStart | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 1 | Which weekday a week begins on, `0` Sunday to `6` Saturday. It used to accept only Sunday or Monday, which quietly asserts that no other convention exists. Saturday is the working-week start across much of the Middle East and North Africa, and a calendar that cannot express it is unusable there rather than merely unfamiliar. |
| focusedDate | string (two-way) | toDateStr(new Date()) | The date the calendar is centred on, `YYYY-MM-DD`. Two-way bindable, so the month the user navigated to survives a reload or a deep link. Defaults to today. |
| loading | boolean | false | True while the visible range is being fetched. Replaces the grid with a placeholder on first load. |
| error | SvDataError | string | null | null | Load failure for the visible range. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| showZonePolicy | boolean | true | Show the one-line zone policy under the calendar ("Times shown as given — no time-zone conversion"). The component never converts a zone, and the reader's assumption is the opposite, so it is stated by default; hide it only when the host states the policy itself. |
| creatable | boolean | false | Enable drag-to-create in the planner (week time-grid). |
| notes | Record<string, string> (two-way) | {} | Day notes keyed by ISO date (two-way: `[(notes)]`). |
Outputs
| Event | Payload type | Description |
|---|---|---|
| eventClick | CalendarEvent | Emitted with the clicked event. |
| dateClick | string | Emitted with the clicked date (ISO string). |
| rangeChange | CalendarRange | The visible date range changed — fetch the events for it. Emitted on first render and whenever the user navigates or switches view, never twice for the same range. This is what makes the calendar usable against a real backend: without it a host has to load every event that exists, because it cannot know what the component is about to show. ```ts onRangeChange({ start, end }: CalendarRange): void { this.loading.set(true); this.api.events(start, end).subscribe((events) => { this.events.set(events); this.loading.set(false); }); } ``` |
| retry | void | The host should re-run the failed load. |
| rangeSelect | { date: string; startTime: string; endTime: string } | Emitted when the user drags out a new time range in the planner. |
| viewChange | CalendarView | Emits the new `view` whenever it changes — the output half of `[(view)]`. |
| focusedDateChange | string | Emits the new `focusedDate` whenever it changes — the output half of `[(focusedDate)]`. |
| notesChange | Record<string, string> | Emits the new `notes` whenever it changes — the output half of `[(notes)]`. |
Wire it to your API
This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.
Template
ProBound to the names this component actually exposes.
Host class
ProDrive the three async inputs from your own fetch.
- This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `events`, `loading` and `error`.
- Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.
Full walkthrough: Connecting to a backend.
Customization & theming
- Overlapping events are laid out in side-by-side lanes rather than stacked, sized per cluster — two meetings at 9am and two at 4pm need two lanes, not four. Touching is not overlapping, so a day of back-to-back meetings stays full width.
- `weekStart` takes any weekday, 0 Sunday to 6 Saturday — Saturday is the working-week start across much of the Middle East and North Africa.
- `endDate` for multi-day events, and `recurrence` for a repeat disclosure.
- Switch month / week / day / agenda views, plus default / compact / timeline / planner variants.
- Color-coded event categories.
- In the planner, set `creatable` to drag out a new time range (15-min snap, live preview) — `rangeSelect` emits `{ date, startTime, endTime }` for your store.
- Today indicator and custom event templates.
Best practices
- Do: Convert to the zone you want displayed BEFORE passing events in. This calendar is a wall-clock grid: it renders the YYYY-MM-DD and HH:MM it is given and never converts an offset, so 09:00 shows at 09:00 for every viewer. That is a deliberate refusal — a component that converts has to decide whose zone wins, and any answer is wrong for somebody.
- Tip: A DST changeover does not break the grid, because the times shown are the ones a local clock will show. It does break a DURATION read off the grid: on the spring-forward day 01:30 to 03:30 is two hours on the clock and one in real time. The changeover day is named under the plot for that reason.
- Do: Send a recurring event's occurrences yourself, each carrying its recurrence rule. The calendar discloses the rule but does not generate occurrences, because generating them means owning cancelled and moved instances — which is where recurrence actually gets hard.
- Do: Use endDate for anything spanning more than one day. Without it a conference over a long weekend can only be sent as separate events that no longer know they are one thing.
- Do: Color-code events by category consistently and expose a legend.
- Do: Respect locale with `weekStart` (Sunday vs Monday).
- Tip: Default to the view that matches the data density — agenda for sparse, week for busy.
- Do: Fetch from `(rangeChange)` rather than loading a year of events up front — the range it hands you is exactly what is about to be drawn.
- Don't: Do not fetch only the focused calendar month. Month view shows leading and trailing days from the neighbouring months, and `rangeChange` accounts for them.
Accessibility
- Concurrent events are given lanes so none is hidden behind another. Every block used to be full column width at an offset derived only from its start time, so two events at the same hour drew on top of each other and the calendar showed a free morning that was double-booked — silently, which is the worst thing a scheduling surface can do.
- A recurring event carries its rule in WORDS in its accessible name, not only as a repeat glyph: moving next Tuesday's stand-up and moving every Tuesday's are different actions, and the glyph says nothing to a screen reader.
- The month view is a true ARIA `grid` (rows of day `gridcell`s) with roving-tabindex day buttons: arrow keys move between days, Home/End jump to week ends, Enter/Space select; each day is labelled with its full date, event count and today state.
- Prev/next controls are labelled per view, the period heading is a polite live region, and the view switcher exposes `aria-pressed`.
- Today is marked with `aria-current="date"`; events activate on Enter/Space and expose their title to assistive tech.
Feedback on Calendar / Scheduler
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.