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

# Flight Booking - Vue Range demo

Build a flight booking date picker with the Vue range component — round-trip and one-way selection. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/range/flight-booking)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/datepicker/range/flight-booking)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/datepicker/range/flight-booking)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/range/flight-booking.md), [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)

## 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/vue/range/mobile-desktop-display).

```vue
<script setup>
const now = new Date()
const min = now
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate())
</script>

<template>
  <MbscDatepicker
    select="range"
    :controls="['calendar']"
    :min="min"
    :max="max"
  />
</template>
```

### Using two inputs

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

```vue
<script setup>
const now = new Date()
const min = now
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate())

const startInput = ref(null)
const endInput = ref(null)
</script>

<template>
  <MbscDatepicker
    select="range"
    :controls="['calendar']"
    :startInput="startInput"
    :endInput="endInput"
    :min="min"
    :max="max"
  />
  <MbscInput ref="startInput" label="Outbound" placeholder="Please Select..." />
  <MbscInput ref="endInput" label="Return " placeholder="Please Select..." />
</template>
```

### Not all days have flights

Besides setting min & max boundaries, days without flights should be [disabled](https://demo.mobiscroll.com/vue/range/disabled-invalid-values). These could be single occurrence or [recurring dates](https://demo.mobiscroll.com/vue/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).

```vue
<script setup>
const now = new Date()
const myInvalid = [
  {
    recurring: {
      repeat: 'weekly',
      weekDays: 'TU,TH'
    }
  },
  new Date(now.getFullYear(), now.getMonth(), 25)
]

const startInput = ref(null)
const endInput = ref(null)
</script>

<template>
  <MbscDatepicker
    select="range"
    :controls="['calendar']"
    :startInput="startInput"
    :endInput="endInput"
    :invalid="myInvalid"
    :inRangeInvalid="true"
  />
  <MbscInput ref="startInput" label="Outbound" placeholder="Please Select..." />
  <MbscInput ref="endInput" label="Return " placeholder="Please Select..." />
</template>
```

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

```vue
<script setup>
const bookingType = ref('round')
</script>

<template>
  <MbscRadioGroup v-model="bookingType">
    <MbscRadio value="round" label="Round trip" />
    <MbscRadio value="oneway" label="One way" />
  </MbscRadioGroup>
  <MbscDatepicker
    :select="bookingType == 'oneway' ? 'date' : 'range'"
    :controls="['calendar']"
  />
</template>
```

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

```vue
<script setup>
const calendarRef = ref(null)
const booking = ref(null)
const myButtons = ref([
  'cancel',
  {
    disabled: true,
    text: 'One way only'
  },
  'set'
])

function changeTripType() {
  const startDate = calendarRef.value.instance.getTempVal()[0]
  flightButtons.value = [
    'cancel',
    {
      disabled: startDate != undefined,
      handler: () => {
        booking.value = [startDate, null]
        calendarRef.value.instance.close()
      },
      text: 'One way only'
    },
    'set'
  ]
}
</script>

<template>
  <MbscDatepicker
    ref="calendarRef"
    v-model="booking"
    select="range"
    :controls="['calendar']"
    :buttons="myButtons"
    @temp-change="changeTripType"
  />
</template>
```

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/datepicker/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/datepicker/api#opt-themeVariant) - option
- [`disabled`](https://mobiscroll.com/docs/vue/datepicker/api#opt-disabled) - option
- [`controls`](https://mobiscroll.com/docs/vue/datepicker/api#opt-controls) - option
- [`select`](https://mobiscroll.com/docs/vue/datepicker/api#opt-select) - option
- [`min`](https://mobiscroll.com/docs/vue/datepicker/api#opt-min) - option
- [`max`](https://mobiscroll.com/docs/vue/datepicker/api#opt-max) - option
- [`pages`](https://mobiscroll.com/docs/vue/datepicker/api#opt-pages) - option
- [`buttons`](https://mobiscroll.com/docs/vue/datepicker/api#opt-buttons) - option
- [`inputStyle`](https://mobiscroll.com/docs/vue/datepicker/api#opt-inputStyle) - option
- [`labelStyle`](https://mobiscroll.com/docs/vue/datepicker/api#opt-labelStyle) - option
- [`startInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-endInput) - option
- [`invalid`](https://mobiscroll.com/docs/vue/datepicker/api#opt-invalid) - option
- [`inRangeInvalid`](https://mobiscroll.com/docs/vue/datepicker/api#opt-inRangeInvalid) - option
- [`theme`](https://mobiscroll.com/docs/vue/forms/radio#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/forms/radio#opt-themeVariant) - option
- [`disabled`](https://mobiscroll.com/docs/vue/forms/input#opt-disabled) - option
- [`temp-change`](https://mobiscroll.com/docs/vue/datepicker/api#event-onTempChange) - event

## Code

### SFC JS

```vue
<script setup>
import {
  MbscDatepicker,
  MbscInput,
  MbscPage,
  MbscRadio,
  MbscRadioGroup,
  setOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const now = new Date()
const booking = ref()
const min = now
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate())
const myInvalid = [
  {
    recurring: {
      repeat: 'weekly',
      weekDays: 'TU,TH'
    }
  },
  new Date(now.getFullYear(), now.getMonth(), 25)
]
const calendarRef = ref(null)
const outboundInput = ref(null)
const returnInput = ref(null)
const outboundInv = ref(null)
const returnInv = ref(null)
const outboundBooking = ref(null)
const returnBooking = ref(null)
const trip = ref('round')
const flightButtons = ref([
  'cancel',
  {
    disabled: true,
    text: 'One way only'
  },
  'set'
])

function changeTripType() {
  const startDate = calendarRef.value.instance.getTempVal()[0]
  flightButtons.value = [
    'cancel',
    {
      disabled: startDate != undefined,
      handler: () => {
        booking.value = [startDate, null]
        calendarRef.value.instance.close()
      },
      text: 'One way only'
    },
    'set'
  ]
}
</script>

<template>
  <MbscPage>
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-12">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :buttons="['set', 'cancel']"
            label="Pick your flight"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :startInput="outboundInput"
            :endInput="returnInput"
          />
          <MbscInput
            ref="outboundInput"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnInput"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :invalid="myInvalid"
            :inRangeInvalid="true"
            :startInput="outboundInv"
            :endInput="returnInv"
          />
          <MbscInput
            ref="outboundInv"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnInv"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <MbscRadioGroup
          v-model="trip"
          name="flight-booking-type"
          theme="material"
          themeVariant="light"
        >
          <MbscRadio value="round" label="Round trip" theme="material" themeVariant="light" />
          <MbscRadio value="oneway" label="One way" theme="material" themeVariant="light" />
        </MbscRadioGroup>
      </div>
      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            :select="trip == 'oneway' ? 'date' : 'range'"
            :min="min"
            :max="max"
            :pages="2"
            :startInput="outboundBooking"
            :endInput="returnBooking"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
          <MbscInput
            ref="outboundBooking"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
            :hidden="trip == 'oneway'"
            :style="{ display: trip == 'oneway' ? 'none' : 'block' }"
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnBooking"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
            :disabled="trip == 'oneway'"
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-12">
          <MbscDatepicker
            ref="calendarRef"
            v-model="booking"
            :controls="['calendar']"
            select="range"
            :buttons="flightButtons"
            :min="min"
            :max="max"
            :pages="2"
            label="Pick your flight"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please select..."
            @temp-change="changeTripType"
          />
        </div>
      </div>
    </div>
  </MbscPage>
</template>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  MbscDatepicker,
  MbscInput,
  MbscPage,
  MbscRadio,
  MbscRadioGroup,
  setOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const now = new Date()
const booking = ref()
const min = now
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate())
const myInvalid = [
  {
    recurring: {
      repeat: 'weekly',
      weekDays: 'TU,TH'
    }
  },
  new Date(now.getFullYear(), now.getMonth(), 25)
]
const calendarRef = ref<any>(null)
const outboundInput = ref<any>(null)
const returnInput = ref<any>(null)
const outboundInv = ref<any>(null)
const returnInv = ref<any>(null)
const outboundBooking = ref<any>(null)
const returnBooking = ref<any>(null)
const trip = ref<string>('round')
const flightButtons = ref<any>([
  'cancel',
  {
    disabled: true,
    text: 'One way only'
  },
  'set'
])

function changeTripType() {
  const startDate = calendarRef.value.instance.getTempVal()[0]
  flightButtons.value = [
    'cancel',
    {
      disabled: startDate != undefined,
      handler: () => {
        booking.value = [startDate, null]
        calendarRef.value.instance.close()
      },
      text: 'One way only'
    },
    'set'
  ]
}
</script>

<template>
  <MbscPage>
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-12">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :buttons="['set', 'cancel']"
            label="Pick your flight"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :startInput="outboundInput"
            :endInput="returnInput"
          />
          <MbscInput
            ref="outboundInput"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnInput"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            select="range"
            :min="min"
            :max="max"
            :pages="2"
            :invalid="myInvalid"
            :inRangeInvalid="true"
            :startInput="outboundInv"
            :endInput="returnInv"
          />
          <MbscInput
            ref="outboundInv"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnInv"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
        </div>
      </div>

      <div class="mbsc-row">
        <MbscRadioGroup
          v-model="trip"
          name="flight-booking-type"
          theme="material"
          themeVariant="light"
        >
          <MbscRadio value="round" label="Round trip" theme="material" themeVariant="light" />
          <MbscRadio value="oneway" label="One way" theme="material" themeVariant="light" />
        </MbscRadioGroup>
      </div>
      <div class="mbsc-row">
        <div class="mbsc-col-6">
          <MbscDatepicker
            :controls="['calendar']"
            :select="trip == 'oneway' ? 'date' : 'range'"
            :min="min"
            :max="max"
            :pages="2"
            :startInput="outboundBooking"
            :endInput="returnBooking"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
          />
          <MbscInput
            ref="outboundBooking"
            label="Outbound"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
            :hidden="trip == 'oneway'"
            :style="{ display: trip == 'oneway' ? 'none' : 'block' }"
          />
        </div>
        <div class="mbsc-col-6">
          <MbscInput
            ref="returnBooking"
            label="Return"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please Select..."
            :disabled="trip == 'oneway'"
          />
        </div>
      </div>

      <div class="mbsc-row">
        <div class="mbsc-col-12">
          <MbscDatepicker
            ref="calendarRef"
            v-model="booking"
            :controls="['calendar']"
            select="range"
            :buttons="flightButtons"
            :min="min"
            :max="max"
            :pages="2"
            label="Pick your flight"
            inputStyle="outline"
            labelStyle="stacked"
            placeholder="Please select..."
            @temp-change="changeTripType"
          />
        </div>
      </div>
    </div>
  </MbscPage>
</template>
```

## 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-vue.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 vue
   ```

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 vue --trial`.

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

## Related demos

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