---
title: "Javascript Range Flight Booking Example | Mobiscroll"
component: "Range"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/range/flight-booking"
---

# Flight Booking - JavaScript Range demo

Build a flight booking date picker with the Javascript range component — round-trip and one-way selection. Plain JS api for usage everywhere.

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

## Description

For a great booking experience a good date picker is essential. The best implementations are contextual and take the intent *(what people want to do)* and context *(where and how are they doing it)* into account. Learn how to use and customize the range picker and see how you can add your flavor that matches with the overall experience of your application.

## What this demo shows

- Shows five flight booking examples for selecting a date range with differently customized range pickers.
- **Single input** The first example uses a single input for selecting the outbound and return dates.
- **Two inputs** The second example uses two separate inputs, one for the Outbound date and another for the Return date.
- **Invalid days** The third example uses two inputs and displays invalid dates for days when no flights are available. These dates are disabled and cannot be selected.
- **One way or round trip** The fourth example uses separate Outbound and Return inputs together with Round trip and One way options. Round trip is selected by default. Switching to One way disables the Return input and allows selecting only the outbound date.
- **Custom button** The fifth example uses a single input and adds a custom `One way only` button to the footer of the range picker, next to the `Set` button. This provides an alternative way to book a one-way flight directly from the picker.
- **Input behavior** The range picker opens below the input over a darkened backdrop. Clicking outside the picker closes it.
- **Outbound and Return inputs** In the examples with two inputs, clickable Outbound and Return inputs appear above the calendar. The active input has a white background, while the inactive input has a gray background. A clear button appears after a date range is selected.
- **Month view** Every example displays two months at the same time, with past dates disabled. A fixed weekday header shows abbreviated day names from Sunday through Saturday, with dates arranged in a grid below.
- **Month navigation** Drag the calendar left or right to navigate between months.
- **Day cell states** Hovering over a day highlights its number with a gray background. The first selected day becomes the range start and the second becomes the range end. Selected dates and the dates between them are visually highlighted. The current date is highlighted by default.
- **Footer actions** The gray `Cancel` button discards the changes, while the blue `Set` button confirms the selected date range. Until a valid range is selected, the `Set` button remains inactive.
- **Input value** Confirming the selection with `Set` displays the selected date or date range in the corresponding input using a month, day, and year format.

## Best for

- **Airline booking forms** where travelers need to select outbound and return dates for round-trip flights.
- **One-way flight booking** where only a departure date is required and the return date can be disabled or skipped.
- **Flight availability calendars** where dates without available flights need to be disabled and clearly distinguished from selectable dates.
- **Travel search interfaces** that need separate Outbound and Return inputs while keeping the date selection inside a single range picker.
- **Flexible booking experiences** where users can switch between round-trip and one-way travel directly from the date picker.

## Tutorial

When building flight booking systems you want to make sure that you set up min & max boundaries for the selectable days. This way you ensure that people can't select dates from the past.

### Setting it up on a single input

