Overview

This tutorial shows how to build a Vue Scheduler using the open-source DayPilot Lite scheduling library.

  • Horizontal timeline grouped by month and day

  • Resources on the vertical axis

  • Custom event template with title and duration

  • Lightweight color tagging (stored in tags.color)

  • Context menu for edit/delete

  • Modal form for event creation and editing

The project uses the Composition API and <script setup> syntax.

Installing DayPilot Lite for Vue

Install the Vue package (@daypilot/daypilot-lite-vue) using npm:

npm install @daypilot/daypilot-lite-vue

Then import the Scheduler component:

import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-vue'

Configuring the Horizontal Timeline

timeline

The timeline can be configured using the scale, startDate and days props.

The time header can display multiple rows, grouped by different time units. Here, we define two rows using the timeHeaders property (grouped by month and day):

<DayPilotScheduler
      ...
      :timeHeaders="[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]"
      scale="Day"
      :startDate="startDate"
      :days="days"
      ...
  >
    ...
  </DayPilotScheduler>

The timeline automatically adjusts to the selected month:

const startDate = ref(new DayPilot.Date("2026-09-01"))
const days = computed(() => startDate.value.daysInMonth())

To change the visible month, simply set a new startDate value:

Assign a DayPilot.Date representing the first day of the desired month to startDate.value. The computed days value follows the new month. Reload the event data for that month as well.

Loading Resources and Events

The Scheduler loads event and row data using the events and resources props:

<DayPilotScheduler
      ...
      :events="events"
      :resources="resources"
      ...
  >
    ...
  </DayPilotScheduler>

These props point to Vue ref variables. The loading functions update their values when the component mounts. The sample uses ISO date-time strings for event start and end values, so the data has the same shape as a typical JSON response. The timeline starts in September 2026 to display these events:

const loadEvents = () => {
  events.value = [
    {
      id: 1,
      start: "2026-09-03T00:00:00",
      end: "2026-09-09T00:00:00",
      text: "Event 1",
      resource: "R1",
      tags: {
        color: "#6fa8dc"
      }
    },
    {
      id: 2,
      start: "2026-09-06T00:00:00",
      end: "2026-09-11T00:00:00",
      text: "Event 2",
      resource: "R2",
      tags: {
        color: "#ffd966"
      }
    }
  ]
}

const loadResources = () => {
  resources.value = [
    {name: "Resource 1", id: "R1"},
    {name: "Resource 2", id: "R2"},
    {name: "Resource 3", id: "R3"},
    {name: "Resource 4", id: "R4"},
    {name: "Resource 5", id: "R5"},
    {name: "Resource 6", id: "R6"},
    {name: "Resource 7", id: "R7"},
    {name: "Resource 8", id: "R8"}
  ]
}


onMounted(() => {
  loadEvents()
  loadResources()
})

The structure of the events data objects is defined in the DayPilot.Event.data object API docs.

The resources need to follow the structure described in the DayPilot.Scheduler.resources property docs.

Rendering Custom Event Content using Vue Templates

template

By default, the events display the value of the text property.

You can define custom event content using a Vue template - use the #event slot:

<DayPilotScheduler
  ...
>
  <template #event="{ event }">
    <div class="ev">
      <div class="ev-title">
        {{ event.text() }}
      </div>

      <div class="ev-meta">
        {{ event.duration().totalDays() }} day<span v-if="event.duration().totalDays() > 1">s</span>
      </div>
    </div>
  </template>

</DayPilotScheduler>

In this example, we display the event text in bold on the first line (ev-title) and the duration in days in a badge on the second line (ev-meta).

Setting Per-Event Background Color

colors

To set custom event properties, you can use the onBeforeEventRender event handler.

There, you have access to the source data object (args.data). This is a shallow copy of the data object that you pass to the events prop.

You can use it to read custom properties (e.g. a custom color) and modify the event properties based on your rules.

We use this approach to set a custom color (if defined) and an automatic border color:

const onBeforeEventRender = (args) => {
  const color = args.data.tags?.color || "#aaaaaa"
  args.data.backColor = color
  args.data.borderColor = "darker"
  // ...
}

