Views

Business Hours & Holidays

Shade the time outside working hours, name the days off, and draw the shape of a day behind the events. All three are display only: they tell the reader what the day is, and leave what can be done with it to onMutate.

Business Hours

businessHours takes an opening time, a closing time and the days they apply to; all three are required. In the time grid the hours outside are shaded, and a day not in days is shaded in full. It also decides where the grid first scrolls to, and what isBusinessHours means in a cell snippet. It does not block anything: an event can still be created, dropped or resized into the shaded time.

<!-- Time outside 9:00 to 18:00, and every hour of Saturday and Sunday, is shaded.
     Nothing is blocked: events can still be created or dropped there. -->
<Scheduler
  bind:events
  businessHours={{ start: '09:00', end: '18:00', days: [1, 2, 3, 4, 5] }}
/>

BusinessHours

FieldDescription
startOpening time, 24 hour 'HH:mm'
endClosing time, 24 hour 'HH:mm', exclusive
daysDays the hours apply to; 0 is Sunday. Other days are shaded in full

Holidays

holidays lists whole days. Each is tinted in every grid view and named wherever there is room, and a holiday is always treated as outside business hours, whatever days says.

<!-- A holiday tints the whole day in every view and is named in the headers.
     It always counts as outside business hours. Dates are read in timeZone. -->
<Scheduler
  bind:events
  holidays={[
    { date: '2026-09-15', title: 'Founders day' },
    { date: '2026-09-25' }                          // tinted, unnamed
  ]}
/>

Holiday

FieldDescription
date'YYYY-MM-DD', read in the scheduler's timeZone
titleOptional. Shown in headers, cells and agenda headings, and read to screen readers

Where a holiday shows

ViewWhat appears
WeekColumn tinted, name under the day number in the header
DayTitle area tinted, name as a badge beside the date
MonthCell tinted, name beside the day number when the grid is wide enough
YearDay number in the tertiary colour
AgendaName beside the day heading
Screen readersPart of the accessible name of month cells and year days

Background Events

An event with background: true is a translucent block drawn behind the others with its title in small type. It takes no column in the overlap layout, cannot be moved, resized or deleted, and opens no details. It is for the shape of the day rather than an appointment: a maintenance window, lunch, an afternoon kept free of meetings.

// A background event is a translucent block behind the others. It takes no
// column, cannot be moved or deleted, and opens no details. Use it for the
// shape of the day: a maintenance window, lunch, a block kept free of meetings.
{ id: 'lunch', title: 'Lunch', start: '2026-09-16T12:00', end: '2026-09-16T13:00', background: true }
{ id: 'focus', title: 'No meetings', start: '2026-09-18T13:00', end: '2026-09-18T17:00', background: true }

A Working Week

Pick the opening hours and the working days, and switch the holidays and week numbers on and off. Tuesday is Founders day. The maintenance window on Wednesday and the free afternoon on Friday are background events.

Oct 19 – 25, 2026

Week 43 GMT+7
Mon 19
Tue 20 Founders day
Wed 21
Thu 22
Fri 23
Sat 24
Sun 25
All day
Maintenance window
Lunch break
No meetings
<Scheduler
  bind:events
  businessHours={{ start: '09:00', end: '18:00', days: [1, 2, 3, 4, 5] }}
  holidays={[{ date: '2026-09-15', title: 'Founders day' }]}
  weekNumbers
/>

Blocking Time

Shading is not a rule. If an event must not land outside the opening hours, decide that where every change already passes: onMutate. Throwing rolls the change back with an animation and calls onError, which is where a toast belongs. See Saving Changes. An interaction plugin can refuse earlier, during the drag, when that matters.

<!-- businessHours only shades. To refuse a change, say so from onMutate:
     the event slides back and onError runs. -->
<Scheduler
  bind:events
  businessHours={hours}
  onMutate={(mutation) => {
    const event = mutation.after;
    if (event && (event.start.hour < 9 || event.end.hour > 18)) {
      throw new Error('Outside opening hours');
    }
    return api.save(mutation);
  }}
  onError={(mutation, error) => toast.error(String(error))}
/>

Reading the Flags in a Cell

The cell snippet is drawn behind every month cell and time grid column and receives isWeekend, isHoliday, isBusinessHours, isToday, isAnchor and isOutside. isBusinessHours is a fact about the day, not the hour: true on a working day that is not a holiday. The hour by hour shading is drawn by the grid itself. Stripes for the weekend, here in the month view; see Custom Rendering for the other snippets.

October 2026

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 Founders day
21
22
23
24
25
26
27
28
29
30 Company holiday
31
1
<!-- The cell snippet is drawn behind each day. It gets the flags, so a stripe
     for weekends or a tint for holidays needs no date maths. -->
<Scheduler bind:events {holidays} {businessHours} cell={backdrop} />

{#snippet backdrop({ isWeekend, isHoliday, isBusinessHours })}
  {#if isHoliday}
    <div class="absolute inset-0 bg-tertiary/5"></div>
  {:else if isWeekend || !isBusinessHours}
    <div class="absolute inset-0 bg-[repeating-linear-gradient(135deg,transparent_0_8px,var(--color-outline-variant)_8px_9px)] opacity-30"></div>
  {/if}
{/snippet}

Props

The props this page uses.

PropTypeDefault
businessHoursBusinessHours-
holidaysHoliday[][]
cellSnippet<[CellSnippetProps]>-
onMutate(m: Mutation<T>) => MutationResult<T> | Promise<...>-

Snippets

What the snippets on this page receive. Every type is exported from @sv5ui/scheduler.

TypeFields
CellSnippetPropsdate, view, isToday, isAnchor, isWeekend, isHoliday, isBusinessHours, isOutside