The range picker can be set up on one or two inputs. If you are building a fully custom experience, you can [embed it into your own modal](https://demo.mobiscroll.com/javascript/range/mobile-desktop-display).

```html
<label>
    Range
    <input id="picker" mbsc-input placeholder="Please select..." />
</label>
```

```js
var min = new Date(),
    max = new Date(min.getFullYear(), min.getMonth() + 6, min.getDate());
                            
mobiscroll.datepicker('#picker', {
    controls: ['calendar'],
    select: 'range',
    min: min,
    max: max
});
```

### Using two inputs

Instead of a single input you can have two, one for **Outbound** and another for **Return**.

```html
<label>
    Outbound
    <input id="start" mbsc-input placeholder="Please select..." />
</label>
<label>
    Return
    <input id="end" mbsc-input placeholder="Please select..." />
</label>
```

```js
var min = new Date(),
    max = new Date(min.getFullYear(), min.getMonth() + 6, min.getDate());
                            
mobiscroll.datepicker('#start', {
    controls: ['calendar'],
    select: 'range',
    startInput: '#start',
    endInput: '#end',
    min: now,
    max: max
});
```

### Not all days have flights

Besides setting min & max boundaries, days without flights should be [disabled](https://demo.mobiscroll.com/javascript/range/disabled-invalid-values). These could be single occurrence or [recurring dates](https://demo.mobiscroll.com/javascript/range/recurring-values), like specific weekdays. Setting `inRangeInvalid: true` allows ranges to contain invalids, it only makes sure that the start and end dates are on valid days. If `inRangeInvalid` is `false`, no invalid dates are allowed inside a selection (useful for hotel booking).

```html
<label>
    Outbound
    <input id="start" mbsc-input placeholder="Please select..." />
</label>
<label>
    Return
    <input id="end" mbsc-input placeholder="Please select..." />
</label>
```

```js
mobiscroll.datepicker('#start', {
    controls: ['calendar'],
    select: 'range',
    startInput: '#start',
    endInput: '#end',
    invalid: [
        {
            recurring: {
                repeat: 'weekly',
                weekDays: 'TU,TH'
            },
        },
        new Date(now.getFullYear(), now.getMonth(), 25)
    ],
    inRangeInvalid: true
});
```

### One way or roundtrip

You will probably need to give people the option to select a single date for one way trips. This can be easily accomplished by dynamically setting the `select` option to `'date'`. Toggling with a radio button between `'date'/'range'` will switch between **single date selection** and **range selection**.

```js
document.querySelectorAll('.flight-type').forEach(function (elm) {
    var oneWay = this.value == 'oneway';
    elm.addEventListener('change', function (ev) {
        mobiscroll.getInst(end).setOptions({ disabled: this.value == oneWay });

        if (oneWay) {
            booking.setOptions({
                select: 'date'
            });
        } else {
            booking.setOptions({
                select: 'range'
            });
        }
    });
});;
```

### Alternative to radio buttons

If you don't want to add another control (like a radio button or segmented) to your UI, you can solve the "one way question" with a custom button on the dialog and a little custom logic. The idea is to track if at least one value is selected and dynamically enable the button.

```js
var calendar = mobiscroll.datepicker('#picker', {
    controls: ['calendar'],
    select: 'range',
    buttons: ['set', 'cancel', {
        text: 'One way only',
        disabled: true,
        handler: function () {
            var start = calendar.getTempVal()[0];

            calendar.setVal([start, null]);
            calendar.close();
        }
    }],
    onTempChange: function (event, inst) {
        inst.setOptions({
            buttons: [
                'cancel',
                {
                    text: 'One way only',
                    disabled: !inst.getTempVal()[0],
                    handler: function () {
                        var start = calendar.getTempVal()[0];

                        calendar.setVal([start, null]);
                        calendar.close();
                    }
                },
                'set'
            ]
        });
    }
});
```

## 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
- [`controls`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-controls) - option
- [`select`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-select) - option
- [`display`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-display) - 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
- [`buttons`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-buttons) - option
- [`startInput`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-endInput) - option
- [`inRangeInvalid`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-inRangeInvalid) - option
- [`invalid`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-invalid) - option
- [`setOptions`](https://mobiscroll.com/docs/javascript/datepicker/api#method-setOptions) - method
- [`disabled`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-disabled) - option
- [`getTempVal`](https://mobiscroll.com/docs/javascript/datepicker/api#method-getTempVal) - method
- [`close`](https://mobiscroll.com/docs/javascript/datepicker/api#method-close) - method
- [`onTempChange`](https://mobiscroll.com/docs/javascript/datepicker/api#event-onTempChange) - event

## Code

### JS

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

var now = new Date();
var min = now;
var max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate());

mobiscroll.datepicker('#demo-flight-booking-single', {
  controls: ['calendar'],
  select: 'range',
  display: 'anchored',
  min: min,
  max: max,
  pages: 2,
  buttons: ['set', 'cancel'],
});

mobiscroll.datepicker('#demo-flight-booking-outbound', {
  controls: ['calendar'],
  select: 'range',
  display: 'anchored',
  startInput: '#demo-flight-booking-outbound',
  endInput: '#demo-flight-booking-return',
  min: min,
  max: max,
  pages: 2,
});

mobiscroll.datepicker('#demo-flight-booking-inv-outbound', {
  controls: ['calendar'],
  select: 'range',
  display: 'anchored',
  inRangeInvalid: true,
  startInput: '#demo-flight-booking-inv-outbound',
  endInput: '#demo-flight-booking-inv-return',
  min: min,
  max: max,
  pages: 2,
  invalid: [
    {
      recurring: {
        repeat: 'weekly',
        weekDays: 'TU,TH',
      },
    },
    new Date(now.getFullYear(), now.getMonth(), 25),
  ],
});

var booking = mobiscroll.datepicker('#demo-flight-booking-type-outbound', {
  controls: ['calendar'],
  select: 'range',
  display: 'anchored',
  startInput: '#demo-flight-booking-type-outbound',
  endInput: '#demo-flight-booking-type-return',
  min: min,
  max: max,
  pages: 2,
});

document.querySelectorAll('.demo-flight-type').forEach(function (item) {
  item.addEventListener('change', function (event) {
    var oneWay = event.target.value == 'oneway';

    mobiscroll.getInst(document.getElementById('demo-flight-booking-type-return')).setOptions({ disabled: oneWay });

    if (oneWay) {
      booking.setOptions({
        select: 'date',
      });
    } else {
      booking.setOptions({
        select: 'range',
      });
    }
  });
});

var singleBooking = mobiscroll.datepicker('#demo-flight-booking-custom', {
  controls: ['calendar'],
  select: 'range',
  display: 'anchored',
  min: min,
  max: max,
  pages: 2,
  buttons: [
    'cancel',
    {
      text: 'One way only',
      disabled: true,
      handler: function () {
        var start = singleBooking.getTempVal()[0];

        singleBooking.setVal([start, null]);
        singleBooking.close();
      },
    },
    'set',
  ],
  onTempChange: function (event, inst) {
    inst.setOptions({
      buttons: [
        'cancel',
        {
          text: 'One way only',
          disabled: !inst.getTempVal()[0],
          handler: function () {
            var start = singleBooking.getTempVal()[0];

            singleBooking.setVal([start, null]);
            singleBooking.close();
          },
        },
        'set',
      ],
    });
  },
});
```

### HTML

```html
<div class="mbsc-grid mbsc-no-padding">

    <div class="mbsc-row">
        <div class="mbsc-col-12">
            <label>
                Pick your flight
                <input id="demo-flight-booking-single" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
    </div>

    <div class="mbsc-row">
        <div class="mbsc-col-6">
            <label>
                Outbound
                <input id="demo-flight-booking-outbound" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
        <div class="mbsc-col-6">
            <label>
                Return
                <input id="demo-flight-booking-return" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
    </div>

    <div class="mbsc-row">
        <div class="mbsc-col-6">
            <label>
                Outbound
                <input id="demo-flight-booking-inv-outbound" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
        <div class="mbsc-col-6">
            <label>
                Return
                <input id="demo-flight-booking-inv-return" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
    </div>

    <div class="mbsc-row">
        <label>
            Round trip
            <input class="demo-flight-type" value="round" mbsc-radio data-theme="material" data-theme-variant="light" type="radio" name="flight-type" checked>
        </label>
        <label>
            One way
            <input class="demo-flight-type" value="oneway" mbsc-radio data-theme="material" data-theme-variant="light" type="radio" name="flight-type">
        </label>
    </div>
    <div class="mbsc-row">
        <div class="mbsc-col-6">
            <label>
                Outbound
                <input id="demo-flight-booking-type-outbound" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
        <div class="mbsc-col-6">
            <label>
                Return
                <input id="demo-flight-booking-type-return" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
    </div>

    <div class="mbsc-row">
        <div class="mbsc-col-12">
            <label>
                Pick your flight
                <input id="demo-flight-booking-custom" mbsc-input data-input-style="outline" data-label-style="stacked" placeholder="Please select..." />
            </label>
        </div>
    </div>
</div>
```

## Implementation notes

- Set `select: 'range'` and `controls: ['calendar']` on `Datepicker`; `min`/`max` bound the selectable window (e.g. `min: <today>`, `max: <today + 6 months>`) to prevent picking past dates.
- `pages: 2` shows two months side by side across all five examples.
- `showRangeLabels: true` together with `rangeStartLabel`/`rangeEndLabel` (e.g. `'Outbound'`/`'Return'`) renames the built-in Start/End range labels shown above the calendar.
- Single-input mode needs no extra wiring beyond `select: 'range'`; two-input mode binds `startInput`/`endInput` to separate Outbound/Return inputs - string selectors in JS/jQuery (`startInput: '#start', endInput: '#end'`), refs in React/Vue, template reference variables in Angular.
- Unavailable flight dates are disabled via `invalid`, which accepts both single dates and recurring rules (e.g. `{ recurring: { repeat: 'weekly', weekDays: 'TU,TH' } }` alongside an exact `Date`).
- `inRangeInvalid: true` allows a selected range to pass through invalid dates in the middle while still keeping the range's start/end endpoints on valid days; when `inRangeInvalid` is `false` (or omitted), no invalid date may fall anywhere inside the selected range - useful when a booking must be fully contiguous (e.g. hotel stays).
- Toggling one-way vs. round-trip is done by changing the `select` option at runtime between `'range'` and `'date'` (e.g. via `.setOptions({ select: 'date' })` in JS/jQuery, or updating a bound `select` prop/binding in React/Vue/Angular) based on a radio control's value; the paired Return input can be disabled in step with it (e.g. `.setOptions({ disabled: true })` on that input's instance).
- The footer buttons can be customized through the `buttons` option, including a custom action button object (`{ text, disabled, handler }`); an `onTempChange` handler (React: `onTempChange`; Angular: `(onTempChange)`; Vue: `@temp-change`; JS/jQuery: `onTempChange`) fires while the user is still choosing dates and can re-evaluate the custom button's `disabled` state via `inst.getTempVal()` (returns the in-progress `[start, end]` selection) and rebuild the `buttons` array through `setOptions`.
- The custom button's handler reads the in-progress start date with `getTempVal()`, commits it as a one-way selection via `setVal([start, null])`, and closes the picker with `close()`.

## 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

- Previous: [Date Filter with Preset Ranges](https://demo.mobiscroll.com/javascript/range/date-filtering-with-predefined-ranges.md) - Build a date filtering UI with the Javascript date range picker — choose from custom or preset ranges (Today, Last 7 days, Last 30 days).
- Next: [Rental Availability](https://demo.mobiscroll.com/javascript/range/book-rental-months-ahead.md) - Build a vacation rental availability calendar with the Javascript date range picker — multi-month view with automatic booking validation.
