Skip to content
ShineUI
Install

SchedulerBuilder

Complex recurrence, plain words.

<sui-scheduler-builder>CompositionsStable

About

Builds a recurring schedule from start, repeat and end rows on a sui-config-list, restates it as one sentence, previews the next five runs and hands back portable JSON. Cadences run from every few minutes to yearly, with weekday scopes, nth weekdays and business-day rules. Reach for it wherever a user sets when a job runs.

Built from

Usage

<sui-scheduler-builder label="Nightly ledger export" timezone="America/Kentucky/Louisville"></sui-scheduler-builder> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-scheduler-builder"); el.config = { unit: "day", every: 1, startTime: "9:00 PM", businessDays: "as-scheduled", endType: "never" }; /* SuiScheduleSeed | undefined */ el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiSchedulerBuilder } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const config = { unit: "day", every: 1, startTime: "9:00 PM", businessDays: "as-scheduled", endType: "never" }; /* SuiScheduleSeed | undefined */ return <SuiSchedulerBuilder label="Nightly ledger export" timezone="America/Kentucky/Louisville" config={config} onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiSchedulerBuilder } from "@shineui/vue"; import "@shineui/core/shineui.css"; const config = { unit: "day", every: 1, startTime: "9:00 PM", businessDays: "as-scheduled", endType: "never" }; // SuiScheduleSeed | undefined const onChange = (e) => console.log(e.detail); </script> <template> <SuiSchedulerBuilder label="Nightly ledger export" timezone="America/Kentucky/Louisville" :config="config" @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiSchedulerBuilder } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiSchedulerBuilder], template: `<sui-scheduler-builder label="Nightly ledger export" timezone="America/Kentucky/Louisville" [config]="config" (suiChange)="onChange($event)"></sui-scheduler-builder>`, }) export class Example { config = { unit: "day", every: 1, startTime: "9:00 PM", businessDays: "as-scheduled", endType: "never" }; // SuiScheduleSeed | undefined onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

A daily run at 9:00 PM that never ends. The sentence and the next-runs list are polite live regions, so every edit is announced, and each field is a group named by its own small label.

<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder label="Nightly ledger export" timezone="America/Kentucky/Louisville"></sui-scheduler-builder> </div>
// setup(root): properties and events setup(root) { root.querySelector('sui-scheduler-builder').config = nightly(); }

Business-day rule

Nov 15, 2026 lands on a Sunday, so the next-runs preview moves that run to Friday the 13th. The Business days row appears only for day, month and year cadences.

<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder id="sch-vendor" label="Vendor payment run" today-date="2026-08-17"></sui-scheduler-builder> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sch-vendor').config = vendorRun(); }

Nth weekday, no JSON

Monthly can also mean the third Friday. hide-json drops the raw config, which a product screen does not need, and leaves the sentence and next runs in the side column.

<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder id="sch-commissions" label="Commission statement" today-date="2026-08-17" hide-json></sui-scheduler-builder> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sch-commissions').config = commissions(); }

Compact with a window

Every 30 minutes between 8:00 AM and 6:00 PM on weekdays. A minute or hour cadence trades the start time for the window and the weekday scope, and the compact variant folds the JSON behind a toggle.

<div style="max-width:420px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder id="sch-feed" variant="compact" label="Bank feed sync" today-date="2026-08-17"></sui-scheduler-builder> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sch-feed').config = bankFeed(); }

Disabled

disabled is the running job you do not own. Every composed control takes its own disabled state while the sentence, the preview and the JSON stay readable, and the copy button stays live because copying is not editing.

<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder id="sch-locked" label="Vendor payment run" today-date="2026-08-17" disabled></sui-scheduler-builder> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sch-locked').config = vendorRun(); }

Events

suiChange fires once after load and after every edit with { config, sentence }. The composed inputs keep their own events to themselves, so this is the only change event a consumer sees, and getConfig() reads the same contract on demand.

Idle.
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px"> <sui-scheduler-builder id="sch-events" label="1099 filing" today-date="2026-08-17"></sui-scheduler-builder> <div style="display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:14px"> <sui-button id="sch-read" variant="outline" label="Read the config"></sui-button> <span id="sch-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#sch-events'); const log = root.querySelector('#sch-log'); el.config = filing(); el.addEventListener('suiChange', (e) => { log.textContent = e.detail.sentence; }); root.querySelector('#sch-read').addEventListener('click', async () => { const config = await el.getConfig(); log.textContent = `Version ${config.version}, ${config.repeat.unit} cadence, ends ${config.end.type}, rule ${config.businessDays.rule}.`; }); }

Data contract

The suiChange detail carries { config, sentence }. Config is versioned JSON: start { date, time }, repeat { every, unit, weekdays, window, monthly, annual }, businessDays { rule }, end { type, date | count }. Store it as-is; any system can replay it.

Keyboard

KeyAction
TabMoves through the fields in reading order: the start date and time, the repeat count and unit, the weekday or monthly controls the unit reveals, the business-day rule, the end rule and its date or count, then the JSON toggle in the compact variant unless hide-json is set. Each field is the ShineUI control it names and takes the keys of that control.
Enter SpaceCommits the focused control as that control does; every commit emits suiChange with the config and the sentence. On the JSON toggle, shows or hides the configuration pane.
ArrowDown ArrowUpOn a select, opens it and moves through its options; on a number field, steps the value; on the date picker, moves through the days once it is open.