You can see that we read the event color from the tags object.

The tags object lets you define custom properties without risking a conflict with the reserved property names.

{
      id: 1,
      start: "2026-09-03T00:00:00",
      end: "2026-09-09T00:00:00",
      text: "Event 1",
      resource: "R1",
      tags: {
        color: "#6fa8dc"
      }
    }

Event Context Menu Icon

icon

The onBeforeEventRender handler lets you set additional event properties (for example, disable drag-and-drop moving for specific events) and add active areas - visual elements with a specified position and action.

In our Vue app, we use an active area to add a context menu icon:

const onBeforeEventRender = (args) => {
  // ...
  args.data.areas = [
    {
      top: 14,
      right: 8,
      width: 24,
      height: 24,
      padding: 4,
      symbol: "./icons/daypilot.svg#threedots-v",
      fontColor: "#ffffff",
      backColor: "#00000033",
      borderRadius: "50%",
      action: "ContextMenu"
    }
  ]
}

This active area displays the three dots icon from the DayPilot icon bundle using the symbol property. It opens the default context menu on click (action: "ContextMenu").

You can define the context menu using the contextMenu prop of the Scheduler component:

<DayPilotScheduler
      ...
      :contextMenu="menu"
      ...
  >
    ...
  </DayPilotScheduler>

The context menu is a DayPilot.Menu object. Our context menu defines two items (“Edit…” and “Delete”):

const menu = new DayPilot.Menu({
  items: [
    {
      text: "Edit...",
      onClick: async (args) => {
        const e = args.source
        const modal = await DayPilot.Modal.form(form, e.data)
        if (modal.canceled) {
          return
        }
        const scheduler = schedulerRef.value.control
        scheduler.events.update(modal.result)
      }
    },
    {
      text: "-"
    },
    {
      text: "Delete",
      onClick: (args) => {
        const scheduler = schedulerRef.value.control
        const e = args.source
        scheduler.events.remove(e)
      }
    }
  ]
})

Creating and Editing Events Using Modal Forms

modal

DayPilot includes a built-in modal form implementation that can be used for both new events and editing existing ones.

If needed, you can replace it with a modal form implementation that you already use in your application.

Form definition

The form array defines the form items. Here, it is the event text and color.

const colors = [
  { name: "(default)", id: null },
  { name: "Blue",    id: "#6fa8dc" },
  { name: "Green",   id: "#93c47d" },
  { name: "Yellow",  id: "#ffd966" },
  { name: "Red",     id: "#f6b26b" },
  {name: "Purple", id: "#8e7cc3"}
]

const form = [
  {name: "Text", id: "text", type: "text"},
  {name: "Color", id: "tags.color", type: "select", options: colors}
]

Creating events by selecting a time range

Now you can use the modal form definition for entering details of a new event in onTimeRangeSelected.

const onTimeRangeSelected = async (args) => {
  const scheduler = args.control

  const data = {
    start: args.start,
    end: args.end,
    id: DayPilot.guid(),
    resource: args.resource,
    text: "Event",
    tags: {
      color: null
    }
  }

  const modal = await DayPilot.Modal.form(form, data)
  scheduler.clearSelection()
  if (modal.canceled) {
    return
  }

  scheduler.events.add(modal.result)
}

Changes are stored in the Scheduler in memory. Reloading the page restores the sample data; connect the create, edit, and delete handlers to your backend to persist changes.

Full Source Code

Here is the full source code of our Vue component that defines and displays a horizontal timeline for multiple resources:

<template>
  <DayPilotScheduler
      :timeHeaders="[{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}]"
      scale="Day"
      :startDate="startDate"
      :days="days"
      :events="events"
      :resources="resources"
      :rowHeaderWidth="100"
      :eventBorderRadius="10"
      :durationBarVisible="false"
      :eventHeight="56"
      :rowMarginTop="2"
      :rowMarginBottom="2"
      :contextMenu="menu"
      @timeRangeSelected="onTimeRangeSelected"
      @beforeEventRender="onBeforeEventRender"
      ref="schedulerRef"
  >
    <template #event="{ event }">
      <div class="ev">
        <div class="ev-title">
          {{ event.text() }}
        </div>

        <div class="ev-meta">
          {{ event.duration().totalDays() }} day<span v-if="event.duration().totalDays() > 1">s</span>
        </div>
      </div>
    </template>

  </DayPilotScheduler>
