---
title: "Javascript Calendar Appointment Booking Example | Mobiscroll"
component: "Calendar"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/calendar/appointment-booking"
---

# Appointment Booking - JavaScript Calendar demo

Build booking date selection interfaces with an inline Javascript calendar — show per-day prices and disable fully booked dates for appointment flows. Plain JavaScript API.

- [Live demo](https://demo.mobiscroll.com/javascript/calendar/appointment-booking)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-javascript/tree/main/src/demos/datepicker/calendar/appointment-booking)
- This demo in other frameworks: [jQuery](https://demo.mobiscroll.com/jquery/calendar/appointment-booking.md), [Angular](https://demo.mobiscroll.com/angular/calendar/appointment-booking.md), [React](https://demo.mobiscroll.com/react/calendar/appointment-booking.md), [Vue](https://demo.mobiscroll.com/vue/calendar/appointment-booking.md)

## Description

Book single or multiple appointments depending on the use-case or set up recurring date and time selection. Limit the available dates with the `min` and `max` options. Make dates un-selectable with the `invalid` option. Load pricing, available spots on demand and show it as [labels](https://demo.mobiscroll.com/javascript/calendar/dots-colors-labels) that help people book the appointment.

The calendar can be displayed inline with one or more months or linked to an input. [Learn how to initialize the calendar.](https://demo.mobiscroll.com/javascript/calendar/usage-on-input-or-inline)

**Looking for flight or hotel booking?** [Check out the range picker →](https://demo.mobiscroll.com/javascript/range/flight-booking)

## What this demo shows

- Three inline appointment-booking examples that show different date-selection patterns.
- **Single-date booking** The first example shows an inline calendar with two months visible side by side.
- **Single-date booking** Past dates are disabled, and booking is limited to a six-month window starting from today.
- **Single-date booking** Some dates are unavailable because they are already booked.
- **Single-date booking** Each day shows a price label below the date.
- **Date and time booking** The second example pairs a single-month date picker with a time slot picker shown next to it.
- **Date and time booking** Users can browse dates from today up to six months ahead and then select from the available time slots for the chosen day.
- **Date and time booking** Each day shows how many spots are still available.
- **Date and time booking** Fully booked days are disabled, and taken time slots remain visible but cannot be selected.
- **Multi-date booking** The third example shows an inline two-month calendar with multiple date selection enabled.
- **Multi-date booking** Each day shows the number of available spots, and dates with no remaining availability are disabled.
- **Calendar header** All three examples include month labels and navigation arrows in the calendar header.

## Best for

- **Single-appointment booking** Consumer-facing flows where users need to choose one available date and see day-based pricing.
- **Date and time selection** Booking experiences where users pick a date first and then choose from the remaining time slots for that day.
- **Multi-visit booking** Flows that let people book multiple appointment dates in one interaction.
- **Availability-driven booking** Interfaces that need to show remaining capacity, disable fully booked dates, and surface useful day-level booking details.
- **Common appointment scenarios** Service bookings, home visits, consultations, demos, sales meetings, and similar scheduled appointments.

## Tutorial

### Single date selection

For enhanced validation set `min` and `max` dates. In addition to that disable invalid days by passing them to the `invalid` option.

If you need to show additional information, use the `labels` option and pass prices or availability. Invalids and things like pricing can be set on-load or on-demand in the `onPageLoading` lifecycle event.

```html
<div id="demo-booking-single"></div>
```

```js
var min = '2026-10-24T00:00';
var max = '2027-04-24T00:00';

mobiscroll.datepicker('#demo-booking-single', {
    controls: ['calendar'],
    display: 'inline',
    min: min,
    max: max,
    onPageLoading: function (event, inst) {
        getPrices(event.firstDay, function callback(bookings) {
            inst.setOptions({
                labels: bookings.labels,
                invalid: bookings.invalid
            });
        });
    }
});
                                  
function getPrices(d, callback) {
    var invalid = [],
        labels = [];

    mobiscroll.getJson('//trial.mobiscroll.com/getprices/?year=' + d.getFullYear() + '&month=' + d.getMonth(), function (bookings) {
        for (var i = 0; i < bookings.length; ++i) {
            var booking = bookings[i],
                d = new Date(booking.d);

            if (booking.price > 0) {
                labels.push({
                    start: d,
                    title: '$' + booking.price,
                    textColor: '#e1528f'
                });
            } else {
                invalid.push(d);
            }
        }
        callback({ labels: labels, invalid: invalid });
    }, 'jsonp');
}
```

### Booking a date and time

Use a `calendar` in combination with a `time` picker or `timegrid`. Dynamically filter the time grid based on availability and add labels to the dates to inform users of free spots.

```html
<div id="demo-booking-multiple"></div>
```

```js
mobiscroll.datepicker('#demo-booking-multiple', {
    controls: ['calendar', 'timegrid'],
    display: 'inline',
    min: '2026-10-24T00:00',
    max: '2027-04-24T00:00',
    minTime: '08:00',
    maxTime: '19:59',
    stepMinute: 60,
    // example for today's labels and invalids
    labels: [{
        start: '2026-10-24',
        textColor: '#e1528f',
        title: '3 SPOTS'
    }],
    invalid: [{
        start: '2026-10-24T08:00',
        end: '2026-10-24T13:00'
    }, {
        start: '2026-10-24T15:00',
        end: '2026-10-24T17:00'
    }, {
        start: '2026-10-24T19:00',
        end: '2026-10-24T20:00'
    }]
});
```

### Booking multiple appointments

For multiple event booking set the `selectMultiple` option to `'true'`. `min`, `max`, `invalid` are essential for a great UX and `labels` can be used for showing extra information about available spots.

```html
<div id="demo-booking-multiple"></div>
```

```js
var min = '2026-10-24T00:00';
var max = '2027-04-24T00:00';

mobiscroll.datepicker('#demo-booking-multiple', {
    controls: ['calendar'],
    display: 'inline',
    min: min,
    max: max,
    selectMultiple: true,
    onInit: function (event, inst) {
        inst.setVal([
            '2026-10-11T00:00',
            '2026-10-16T00:00',
            '2026-10-17T00:00'
        ], true);
    },
    onPageLoading: function (event, inst) {
        getBookings(event.firstDay, function callback(bookings) {
            inst.setOptions({
                labels: bookings.labels,
                invalid: bookings.invalid
            });
        });
    }
});

function getBookings(d, callback) {
    var invalid = [],
        labels = [];

    mobiscroll.getJson('//trial.mobiscroll.com/getbookings/?year=' + d.getFullYear() + '&month=' + d.getMonth(), function (bookings) {
        for (var i = 0; i < bookings.length; ++i) {
            var booking = bookings[i],
                d = new Date(booking.d);

            if (booking.nr > 0) {
                labels.push({
                    start: d,
                    title: booking.nr + ' SPOTS',
                    textColor: '#e1528f'
                });
            } else {
                invalid.push(d);
            }
        }
        callback({ labels: labels, invalid: invalid });
    }, 'jsonp');
}
```

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-themeVariant) - option
- [`display`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-display) - option
- [`controls`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-controls) - option
- [`min`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-min) - option
- [`max`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-max) - option
- [`pages`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-pages) - option
- [`onPageLoading`](https://mobiscroll.com/docs/javascript/datepicker/api#event-onPageLoading) - event
- [`setOptions`](https://mobiscroll.com/docs/javascript/datepicker/api#method-setOptions) - method
- [`labels`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-labels) - option
- [`invalid`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-invalid) - option
- [`minTime`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-minTime) - option
- [`maxTime`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-maxTime) - option
- [`stepMinute`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-stepMinute) - option
- [`selectMultiple`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-selectMultiple) - option
- [`onInit`](https://mobiscroll.com/docs/javascript/datepicker/api#event-onInit) - event

## Code

### JS

```js
mobiscroll.setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

var min = '2026-10-24T00:00';
var max = '2027-04-24T00:00';

mobiscroll.datepicker('#demo-booking-single', {
  display: 'inline',
  controls: ['calendar'],
  min: min,
  max: max,
  pages: 'auto',
  onPageLoading: function (event, inst) {
    getPrices(event.firstDay, function callback(bookings) {
      inst.setOptions({
        labels: bookings.labels,
        invalid: bookings.invalid,
      });
    });
  },
});

mobiscroll.datepicker('#demo-booking-datetime', {
  display: 'inline',
  controls: ['calendar', 'timegrid'],
  min: min,
  max: max,
  minTime: '08:00',
  maxTime: '19:59',
  stepMinute: 60,
  onPageLoading: function (event, inst) {
    getDatetimes(event.firstDay, function callback(bookings) {
      inst.setOptions({
        labels: bookings.labels,
        invalid: bookings.invalid,
      });
    });
  },
});

mobiscroll.datepicker('#demo-booking-multiple', {
  display: 'inline',
  controls: ['calendar'],
  min: min,
  max: max,
  pages: 'auto',
  selectMultiple: true,
  onInit: function (event, inst) {
    inst.setVal(['2026-10-11T00:00', '2026-10-16T00:00', '2026-10-17T00:00'], true);
  },
  onPageLoading: function (event, inst) {
    getBookings(event.firstDay, function callback(bookings) {
      inst.setOptions({
        labels: bookings.labels,
        invalid: bookings.invalid,
      });
    });
  },
});

function getPrices(d, callback) {
  var invalid = [];
  var labels = [];

  mobiscroll.getJson(
    'https://trial.mobiscroll.com/getprices/?year=' + d.getFullYear() + '&month=' + d.getMonth(),
    function (bookings) {
      for (var i = 0; i < bookings.length; ++i) {
        var booking = bookings[i];
        var d = new Date(booking.d);

        if (booking.price > 0) {
          labels.push({
            start: d,
            title: '$' + booking.price,
            textColor: '#e1528f',
          });
        } else {
          invalid.push(d);
        }
      }
      callback({ labels: labels, invalid: invalid });
    },
    'jsonp',
  );
}

function getDatetimes(day, callback) {
  var invalid = [];
  var labels = [];

  mobiscroll.getJson(
    'https://trial.mobiscroll.com/getbookingtime/?year=' + day.getFullYear() + '&month=' + day.getMonth(),
    function (bookings) {
      for (var i = 0; i < bookings.length; ++i) {
        var booking = bookings[i];
        var bDate = new Date(booking.d);

        if (booking.nr > 0) {
          labels.push({
            start: bDate,
            title: booking.nr + ' SPOTS',
            textColor: '#e1528f',
          });
          invalid = invalid.concat(booking.invalid);
        } else {
          invalid.push(bDate);
        }
      }
      callback({ labels: labels, invalid: invalid });
    },
    'jsonp',
  );
}

function getBookings(d, callback) {
  var invalid = [];
  var labels = [];

  mobiscroll.getJson(
    'https://trial.mobiscroll.com/getbookings/?year=' + d.getFullYear() + '&month=' + d.getMonth(),
    function (bookings) {
      for (var i = 0; i < bookings.length; ++i) {
        var booking = bookings[i];
        var d = new Date(booking.d);

        if (booking.nr > 0) {
          labels.push({
            start: d,
            title: booking.nr + ' SPOTS',
            textColor: '#e1528f',
          });
        } else {
          invalid.push(d);
        }
      }
      callback({ labels: labels, invalid: invalid });
    },
    'jsonp',
  );
}
```

### HTML

```html
<div class="md-calendar-booking">
    <div class="mbsc-form-group">
        <div class="mbsc-form-group-title">Single date & appointment booking</div>
        <div id="demo-booking-single"></div>
    </div>
    <div class="mbsc-form-group">
        <div class="mbsc-form-group-title">Select date & time</div>
        <div id="demo-booking-datetime" class="booking-datetime"></div>
    </div>
    <div class="mbsc-form-group">
        <div class="mbsc-form-group-title">Booking multiple appointments</div>
        <div id="demo-booking-multiple"></div>
    </div>
</div>
```

### CSS

```css
.md-calendar-booking .mbsc-calendar-text {
    text-align: center;
}

.md-calendar-booking .booking-datetime .mbsc-datepicker-tab-calendar {
    flex: 1 1 0;
    min-width: 300px;
}

.md-calendar-booking .mbsc-timegrid-item {
    margin-top: 1.5em;
    margin-bottom: 1.5em;
}

.md-calendar-booking .mbsc-timegrid-container {
    top: 30px;
}
```

## Implementation notes

- **Booking window** Use `min` and `max` to limit selection to the allowed booking range, such as from today through the next six months.
- **Unavailable dates** Use `invalid` to disable dates that are fully booked or otherwise unavailable.
- **Day labels** Use `labels` to display supporting day-level information such as pricing or remaining spots.
- **Dynamic data loading** Set `invalid` dates and `labels` on load or update them in the `onPageLoading` lifecycle event when availability and pricing need to be loaded for the visible date range.
- **Date and time booking** Combine the calendar with a timegrid for date-and-time selection, then filter the time slots based on availability for the selected day.
- **Multi-date booking** Set `selectMultiple` to `true` when the booking flow needs to allow selecting more than one date.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-javascript.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config javascript
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config javascript --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/javascript/getting-started/installation

## Related demos

- Next: [Custom Day Cells](https://demo.mobiscroll.com/javascript/calendar/activity-calendar.md) - Add custom activity indicators, availability markers, or daily metrics to each day cell in the inline Javascript calendar date picker.