API

<sui-scheduler-builder>

Properties

PropertyAttributeTypeDefaultDescription
configproperty onlySuiScheduleSeedSuiScheduleSeed | undefinedSeed mechanics (the reference defaultConfig), typed as SuiScheduleSeed. Property, not attribute. Mutable: after every edit the element writes the current mechanics back in seed form, so reading it returns the live state. Missing fields take the reference defaults. The mechanics: minute and hour cadences with a time window and a weekday scope; daily; weekly weekday sets; monthly on a day of the month or an ordinal weekday (first to fourth, last); yearly month and day; a business-day rule for runs that land on a weekend (as scheduled, previous, next, skip); ends never, on a date or after N runs.
disableddisabledbooleanfalseDisables every composed control, so the schedule reads but does not edit: the sentence, the next-runs preview and the JSON all stay legible while nothing can be changed. This is the screen for a running job you do not own. The copy button on the JSON and the compact variant's JSON toggle stay live, because they read the schedule and do not edit it. Reflected.
hideJsonhide-jsonbooleanfalseHides the configuration JSON, which the builder shows in a sui-code-block by default. Raw config is a developer affordance, so a product screen drops it: desktop then leaves the sentence and next runs in the side column, and compact drops the toggle row with it. Reflected.
labellabelstringstring | undefinedHeading above the builder, e.g. "Nightly export". Empty string hides it.
timezonetimezonestringstring | undefinedIANA zone the schedule is written in. Defaults to the reader's own, from Intl.DateTimeFormat().resolvedOptions().timeZone, falling back to UTC where the runtime does not report one. It used to default to America/Kentucky/Louisville, which is one customer's office and is wrong for everybody else who installs this library. A schedule is written in a zone whether or not the author names one, so the honest default is the zone the reader is actually in rather than a constant somebody has to notice and override.
todayDatetoday-dateDate | string | undefinedPins "today" for the date pickers and the default start date (demos and tests), e.g. "2026-08-17".
variantvariant"compact" | "desktop"'desktop'desktop = config list beside a 300px column with the sentence, next runs and JSON; compact = everything stacked on the mobile config list, with the JSON folded behind a toggle.

Events

EventDetailReactVueDescription
suiChangeSuiScheduleChangeDetailonSuiChange@sui-changeFires after every edit (and once after load) with { config, sentence }: the version-2 contract and its restatement in the page's language. The composed inputs' own suiChange and suiInput events stop at this element, so this is the only change event a consumer sees. The builder is a value control: Vue's v-model and Angular's ngModel bind the config prop through this event, so the model holds the seed form the element writes back.

Methods

MethodSignatureDescription
getConfig()getConfig() => Promise<SuiScheduleConfig>The full version-2 config for the current mechanics, the same contract suiChange carries.
setFocus()setFocus() => Promise<void>Moves keyboard focus to the first control, the start date, as host.focus() does, so a form that focuses its first invalid field lands in the builder.

Types

SuiBusinessDayRule typeWhat happens when a run lands on a weekend.
No members.
SuiScheduleChangeDetail interfacesuiChange detail: the resolved config and its one-sentence restatement in the page's language.
configSuiScheduleConfig
sentencestring
SuiScheduleConfig interfacePortable schedule contract (version 2): { version, timezone, start, repeat, businessDays, end }, the storage and interop shape other systems interpret. sui-scheduler-builder produces it in suiChange and from getConfig().
version2
timezonestring
start{ date: string; time: string; }
repeat{ every: number; unit: SuiScheduleUnit; /** Full weekday names ("Monday"); weekly sets, or the weekday scope of a minute/hour cadence. */ weekdays?: string[]; /** Active time window of a minute/hour cadence, as display times ("8:00 AM"). */ window?: { from: string; to: string; }; monthly?: { mode: 'dayOfMonth'; day: number; } | { mode: 'nthWeekday'; ordinal: string; weekday: string; }; annual?: { month: string; day: number; }; }
businessDays{ rule: SuiBusinessDayRule; }
end{ type: 'never'; } | { type: 'onDate'; date: string | null; } | { type: 'afterRuns'; count: number; }
SuiScheduleSeed interfaceLoose seed the builder's config property accepts (the reference defaultConfig): startDate, startTime, every, unit, weekdays (two-letter codes), monthlyMode, dayOfMonth, ordinal, weekday, month (0-based), day, windowFrom, windowTo, businessDays, endType, endDate, endCount. Every field is optional.
startDate?stringISO YYYY-MM-DD. Defaults to today.
startTime?stringDisplay time, e.g. "9:00 AM".
every?number
unit?string
weekdays?string[]Two-letter weekday codes: Mo Tu We Th Fr Sa Su.
monthlyMode?'day' | 'nth'
dayOfMonth?number
ordinal?string
weekday?string
month?numberZero-based month for the yearly cadence.
day?numberDay of month for the yearly cadence.
windowFrom?string
windowTo?string
businessDays?string
endType?'never' | 'on' | 'after'
endDate?stringISO YYYY-MM-DD.
endCount?number
SuiScheduleUnit typeCadence unit.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also