</template>

<script setup>
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-vue'
import {ref, onMounted, computed} from 'vue'

const schedulerRef = ref(null)

const resources = ref([])
const events = ref([])
const startDate = ref(new DayPilot.Date("2026-09-01"))
const days = computed(() => startDate.value.daysInMonth())

const colors = [
  { name: "(default)", id: null },
  { name: "Blue",    id: "#6fa8dc" },
  { name: "Green",   id: "#93c47d" },
  { name: "Yellow",  id: "#ffd966" },
  { name: "Red",     id: "#f6b26b" },
  {name: "Purple", id: "#8e7cc3"}
]

const form = [
  {name: "Text", id: "text", type: "text"},
  {name: "Color", id: "tags.color", type: "select", options: colors}
]

const menu = new DayPilot.Menu({
  items: [
    {
      text: "Edit...",
      onClick: async (args) => {
        const e = args.source
        const modal = await DayPilot.Modal.form(form, e.data)
        if (modal.canceled) {
          return
        }
        const scheduler = schedulerRef.value.control
        scheduler.events.update(modal.result)
      }
    },
    {
      text: "-"
    },
    {
      text: "Delete",
      onClick: (args) => {
        const scheduler = schedulerRef.value.control
        const e = args.source
        scheduler.events.remove(e)
      }
    }
  ]
})

const onTimeRangeSelected = async (args) => {
  const scheduler = args.control

  const data = {
    start: args.start,
    end: args.end,
    id: DayPilot.guid(),
    resource: args.resource,
    text: "Event",
    tags: {
      color: null
    }
  }

  const modal = await DayPilot.Modal.form(form, data)
  scheduler.clearSelection()
  if (modal.canceled) {
    return
  }

  scheduler.events.add(modal.result)
}

const onBeforeEventRender = (args) => {
  const color = args.data.tags?.color || "#aaaaaa"
  args.data.backColor = color
  args.data.borderColor = "darker"
  args.data.areas = [
    {
      top: 14,
      right: 8,
      width: 24,
      height: 24,
      padding: 4,
      symbol: "./icons/daypilot.svg#threedots-v",
      fontColor: "#ffffff",
      backColor: "#00000033",
      borderRadius: "50%",
      action: "ContextMenu"
    }
  ]
}

const loadEvents = () => {
  events.value = [
    {
      id: 1,
      start: "2026-09-03T00:00:00",
      end: "2026-09-09T00:00:00",
      text: "Event 1",
      resource: "R1",
      tags: {
        color: "#6fa8dc"
      }
    },
    {
      id: 2,
      start: "2026-09-06T00:00:00",
      end: "2026-09-11T00:00:00",
      text: "Event 2",
      resource: "R2",
      tags: {
        color: "#ffd966"
      }
    }
  ]
}

const loadResources = () => {
  resources.value = [
    {name: "Resource 1", id: "R1"},
    {name: "Resource 2", id: "R2"},
    {name: "Resource 3", id: "R3"},
    {name: "Resource 4", id: "R4"},
    {name: "Resource 5", id: "R5"},
    {name: "Resource 6", id: "R6"},
    {name: "Resource 7", id: "R7"},
    {name: "Resource 8", id: "R8"}
  ]
}


onMounted(() => {
  loadEvents()
  loadResources()
})
</script>




<style scoped>
.ev {
  position: absolute;
  inset: 6px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 6px;
  overflow: hidden;
}

.ev-title {
  color: #111111;
  font-weight: 700;
  font-size: 14px;
  line-height: 1.15;

  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.ev-meta {
  align-self: flex-start;
  color: #3f3f3f;
  font-size: 12px;
  line-height: 1;

  padding: 4px 10px;
  border-radius: 999px;

  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(0, 0, 0, 0.08);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);

  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
}
</style>