---
title: "Vue Timeline Custom Event Tooltip Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/timeline/custom-event-tooltip"
---

# Custom Event Tooltip - Vue Timeline demo

Display native, custom Mobiscroll, or third-party tooltips on Vue Timeline events — control hover behavior with event hover callbacks. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/timeline/custom-event-tooltip)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/timeline/custom-event-tooltip)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/timeline/custom-event-tooltip)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/timeline/custom-event-tooltip.md), [jQuery](https://demo.mobiscroll.com/jquery/timeline/custom-event-tooltip.md), [Angular](https://demo.mobiscroll.com/angular/timeline/custom-event-tooltip.md), [React](https://demo.mobiscroll.com/react/timeline/custom-event-tooltip.md)

## Description

There are several approaches to showing a tooltip when hovering events.

**The native tooltip**

By default, the calendar shows the browser native tooltip when hovering over the event. This includes the times and title of the event, which does the job most of the times. For showing a custom text use the [`tooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) property of the data object. This tooltip is specific to every event. If you want to hide the native tooltip, you can set the [`showEventTooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-showEventTooltip) to `false`.

**Fully custom tooltip**

Setting the [`showEventTooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-showEventTooltip) to false gives room for a fully custom tooltip that can be implemented by using the [`event-hover-in`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventHoverIn) and [`event-hover-out`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventHoverOut) lifecycle events. With the help of the Mobiscroll popup you can show a custom tooltip that holds details, actions applicable to the event it is anchored to.

**Showing a third party popup**

There are cases when you are using a third party library, something like md-boostrap. For those cases, turn the browser native tooltip off with the [`showEventTooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-showEventTooltip) option and use the third-party tooltip accordingly. In case of md-boostrap, you can add the tooltip directive to the [custom event template](https://demo.mobiscroll.com/vue/timeline/timeline-custom-event-rendering) so that the library knows where and when to show the tooltip.

## What this demo shows

- A desktop daily timeline where hours are arranged horizontally and resources are listed as rows on the left.
- **Header navigation** The month and year label opens date navigation, while the previous and next arrows move between days and the Today button returns to the current date.
- **Day view** The strip below the header shows the selected day with the timeline displays hourly columns from 8 AM to 4 PM.
- **Resources** Multiple resources are shown as separate timeline rows.
- **Current time** A vertical blue line with a time label marks the current time.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event cards** Events are shown as colored cards with a left color stripe, a bold title, and the start and end time.
- **Custom event tooltip** Hovering over an event opens a custom tooltip below the hovered event.
- **Tooltip header** The tooltip header uses the event color and shows the patient name, age, and event start and end time.
- **Tooltip details** The tooltip body shows `Status`, `Reason for visiting`, and `Location` rows.
- **Appointment status** The `Status` row shows whether the appointment is confirmed.
- **Status action** The tooltip shows either a `Confirm appointment` or `Cancel appointment` button based on the current appointment status.
- **Status feedback** Clicking the status action changes the appointment status, closes the tooltip, and shows a toast message on the timeline.
- **Reason for visiting** The tooltip includes a short description of why the patient is visiting.
- **Location** The tooltip shows the appointment location.
- **Footer actions** The footer includes a gray `View patient file` button and a red `Delete appointment` button.
- **View patient action** Clicking `View patient file` shows a toast message with the `View file` text.
- **Delete action** Clicking `Delete appointment` removes the event.
- **Date positioning** Events are positioned by their assigned resource and exact date and time range.
- **Event selection** Clicking an event selects and highlights it.
- **Scrolling** The timeline supports horizontal and vertical scrolling for navigating through time and resources.

## Best for

- **Appointment schedulers** Showing patient appointments with status, reason, location, and event-specific actions.
- **Healthcare scheduling** Building timeline views where users need quick access to appointment details without opening a separate page.
- **Desktop workflows** Presenting a weekly timeline layout with hover states, selection states, and header navigation.
- **Event detail previews** Displaying structured event information in a custom tooltip anchored to the hovered event.
- **Inline appointment actions** Letting users confirm, cancel, view, or delete appointments directly from the scheduler.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToResize) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`showEventTooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-showEventTooltip) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`event-click`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventClick) - event
- [`event-drag-start`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDragStart) - event
- [`event-hover-in`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventHoverIn) - event
- [`event-hover-out`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventHoverOut) - event
- [`display`](https://mobiscroll.com/docs/vue/popup/api#opt-display) - option
- [`anchor`](https://mobiscroll.com/docs/vue/popup/api#opt-anchor) - option
- [`isOpen`](https://mobiscroll.com/docs/vue/popup/api#opt-isOpen) - option
- [`scrollLock`](https://mobiscroll.com/docs/vue/popup/api#opt-scrollLock) - option
- [`showOverlay`](https://mobiscroll.com/docs/vue/popup/api#opt-showOverlay) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/popup/api#opt-touchUi) - option
- [`width`](https://mobiscroll.com/docs/vue/popup/api#opt-width) - option
- [`close`](https://mobiscroll.com/docs/vue/popup/api#event-onClose) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  formatDate,
  MbscButton,
  MbscEventcalendar,
  MbscPopup,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

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

const doctors = [
  { id: 1, name: 'Dr. Breanne Lorinda', color: '#b33d3d' },
  { id: 2, name: 'Dr. Ryan Melicent', color: '#309346' },
  { id: 3, name: 'Dr. Meredith Chantelle', color: '#c77c0a' }
]

const appointments = ref([
  {
    title: 'Jude Chester',
    age: 69,
    start: '2026-10-20T08:00',
    end: '2026-10-20T08:45',
    confirmed: false,
    reason: 'Headaches morning & afternoon',
    location: 'Topmed, Building A, Room 203',
    resource: 1
  },
  {
    title: 'Leon Porter',
    age: 44,
    start: '2026-10-20T09:00',
    end: '2026-10-20T09:45',
    confirmed: false,
    reason: 'Left abdominal pain',
    location: 'Topmed, Building D, Room 360',
    resource: 1
  },
  {
    title: 'Merv Kenny',
    age: 56,
    start: '2026-10-20T10:00',
    end: '2026-10-20T10:45',
    confirmed: true,
    reason: 'Itchy, red rashes',
    location: 'Topmed, Building D, Room 360',
    resource: 1
  },
  {
    title: 'Derek Austyn',
    age: 72,
    start: '2026-10-20T13:00',
    end: '2026-10-20T13:45',
    confirmed: false,
    reason: 'Nausea & weakness',
    location: 'Rose Medical Center, Room 18',
    resource: 1
  },
  {
    title: 'Jenifer Kalyn',
    age: 65,
    start: '2026-10-20T14:00',
    end: '2026-10-20T14:45',
    confirmed: true,
    reason: 'Cough & fever',
    location: 'Rose Medical Center, Room 18',
    resource: 1
  },
  {
    title: 'Lily Racquel',
    age: 54,
    start: '2026-10-20T10:00',
    end: '2026-10-20T10:45',
    confirmed: true,
    reason: 'Dry, persistent cough & headache',
    location: 'Procare, Building C, Room 12',
    resource: 2
  },
  {
    title: 'Mia Sawyer',
    age: 59,
    start: '2026-10-20T11:00',
    end: '2026-10-20T11:45',
    confirmed: true,
    reason: 'Difficulty sleeping & loss of appetite',
    location: 'Procare, Building C, Room 12',
    resource: 2
  },
  {
    title: 'Fred Valdez',
    age: 62,
    start: '2026-10-20T15:00',
    end: '2026-10-20T15:45',
    confirmed: false,
    reason: 'High blood pressure',
    location: 'Procare, Building C, Room 40',
    resource: 2
  },
  {
    title: 'Sylvia Cale',
    age: 41,
    start: '2026-10-20T08:00',
    end: '2026-10-20T08:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Isadora Lyric',
    age: 30,
    start: '2026-10-20T09:00',
    end: '2026-10-20T09:45',
    confirmed: false,
    reason: 'Constant tiredness & weakness',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Jon Candace',
    age: 63,
    start: '2026-10-20T12:00',
    end: '2026-10-20T12:45',
    confirmed: true,
    reason: 'Nausea & weakness',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Layton Drake',
    age: 57,
    start: '2026-10-20T13:00',
    end: '2026-10-20T13:45',
    confirmed: true,
    reason: 'Headaches & loss of appetite',
    location: 'Vitalife, Room 160',
    resource: 3
  },
  {
    title: 'Florence Amy',
    age: 73,
    start: '2026-10-20T14:00',
    end: '2026-10-20T14:45',
    confirmed: false,
    reason: 'Dry, persistent cough & headache',
    location: 'Vitalife, Room 160',
    resource: 3
  },
  {
    title: 'Willis Kane',
    age: 44,
    start: '2026-10-21T08:00',
    end: '2026-10-21T08:45',
    confirmed: true,
    reason: 'Back pain',
    location: 'Care Cente, Room 320r',
    resource: 1
  },
  {
    title: 'Theo Calanthia',
    age: 60,
    start: '2026-10-21T09:00',
    end: '2026-10-21T09:45',
    confirmed: true,
    reason: 'Anxiousness & sleeping disorder',
    location: 'Care Center, Room 320',
    resource: 1
  },
  {
    title: 'Ford Kaiden',
    age: 53,
    start: '2026-10-21T14:00',
    end: '2026-10-21T14:45',
    confirmed: true,
    reason: 'Nausea & vomiting',
    location: 'Care Center, Room 206',
    resource: 1
  },
  {
    title: 'Jewell Ryder',
    age: 75,
    start: '2026-10-21T15:00',
    end: '2026-10-21T15:45',
    confirmed: false,
    reason: 'High blood pressure',
    location: 'Care Center, Room 206',
    resource: 1
  },
  {
    title: 'Antonia Cindra',
    age: 48,
    start: '2026-10-21T12:00',
    end: '2026-10-21T12:45',
    confirmed: true,
    reason: 'Dry, persistent cough',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Gerry Irma',
    age: 50,
    start: '2026-10-21T13:00',
    end: '2026-10-21T13:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Carlyn Dorothy',
    age: 36,
    start: '2026-10-21T14:00',
    end: '2026-10-21T14:45',
    confirmed: true,
    reason: 'Tiredness & muscle pain',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Alma Potter',
    age: 74,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'High blood pressure',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Debra Aguilar',
    age: 47,
    start: '2026-10-19T11:00',
    end: '2026-10-19T11:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Tommie Love',
    age: 42,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: false,
    reason: 'Dry, persistent cough & headache',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Marjorie White',
    age: 55,
    start: '2026-10-19T13:00',
    end: '2026-10-19T13:45',
    confirmed: true,
    reason: 'Back pain',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Brandon Perkins',
    age: 68,
    start: '2026-10-19T14:00',
    end: '2026-10-19T14:45',
    confirmed: true,
    reason: 'Swollen ankles',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Lora Wilson',
    age: 66,
    start: '2026-10-19T15:00',
    end: '2026-10-19T15:45',
    confirmed: false,
    reason: 'Fever & headache',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Ismael Bates',
    age: 58,
    start: '2026-10-19T08:00',
    end: '2026-10-19T08:45',
    confirmed: false,
    reason: 'Tiredness & muscle pain',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Archie Wilkins',
    age: 69,
    start: '2026-10-19T09:00',
    end: '2026-10-19T09:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Christie Baker',
    age: 71,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'Headaches morning & afternoon',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Laura Shelton',
    age: 45,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: false,
    reason: 'Dry, persistent cough',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Mary Hudson',
    age: 77,
    start: '2026-10-19T09:00',
    end: '2026-10-19T09:45',
    confirmed: true,
    reason: 'Fever & sore throat',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Ralph Rice',
    age: 64,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'Left abdominal pain',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Marc Hoffman',
    age: 53,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: true,
    reason: 'Dry, persistent cough & headache',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Arlene Lyons',
    age: 41,
    start: '2026-10-19T14:00',
    end: '2026-10-19T14:45',
    confirmed: true,
    reason: 'Nausea & weakness',
    location: 'Care Center, Room 202',
    resource: 3
  },
  {
    title: 'Thelma Shaw',
    age: 26,
    start: '2026-10-19T15:00',
    end: '2026-10-19T15:45',
    confirmed: true,
    reason: 'Anxiousness & sleeping disorder',
    location: 'Care Center, Room 202',
    resource: 3
  },
  {
    title: 'Dory Edie',
    age: 45,
    start: '2026-10-18T09:00',
    end: '2026-10-18T09:45',
    confirmed: true,
    reason: 'Right abdominal pain',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Kaylin Toni',
    age: 68,
    start: '2026-10-18T10:00',
    end: '2026-10-18T10:45',
    confirmed: true,
    reason: 'Itchy, red rashes',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Gray Kestrel',
    age: 60,
    start: '2026-10-18T12:00',
    end: '2026-10-18T12:45',
    confirmed: true,
    reason: 'Cough & fever',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Reg Izabelle',
    age: 41,
    start: '2026-10-18T14:00',
    end: '2026-10-18T14:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Medica Zone, Room 13',
    resource: 2
  },
  {
    title: 'Lou Andie',
    age: 76,
    start: '2026-10-18T15:00',
    end: '2026-10-18T15:45',
    confirmed: true,
    reason: 'High blood pressure',
    location: 'Medica Zone, Room 13',
    resource: 2
  },
  {
    title: 'Yancy Dustin',
    age: 52,
    start: '2026-10-18T10:00',
    end: '2026-10-18T10:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Vitacure, Building E, Room 50',
    resource: 3
  },
  {
    title: 'Terry Clark',
    age: 78,
    start: '2026-10-18T11:00',
    end: '2026-10-18T11:45',
    confirmed: true,
    reason: 'Swollen ankles',
    location: 'Vitacure, Building E, Room 50',
    resource: 3
  }
])

const appointment = ref()
const appointmentInfo = ref()
const appointmentLocation = ref()
const appointmentReason = ref()
const appointmentStatus = ref()
const appointmentTime = ref()
const buttonText = ref()
const buttonType = ref()
const isTooltipOpen = ref(false)
const isToastOpen = ref(false)
const toastMessage = ref('')
const tooltipAnchor = ref()
const tooltipColor = ref()
const timer = ref()

const myView = ref({
  timeline: {
    type: 'day',
    startDay: 1,
    endDay: 5,
    startTime: '08:00',
    endTime: '16:00'
  }
})

function openTooltip(args) {
  const event = args.event
  const doctor = args.resourceObj
  const time =
    formatDate('hh:mm A', new Date(event.start)) +
    ' - ' +
    formatDate('hh:mm A', new Date(event.end))

  if (timer.value) {
    clearTimeout(timer.value)
    timer.value = null
  }

  if (event.confirmed) {
    appointmentStatus.value = 'Confirmed'
    buttonText.value = 'Cancel appointment'
    buttonType.value = 'warning'
  } else {
    appointmentStatus.value = 'Canceled'
    buttonText.value = 'Confirm appointment'
    buttonType.value = 'success'
  }

  appointment.value = event
  appointmentInfo.value = event.title + ', Age: ' + event.age
  appointmentLocation.value = event.location
  appointmentTime.value = time
  appointmentReason.value = event.reason

  tooltipColor.value = doctor.color
  tooltipAnchor.value = args.domEvent.target.closest('.mbsc-timeline-event')

  isTooltipOpen.value = true
}

function handleEventClick(args) {
  openTooltip(args)
}

function handleEventDragStart() {
  isTooltipOpen.value = false
}

function handleEventHoverIn(args) {
  openTooltip(args)
}

function handleEventHoverout() {
  if (!timer.value) {
    timer.value = setTimeout(() => {
      isTooltipOpen.value = false
    }, 200)
  }
}

function handleMouseEnter() {
  if (timer.value) {
    clearTimeout(timer.value)
    timer.value = null
  }
}

function handleMouseLeave() {
  timer.value = setTimeout(() => {
    isTooltipOpen.value = false
  }, 200)
}

function updateAppointmentStatus() {
  appointment.value.confirmed = !appointment.value.confirmed
  toastMessage.value = 'Appointment ' + (appointment.value.confirmed ? 'confirmed' : 'canceled')
  isToastOpen.value = true
  isTooltipOpen.value = false
}

function viewAppointmentFile() {
  toastMessage.value = 'View file'
  isToastOpen.value = true
  isTooltipOpen.value = false
}

function deleteAppointment() {
  appointments.value = appointments.value.filter((item) => item.id !== appointment.value.id)
  toastMessage.value = 'Appointment deleted'
  isToastOpen.value = true
  isTooltipOpen.value = false
}
</script>

<template>
  <MbscEventcalendar
    :clickToCreate="false"
    :dragToCreate="false"
    :dragToMove="true"
    :dragToResize="false"
    :data="appointments"
    :resources="doctors"
    :showEventTooltip="false"
    :view="myView"
    @event-click="handleEventClick"
    @event-drag-start="handleEventDragStart"
    @event-hover-in="handleEventHoverIn"
    @event-hover-out="handleEventHoverout"
  />
  <MbscPopup
    display="anchored"
    :anchor="tooltipAnchor"
    :contentPadding="false"
    :isOpen="isTooltipOpen"
    :scrollLock="false"
    :showOverlay="false"
    :touchUi="false"
    :width="380"
    @close="isTooltipOpen = false"
  >
    <div class="mds-tooltip" @mouseenter="handleMouseEnter()" @mouseleave="handleMouseLeave()">
      <div class="mds-tooltip-header" :style="{ background: tooltipColor }">
        <span>{{ appointmentInfo }}</span>
        <span class="mbsc-pull-right">{{ appointmentTime }}</span>
      </div>
      <div class="mbsc-padding">
        <div class="md-tooltip-title">
          Status: <span class="mds-tooltip-label mbsc-margin">{{ appointmentStatus }}</span>
          <MbscButton
            :color="buttonType"
            variant="outline"
            className="mds-tooltip-button mbsc-pull-right"
            @click="updateAppointmentStatus()"
          >
            {{ buttonText }}
          </MbscButton>
        </div>
        <div class="mds-tooltip-label mbsc-margin">
          Reason for visit: <span class="mbsc-light">{{ appointmentReason }}</span>
        </div>
        <div class="mds-tooltip-label mbsc-margin">
          Location: <span class="mbsc-light">{{ appointmentLocation }}</span>
        </div>
        <MbscButton color="secondary" className="mds-tooltip-button" @click="viewAppointmentFile()">
          View patient file
        </MbscButton>
        <MbscButton
          color="danger"
          variant="outline"
          className="mds-tooltip-button mbsc-pull-right"
          @click="deleteAppointment()"
        >
          Delete appointment
        </MbscButton>
      </div>
    </div>
  </MbscPopup>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
</template>

<style>
.mds-tooltip {
  font-size: 15px;
  font-weight: 600;
}

.mds-tooltip-header {
  padding: 12px 16px;
  color: #eee;
}

.mds-tooltip-label {
  line-height: 32px;
}

.mds-tooltip-button.mbsc-button {
  font-size: 16px;
  margin: 0;
  line-height: 32px;
  padding-top: 0;
  padding-bottom: 0;
}

.mds-tooltip-button .mbsc-button-txt {
  font-size: 14px;
  padding-left: 0;
  padding-right: 0;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  formatDate,
  MbscButton,
  MbscEventcalendar,
  MbscPopup,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscResource
} from '@mobiscroll/vue'
import { ref } from 'vue'

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

const doctors: MbscResource[] = [
  { id: 1, name: 'Dr. Breanne Lorinda', color: '#b33d3d' },
  { id: 2, name: 'Dr. Ryan Melicent', color: '#309346' },
  { id: 3, name: 'Dr. Meredith Chantelle', color: '#c77c0a' }
]

const appointments = ref<MbscCalendarEvent[]>([
  {
    title: 'Jude Chester',
    age: 69,
    start: '2026-10-20T08:00',
    end: '2026-10-20T08:45',
    confirmed: false,
    reason: 'Headaches morning & afternoon',
    location: 'Topmed, Building A, Room 203',
    resource: 1
  },
  {
    title: 'Leon Porter',
    age: 44,
    start: '2026-10-20T09:00',
    end: '2026-10-20T09:45',
    confirmed: false,
    reason: 'Left abdominal pain',
    location: 'Topmed, Building D, Room 360',
    resource: 1
  },
  {
    title: 'Merv Kenny',
    age: 56,
    start: '2026-10-20T10:00',
    end: '2026-10-20T10:45',
    confirmed: true,
    reason: 'Itchy, red rashes',
    location: 'Topmed, Building D, Room 360',
    resource: 1
  },
  {
    title: 'Derek Austyn',
    age: 72,
    start: '2026-10-20T13:00',
    end: '2026-10-20T13:45',
    confirmed: false,
    reason: 'Nausea & weakness',
    location: 'Rose Medical Center, Room 18',
    resource: 1
  },
  {
    title: 'Jenifer Kalyn',
    age: 65,
    start: '2026-10-20T14:00',
    end: '2026-10-20T14:45',
    confirmed: true,
    reason: 'Cough & fever',
    location: 'Rose Medical Center, Room 18',
    resource: 1
  },
  {
    title: 'Lily Racquel',
    age: 54,
    start: '2026-10-20T10:00',
    end: '2026-10-20T10:45',
    confirmed: true,
    reason: 'Dry, persistent cough & headache',
    location: 'Procare, Building C, Room 12',
    resource: 2
  },
  {
    title: 'Mia Sawyer',
    age: 59,
    start: '2026-10-20T11:00',
    end: '2026-10-20T11:45',
    confirmed: true,
    reason: 'Difficulty sleeping & loss of appetite',
    location: 'Procare, Building C, Room 12',
    resource: 2
  },
  {
    title: 'Fred Valdez',
    age: 62,
    start: '2026-10-20T15:00',
    end: '2026-10-20T15:45',
    confirmed: false,
    reason: 'High blood pressure',
    location: 'Procare, Building C, Room 40',
    resource: 2
  },
  {
    title: 'Sylvia Cale',
    age: 41,
    start: '2026-10-20T08:00',
    end: '2026-10-20T08:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Isadora Lyric',
    age: 30,
    start: '2026-10-20T09:00',
    end: '2026-10-20T09:45',
    confirmed: false,
    reason: 'Constant tiredness & weakness',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Jon Candace',
    age: 63,
    start: '2026-10-20T12:00',
    end: '2026-10-20T12:45',
    confirmed: true,
    reason: 'Nausea & weakness',
    location: 'MedStar, Building A, Room 1',
    resource: 3
  },
  {
    title: 'Layton Drake',
    age: 57,
    start: '2026-10-20T13:00',
    end: '2026-10-20T13:45',
    confirmed: true,
    reason: 'Headaches & loss of appetite',
    location: 'Vitalife, Room 160',
    resource: 3
  },
  {
    title: 'Florence Amy',
    age: 73,
    start: '2026-10-20T14:00',
    end: '2026-10-20T14:45',
    confirmed: false,
    reason: 'Dry, persistent cough & headache',
    location: 'Vitalife, Room 160',
    resource: 3
  },
  {
    title: 'Willis Kane',
    age: 44,
    start: '2026-10-21T08:00',
    end: '2026-10-21T08:45',
    confirmed: true,
    reason: 'Back pain',
    location: 'Care Cente, Room 320r',
    resource: 1
  },
  {
    title: 'Theo Calanthia',
    age: 60,
    start: '2026-10-21T09:00',
    end: '2026-10-21T09:45',
    confirmed: true,
    reason: 'Anxiousness & sleeping disorder',
    location: 'Care Center, Room 320',
    resource: 1
  },
  {
    title: 'Ford Kaiden',
    age: 53,
    start: '2026-10-21T14:00',
    end: '2026-10-21T14:45',
    confirmed: true,
    reason: 'Nausea & vomiting',
    location: 'Care Center, Room 206',
    resource: 1
  },
  {
    title: 'Jewell Ryder',
    age: 75,
    start: '2026-10-21T15:00',
    end: '2026-10-21T15:45',
    confirmed: false,
    reason: 'High blood pressure',
    location: 'Care Center, Room 206',
    resource: 1
  },
  {
    title: 'Antonia Cindra',
    age: 48,
    start: '2026-10-21T12:00',
    end: '2026-10-21T12:45',
    confirmed: true,
    reason: 'Dry, persistent cough',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Gerry Irma',
    age: 50,
    start: '2026-10-21T13:00',
    end: '2026-10-21T13:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Carlyn Dorothy',
    age: 36,
    start: '2026-10-21T14:00',
    end: '2026-10-21T14:45',
    confirmed: true,
    reason: 'Tiredness & muscle pain',
    location: 'Medica Zone, Building C, Room 2',
    resource: 3
  },
  {
    title: 'Alma Potter',
    age: 74,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'High blood pressure',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Debra Aguilar',
    age: 47,
    start: '2026-10-19T11:00',
    end: '2026-10-19T11:45',
    confirmed: false,
    reason: 'Fever & sore throat',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Tommie Love',
    age: 42,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: false,
    reason: 'Dry, persistent cough & headache',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Marjorie White',
    age: 55,
    start: '2026-10-19T13:00',
    end: '2026-10-19T13:45',
    confirmed: true,
    reason: 'Back pain',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Brandon Perkins',
    age: 68,
    start: '2026-10-19T14:00',
    end: '2026-10-19T14:45',
    confirmed: true,
    reason: 'Swollen ankles',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Lora Wilson',
    age: 66,
    start: '2026-10-19T15:00',
    end: '2026-10-19T15:45',
    confirmed: false,
    reason: 'Fever & headache',
    location: 'Vitacure, Building D, Room 2',
    resource: 1
  },
  {
    title: 'Ismael Bates',
    age: 58,
    start: '2026-10-19T08:00',
    end: '2026-10-19T08:45',
    confirmed: false,
    reason: 'Tiredness & muscle pain',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Archie Wilkins',
    age: 69,
    start: '2026-10-19T09:00',
    end: '2026-10-19T09:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Christie Baker',
    age: 71,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'Headaches morning & afternoon',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Laura Shelton',
    age: 45,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: false,
    reason: 'Dry, persistent cough',
    location: 'Care Center, Room 300',
    resource: 2
  },
  {
    title: 'Mary Hudson',
    age: 77,
    start: '2026-10-19T09:00',
    end: '2026-10-19T09:45',
    confirmed: true,
    reason: 'Fever & sore throat',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Ralph Rice',
    age: 64,
    start: '2026-10-19T10:00',
    end: '2026-10-19T10:45',
    confirmed: true,
    reason: 'Left abdominal pain',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Marc Hoffman',
    age: 53,
    start: '2026-10-19T12:00',
    end: '2026-10-19T12:45',
    confirmed: true,
    reason: 'Dry, persistent cough & headache',
    location: 'Medica Zone, Room 45',
    resource: 3
  },
  {
    title: 'Arlene Lyons',
    age: 41,
    start: '2026-10-19T14:00',
    end: '2026-10-19T14:45',
    confirmed: true,
    reason: 'Nausea & weakness',
    location: 'Care Center, Room 202',
    resource: 3
  },
  {
    title: 'Thelma Shaw',
    age: 26,
    start: '2026-10-19T15:00',
    end: '2026-10-19T15:45',
    confirmed: true,
    reason: 'Anxiousness & sleeping disorder',
    location: 'Care Center, Room 202',
    resource: 3
  },
  {
    title: 'Dory Edie',
    age: 45,
    start: '2026-10-18T09:00',
    end: '2026-10-18T09:45',
    confirmed: true,
    reason: 'Right abdominal pain',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Kaylin Toni',
    age: 68,
    start: '2026-10-18T10:00',
    end: '2026-10-18T10:45',
    confirmed: true,
    reason: 'Itchy, red rashes',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Gray Kestrel',
    age: 60,
    start: '2026-10-18T12:00',
    end: '2026-10-18T12:45',
    confirmed: true,
    reason: 'Cough & fever',
    location: 'Vitacure, Building A, Room 203',
    resource: 2
  },
  {
    title: 'Reg Izabelle',
    age: 41,
    start: '2026-10-18T14:00',
    end: '2026-10-18T14:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Medica Zone, Room 13',
    resource: 2
  },
  {
    title: 'Lou Andie',
    age: 76,
    start: '2026-10-18T15:00',
    end: '2026-10-18T15:45',
    confirmed: true,
    reason: 'High blood pressure',
    location: 'Medica Zone, Room 13',
    resource: 2
  },
  {
    title: 'Yancy Dustin',
    age: 52,
    start: '2026-10-18T10:00',
    end: '2026-10-18T10:45',
    confirmed: true,
    reason: 'Fever & headache',
    location: 'Vitacure, Building E, Room 50',
    resource: 3
  },
  {
    title: 'Terry Clark',
    age: 78,
    start: '2026-10-18T11:00',
    end: '2026-10-18T11:45',
    confirmed: true,
    reason: 'Swollen ankles',
    location: 'Vitacure, Building E, Room 50',
    resource: 3
  }
])

const appointment = ref<MbscCalendarEvent>()
const appointmentInfo = ref<string>()
const appointmentLocation = ref<string>()
const appointmentReason = ref<string>()
const appointmentStatus = ref<string>()
const appointmentTime = ref<string>()
const buttonText = ref<string>()
const buttonType = ref<'success' | 'warning'>()
const isTooltipOpen = ref<boolean>(false)
const isToastOpen = ref<boolean>(false)
const toastMessage = ref<string>('')
const tooltipAnchor = ref<HTMLElement>()
const tooltipColor = ref<string>()
const timer = ref<ReturnType<typeof setTimeout>>()

const myView = ref<MbscEventcalendarView>({
  timeline: {
    type: 'day',
    startDay: 1,
    endDay: 5,
    startTime: '08:00',
    endTime: '16:00'
  }
})

function openTooltip(args: MbscEventClickEvent) {
  const event: MbscCalendarEvent = args.event
  const doctor = args.resourceObj!
  const time =
    formatDate('hh:mm A', new Date(event.start as string)) +
    ' - ' +
    formatDate('hh:mm A', new Date(event.end as string))

  if (timer.value) {
    clearTimeout(timer.value)
    timer.value = undefined
  }

  if (event.confirmed) {
    appointmentStatus.value = 'Confirmed'
    buttonText.value = 'Cancel appointment'
    buttonType.value = 'warning'
  } else {
    appointmentStatus.value = 'Canceled'
    buttonText.value = 'Confirm appointment'
    buttonType.value = 'success'
  }

  appointment.value = event
  appointmentInfo.value = event.title + ', Age: ' + event.age
  appointmentLocation.value = event.location
  appointmentTime.value = time
  appointmentReason.value = event.reason

  tooltipColor.value = doctor.color
  tooltipAnchor.value = args.domEvent.target.closest('.mbsc-timeline-event')

  isTooltipOpen.value = true
}

function handleEventClick() {
  isTooltipOpen.value = true
}

function handleEventDragStart() {
  isTooltipOpen.value = false
}

function handleEventHoverIn(args: MbscEventClickEvent) {
  openTooltip(args)
}

function handleEventHoverout() {
  if (!timer.value) {
    timer.value = setTimeout(() => {
      isTooltipOpen.value = false
    }, 200)
  }
}

function handleMouseEnter() {
  if (timer.value) {
    clearTimeout(timer.value)
    timer.value = undefined
  }
}

function handleMouseLeave() {
  timer.value = setTimeout(() => {
    isTooltipOpen.value = false
  }, 200)
}

function updateAppointmentStatus() {
  appointment.value!.confirmed = !appointment.value!.confirmed
  toastMessage.value = 'Appointment ' + (appointment.value!.confirmed ? 'confirmed' : 'canceled')
  isToastOpen.value = true
  isTooltipOpen.value = false
}

function viewAppointmentFile() {
  toastMessage.value = 'View file'
  isToastOpen.value = true
  isTooltipOpen.value = false
}

function deleteAppointment() {
  appointments.value = appointments.value.filter((item) => item.id !== appointment.value!.id)
  toastMessage.value = 'Appointment deleted'
  isToastOpen.value = true
  isTooltipOpen.value = false
}
</script>

<template>
  <MbscEventcalendar
    :clickToCreate="false"
    :dragToCreate="false"
    :dragToMove="true"
    :dragToResize="false"
    :data="appointments"
    :resources="doctors"
    :showEventTooltip="false"
    :view="myView"
    @event-click="handleEventClick"
    @event-drag-start="handleEventDragStart"
    @event-hover-in="handleEventHoverIn"
    @event-hover-out="handleEventHoverout"
  />
  <MbscPopup
    display="anchored"
    :anchor="tooltipAnchor"
    :contentPadding="false"
    :isOpen="isTooltipOpen"
    :scrollLock="false"
    :showOverlay="false"
    :touchUi="false"
    :width="380"
    @close="isTooltipOpen = false"
  >
    <div class="mds-tooltip" @mouseenter="handleMouseEnter()" @mouseleave="handleMouseLeave()">
      <div class="mds-tooltip-header" :style="{ background: tooltipColor }">
        <span>{{ appointmentInfo }}</span>
        <span class="mbsc-pull-right">{{ appointmentTime }}</span>
      </div>
      <div class="mbsc-padding">
        <div class="md-tooltip-title">
          Status: <span class="mds-tooltip-label mbsc-margin">{{ appointmentStatus }}</span>
          <MbscButton
            :color="buttonType"
            variant="outline"
            className="mds-tooltip-button mbsc-pull-right"
            @click="updateAppointmentStatus()"
          >
            {{ buttonText }}
          </MbscButton>
        </div>
        <div class="mds-tooltip-label mbsc-margin">
          Reason for visit: <span class="mbsc-light">{{ appointmentReason }}</span>
        </div>
        <div class="mds-tooltip-label mbsc-margin">
          Location: <span class="mbsc-light">{{ appointmentLocation }}</span>
        </div>
        <MbscButton color="secondary" className="mds-tooltip-button" @click="viewAppointmentFile()">
          View patient file
        </MbscButton>
        <MbscButton
          color="danger"
          variant="outline"
          className="mds-tooltip-button mbsc-pull-right"
          @click="deleteAppointment()"
        >
          Delete appointment
        </MbscButton>
      </div>
    </div>
  </MbscPopup>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
</template>

<style>
.mds-tooltip {
  font-size: 15px;
  font-weight: 600;
}

.mds-tooltip-header {
  padding: 12px 16px;
  color: #eee;
}

.mds-tooltip-label {
  line-height: 32px;
}

.mds-tooltip-button.mbsc-button {
  font-size: 16px;
  margin: 0;
  line-height: 32px;
  padding-top: 0;
  padding-bottom: 0;
}

.mds-tooltip-button .mbsc-button-txt {
  font-size: 14px;
  padding-left: 0;
  padding-right: 0;
}
</style>
```

## Implementation notes

- Use `timeline: { type: 'day', startDay: 1, endDay: 5, startTime: '08:00', endTime: '16:00' }` - Mon-Fri, 8am-4pm.
- Define 3 doctor resources, each with `id`, `name`, and `color`. Each appointment event carries custom properties: `age`, `confirmed` (boolean), `reason`, and `location`, in addition to the standard `title`, `start`, `end`, and `resource`.
- Set `showEventTooltip: false` to suppress the native browser tooltip. Set `dragToMove: true`; disable all create and resize interactions.
- Use a `Popup` component as the custom tooltip. Configure it with `display="anchored"`, `showOverlay={false}`, `scrollLock={false}`, `touchUi={false}`, `contentPadding={false}`. The `anchor` prop is set dynamically to the hovered event's DOM element, obtained via `args.domEvent.target.closest('.mbsc-timeline-event')` inside the hover handler.
- **Tooltip open/close logic** - use a shared timer ref to bridge the gap between the event element and the popup:
  - `onEventHoverIn` / `onEventClick`: clear any pending close timer, populate popup state from `args.event` (title, age, confirmed, reason, location) and `args.resourceObj` (color), format the time range with `formatDate`, set the anchor, and open the popup.
  - `onEventHoverOut`: start a 200ms timer to close the popup.
  - Mouse-enter on the popup itself: clear the close timer so the popup stays open when the cursor moves from the event onto the popup.
  - Mouse-leave on the popup: start a 200ms close timer.
  - `onEventDragStart`: close the popup immediately.
- **Popup content**: a colored header showing patient name + age and the formatted appointment time (color driven by `args.resourceObj.color`); a body with the appointment status (Confirmed/Canceled) and a toggle button (`Button color="warning"|"success" variant="outline"`), reason for visit, location, a "View patient file" button (`color="secondary"`), and a "Delete appointment" button (`color="danger" variant="outline"`).
- Use a `Toast` component to confirm user actions: status toggle, file view, and deletion.

## 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: [Resource Header Customization](https://demo.mobiscroll.com/vue/timeline/resource-header-template.md) - Add custom filters, labels, or contextual UI to Vue Timeline resource headers — useful for room booking and resource scheduling views.
- Next: [Resource Row Height](https://demo.mobiscroll.com/vue/timeline/setting-row-height.md) - Set fixed heights for resource rows, control parent row sizing, and remove bottom gutter spacing on a Vue Timeline using CSS overrides.
