Overview
A recurrence-definition control. Users author a schedule via friendly presets (every N minutes/hours, daily, weekly on selected days, or monthly on a day), raw five-field cron, or a fixed interval — all three authoring modes lower to the same portable emitted value: { mode: 'cron', expression } or { mode: 'interval', every, unit }. mode: 'preset' never appears in the emitted value; presets are sugar.
ScheduleBuilder ships no date, cron-parsing, or scheduling library. The always-visible "next N fires" preview is entirely consumer-supplied via computeNextFires, and the preview section is not rendered at all when that prop is omitted. Scope is recurrence only — overlap policy, jitter, and backfill belong to the consumer's surrounding form.
Usage
Authoring modes
Three modes live behind a tablist (Presets, Cron, Interval); presets is the default.
- Presets — a preset-kind selector (
Every N/Daily/Weekly/Monthly) reveals the matching fields.Every Nlowers to anintervalvalue;Daily,Weekly, andMonthlylower to acronvalue. - Cron — five separate fields (minute, hour, day of month, month, day of week), each validated independently with an inline error and hint. The emitted value is the joined five-field expression.
- Interval — a number and a unit (
minutes/hours/days/weeks).
Switching modes never emits a change by itself — it re-seeds the destination mode's fields from the last value you actually committed, losslessly where representable. A minute or hour interval converts to an equivalent cron step only when the count evenly divides its field cycle (e.g. */15 * * * *); day/week intervals and non-dividing minute/hour intervals (like every 45 minutes) are not faithfully representable as a cron step, so Cron mode seeds a neutral daily default rather than a misleading expression. A cron pattern converts back to an interval only when it is such a dividing step pattern.
Regardless of mode, three things are always visible: a plain-English summary line, the next-fires preview (when computeNextFires is supplied), and a timezone display slot (timezone snippet, else timezoneLabel text, else a "Not set" placeholder).
When to use
- Letting a user define when a job or notification recurs, supplying your own date/cron library to compute upcoming fire times.
- You want a friendly presets UI (every N, daily, weekly, monthly) that still round-trips to a portable cron or interval value.
- You need overlap policy, jitter, or backfill controls — those belong to the consumer's surrounding form. Use Invocation rule builder instead
- You only need to pick a single point in time, not a recurrence — use date-picker instead. Use Date picker instead
Examples
Basic schedule builder
Author a recurrence via presets, raw cron, or a fixed interval. The summary line, next-fires preview, and timezone slot stay visible across every mode.
Cron mode
Open the Cron tab to see five independently validated fields seeded losslessly from the initial value. Each field carries a numeric hint and surfaces an inline error the moment it falls outside its valid range.
Interval mode
Open the Interval tab to author a fixed-cadence schedule. The interval value round-trips losslessly through every mode that can represent it; a cadence with no exact cron equivalent (like every 5 hours) seeds a neutral default in Cron mode rather than silently changing meaning.
Presets mode
Presets is the default authoring mode. Try each preset kind — "every N" lowers to an interval value, and daily/weekly/monthly all lower to a cron expression. The summary line always reflects the resulting cron or interval value, never a "preset".
Props
| Name | Type | Default | Required | Bindable | Description |
|---|---|---|---|---|---|
value | ScheduleValue | undefined | The current recurrence value (controlled). Pass the value returned from
onchange back in to commit an edit. When omitted, the component starts
from a sensible default (interval, every 15 minutes). | |||
onchange | ((value: ScheduleValue) => void)undefined | Called whenever the user edits the recurrence. Receives the next lossless {@link ScheduleValue}. The consumer owns persistence and validation. | |||
allowedModes | readonly ScheduleAuthoringMode[]undefined | Authoring modes to expose. Defaults to ['presets', 'cron', 'interval'].
Use ['cron'] for cron-only consumers; the component then renders only
cron fields and never emits an interval value. | |||
computeNextFires | ((value: ScheduleValue, count: number) => ScheduleFire[])undefined | Injected next-fires computation. The component passes the current value and the requested count and renders whatever fires the consumer returns. When omitted, the preview list is hidden (the component ships no date logic). | |||
previewCount | number | undefined | 5 | How many upcoming fires to request from computeNextFires. Defaults to 5. | ||
timezoneLabel | string | undefined | Timezone label rendered in the always-visible timezone display slot, e.g.
"America/New_York" or "UTC". Purely presentational — the component does not
interpret it. When omitted, provide the timezone snippet instead; if
neither is supplied the slot shows a "Not set" placeholder (the slot itself
is always rendered). | |||
timezone | Snippet | undefined | Custom content for the timezone display slot. Takes precedence over
timezoneLabel when both are supplied. | |||
label | string | undefined | 'Schedule' | Accessible label for the whole control. Defaults to "Schedule". |