---
title: "React Timeline Employee Shift Slots Example | Mobiscroll"
component: "Timeline"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/timeline/shift-template"
---

# Employee Shift Slots - React Timeline demo

Build a React Timeline with custom shift slot headers — replace hourly intervals with Morning and Afternoon, each with its own time range and color. For React and Next.js.

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

## Description

Use the [`renderSlot`](https://mobiscroll.com/docs/react/eventcalendar/api#renderer-renderSlot) option for rendering a custom time slot header. Customize how the time slots look and what they show. Utilize properties passed in the [`slots` array](https://mobiscroll.com/docs/react/eventcalendar/api#opt-slots).

## What this demo shows

- A desktop timeline configured as a Monday-to-Friday week view where days are arranged horizontally and resources are listed as rows on the left.
- **Header navigation** The month and year label opens date navigation, the previous and next arrows move between weeks, and the Today button returns to the current date.
- **Week view** The date strip shows the selected work week from Monday to Friday, with the current date highlighted.
- **Resources** Employees are listed as separate timeline rows on the left.
- **Shift slots** Each day is divided into Morning and Afternoon slots instead of standard hourly intervals.
- **Custom slot headers** The slot headers show business-specific shift names with their time ranges, such as Morning 7:00-13:00 and Afternoon 12:00-18:00.
- **Slot background** Morning slots have a blue background while afternoon slots have a yellow background color.
- **Event duration** Shift events fill the predefined slot duration instead of representing individual appointment lengths.
- **Event styling** Different shift types use color-coded styling.
- **Event labels** Events appear as colored labels with the start and end time shown in bold.
- **Date positioning** Events are placed by assigned employee, day, and shift slot.
- **Event interaction** Events highlight on hover.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** New shift events can be created by double-clicking the timeline.
- **Scrolling** The timeline supports horizontal and vertical scrolling for navigating across dates, slots, and resources.

## Best for

- **Employee shift planning** Assigning employees to predefined shifts across consecutive workdays.
- **Coverage review** Checking employee availability and shift coverage across Morning and Afternoon periods.
- **Workforce scheduling** Building scheduling interfaces for manufacturing, healthcare, retail, hospitality, field service, and similar operations.
- **Shift-based timelines** Showing scheduled work blocks where the important unit is the shift, not an individual hourly appointment.
- **Business-specific time slots** Replacing standard time intervals with custom slot labels that match the organization's scheduling model.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-themeVariant) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-resources) - option
- [`colors`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-colors) - option
- [`slots`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-slots) - option
- [`clickToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-clickToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToResize) - option
- [`renderSlot`](https://mobiscroll.com/docs/react/eventcalendar/api#renderer-renderSlot) - renderer
- [`onEventUpdate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdate) - event
- [`extendDefaultEvent`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-extendDefaultEvent) - option
- [`cssClass`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-cssClass) - option

## Code

### JSX

```jsx
import { Eventcalendar, formatDate, setOptions } from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';

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

const staff = [
  {
    id: 1,
    name: 'Ryan',
    color: '#e20000',
    title: 'Cloud System Engineer',
    img: 'https://img.mobiscroll.com/demos/m1.png',
  },
  {
    id: 2,
    name: 'Kate',
    color: '#60e81a',
    title: 'Help Desk Specialist',
    img: 'https://img.mobiscroll.com/demos/f1.png',
  },
  {
    id: 3,
    name: 'John',
    color: '#3ba7ff',
    title: 'Application Developer',
    img: 'https://img.mobiscroll.com/demos/m2.png',
  },
];

const defaultShifts = [
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 2,
    slot: 1,
  },
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 3,
    slot: 1,
  },
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 6,
    slot: 1,
  },
  {
    start: '2026-11-07T07:00',
    end: '2026-11-07T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-07T07:00',
    end: '2026-11-07T13:00',
    title: '07:00 - 13:00',
    resource: 2,
    slot: 1,
  },
  {
    start: '2026-11-07T12:00',
    end: '2026-11-07T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
  {
    start: '2026-11-08T07:00',
    end: '2026-11-08T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-08T07:00',
    end: '2026-11-08T13:00',
    title: '07:00 - 13:00',
    resource: 3,
    slot: 1,
  },
  {
    start: '2026-11-08T12:00',
    end: '2026-11-08T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-09T12:00',
    end: '2026-11-09T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-10T07:00',
    end: '2026-11-10T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-10T12:00',
    end: '2026-11-10T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-10T12:00',
    end: '2026-11-10T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
  {
    start: '2026-11-11T07:00',
    end: '2026-11-11T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-11T12:00',
    end: '2026-11-11T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
];

const myColors = [
  {
    background: '#a5ceff4d',
    slot: 1,
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO,TU,WE,TH,FR',
    },
  },
  {
    background: '#f7f7bb4d',
    slot: 2,
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO,TU,WE,TH,FR',
    },
  },
];

const mySlots = [
  {
    id: 1,
    name: 'Morning',
  },
  {
    id: 2,
    name: 'Afternoon',
  },
];

function App() {
  const [shifts, setShifts] = useState(defaultShifts);
  const myView = useMemo(
    () => ({
      timeline: {
        type: 'week',
        eventDisplay: 'fill',
        startDay: 1,
        endDay: 5,
      },
    }),
    [],
  );

  const renderMySlot = useCallback((args) => {
    const slotId = args.slot.id;
    return (
      <div style={{ backgroundColor: slotId === 1 ? '#a5ceff4d' : '#f7f7bb4d', padding: '4px' }}>
        <div className="slot-name">{args.slot.name}</div>
        <div className="slot-time">{slotId === 1 ? '7am - 1pm' : '12pm - 6pm'}</div>
      </div>
    );
  }, []);

  const extendMyDefaultEvent = useCallback((ev) => {
    const d = ev.start;
    const start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), ev.slot === 1 ? 7 : 12);
    const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), ev.slot === 1 ? 13 : 18);

    return {
      title: formatDate('HH:mm', start) + ' - ' + formatDate('HH:mm', end),
      start,
      end,
    };
  }, []);

  const handleEventUpdate = useCallback(
    (args) => {
      const event = args.event;
      const index = shifts.findIndex((x) => x.id === event.id);
      const newShifts = [...shifts];

      newShifts.splice(index, 1, { ...event, title: event.slot === 1 ? '07:00 - 13:00' : '12:00 - 18:00' });
      setShifts(newShifts);
      return false;
    },
    [shifts],
  );

  return (
    <Eventcalendar
      view={myView}
      data={shifts}
      resources={staff}
      colors={myColors}
      slots={mySlots}
      clickToCreate={true}
      dragToMove={true}
      dragToResize={false}
      renderSlot={renderMySlot}
      onEventUpdate={handleEventUpdate}
      extendDefaultEvent={extendMyDefaultEvent}
      cssClass="md-shift-template"
    />
  );
}

export default App;
```

### TSX

```tsx
import {
  Eventcalendar,
  formatDate,
  MbscCalendarColor,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventUpdateEvent,
  MbscNewEventData,
  MbscResource,
  MbscSlot,
  MbscSlotData,
  setOptions,
} from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';

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

const staff: MbscResource[] = [
  {
    id: 1,
    name: 'Ryan',
    color: '#e20000',
    title: 'Cloud System Engineer',
    img: 'https://img.mobiscroll.com/demos/m1.png',
  },
  {
    id: 2,
    name: 'Kate',
    color: '#60e81a',
    title: 'Help Desk Specialist',
    img: 'https://img.mobiscroll.com/demos/f1.png',
  },
  {
    id: 3,
    name: 'John',
    color: '#3ba7ff',
    title: 'Application Developer',
    img: 'https://img.mobiscroll.com/demos/m2.png',
  },
];

const defaultShifts: MbscCalendarEvent[] = [
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 2,
    slot: 1,
  },
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 3,
    slot: 1,
  },
  {
    start: '2026-11-06T07:00',
    end: '2026-11-06T13:00',
    title: '07:00 - 13:00',
    resource: 6,
    slot: 1,
  },
  {
    start: '2026-11-07T07:00',
    end: '2026-11-07T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-07T07:00',
    end: '2026-11-07T13:00',
    title: '07:00 - 13:00',
    resource: 2,
    slot: 1,
  },
  {
    start: '2026-11-07T12:00',
    end: '2026-11-07T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
  {
    start: '2026-11-08T07:00',
    end: '2026-11-08T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-08T07:00',
    end: '2026-11-08T13:00',
    title: '07:00 - 13:00',
    resource: 3,
    slot: 1,
  },
  {
    start: '2026-11-08T12:00',
    end: '2026-11-08T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-09T12:00',
    end: '2026-11-09T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-10T07:00',
    end: '2026-11-10T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-10T12:00',
    end: '2026-11-10T18:00',
    title: '12:00 - 18:00',
    resource: 2,
    slot: 2,
  },
  {
    start: '2026-11-10T12:00',
    end: '2026-11-10T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
  {
    start: '2026-11-11T07:00',
    end: '2026-11-11T13:00',
    title: '07:00 - 13:00',
    resource: 1,
    slot: 1,
  },
  {
    start: '2026-11-11T12:00',
    end: '2026-11-11T18:00',
    title: '12:00 - 18:00',
    resource: 3,
    slot: 2,
  },
];

const myColors: MbscCalendarColor[] = [
  {
    background: '#a5ceff4d',
    slot: 1,
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO,TU,WE,TH,FR',
    },
  },
  {
    background: '#f7f7bb4d',
    slot: 2,
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO,TU,WE,TH,FR',
    },
  },
];

const mySlots: MbscSlot[] = [
  {
    id: 1,
    name: 'Morning',
  },
  {
    id: 2,
    name: 'Afternoon',
  },
];

function App() {
  const [shifts, setShifts] = useState<MbscCalendarEvent[]>(defaultShifts);
  const myView = useMemo<MbscEventcalendarView>(
    () => ({
      timeline: {
        type: 'week',
        eventDisplay: 'fill',
        startDay: 1,
        endDay: 5,
      },
    }),
    [],
  );

  const renderMySlot = (args: MbscSlotData) => {
    const slotId = args.slot.id;
    return (
      <div style={{ backgroundColor: slotId === 1 ? '#a5ceff4d' : '#f7f7bb4d', padding: '4px' }}>
        <div className="slot-name">{args.slot.name}</div>
        <div className="slot-time">{slotId === 1 ? '7am - 1pm' : '12pm - 6pm'}</div>
      </div>
    );
  };

  const extendMyDefaultEvent = useCallback((ev: MbscNewEventData) => {
    const d = ev.start;
    const start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), ev.slot === 1 ? 7 : 12);
    const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), ev.slot === 1 ? 13 : 18);

    return {
      title: formatDate('HH:mm', start) + ' - ' + formatDate('HH:mm', end),
      start,
      end,
    };
  }, []);

  const handleEventUpdate = useCallback(
    (args: MbscEventUpdateEvent) => {
      const event = args.event;
      const index = shifts.findIndex((x) => x.id === event.id);
      const newShifts = [...shifts];

      newShifts.splice(index, 1, { ...event, title: event.slot === 1 ? '07:00 - 13:00' : '12:00 - 18:00' });
      setShifts(newShifts);
      return false;
    },
    [shifts],
  );

  return (
    <Eventcalendar
      view={myView}
      data={shifts}
      resources={staff}
      colors={myColors}
      slots={mySlots}
      clickToCreate={true}
      dragToMove={true}
      dragToResize={false}
      renderSlot={renderMySlot}
      onEventUpdate={handleEventUpdate}
      extendDefaultEvent={extendMyDefaultEvent}
      cssClass="md-shift-template"
    />
  );
}
export default App;
```

### CSS

```css
.slot-name {
  line-height: 20px;
  font-size: 15px;
}

.slot-time {
  opacity: 0.5;
  line-height: 20px;
  font-size: 13px;
}

.md-shift-template .mbsc-timeline-header-placeholder {
  height: 74px;
}

.md-shift-template .mbsc-timeline-slots {
  height: 48px;
  text-align: center;
}

.md-shift-template .mbsc-schedule-event {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
}

.md-shift-template .mbsc-timeline-slot-title {
  padding: 0;
}
```

## Implementation notes

- Use `timeline: { type: 'week', eventDisplay: 'fill', startDay: 1, endDay: 5 }` - a Mon-Fri week view where events fill the full height of their slot row.
- Define 3 staff resources (Ryan, Kate, John), each with `id`, `name`, `color`, `title` (job title), and `img` (avatar URL).
- Define 2 slots: `{ id: 1, name: 'Morning' }` and `{ id: 2, name: 'Afternoon' }`. Each event carries a `slot` property (1 or 2) matching one of these IDs. Morning shift covers 7:00-13:00; afternoon covers 12:00-18:00. Event `title` is the formatted time range (e.g. `'07:00 - 13:00'`).
- Use the `colors` array to apply a semi-transparent background to each slot column across all weekdays. Each entry specifies `background`, `slot` (the slot ID), and `recurring: { repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' }`.
- Use `renderSlot` (Angular: `slotTemplate`, Vue: `#slot`) to render a custom slot header. The renderer receives `args` with `args.slot` - the slot object from the `slots` array (has `args.slot.id` and `args.slot.name`). Render the slot name and its time range string derived from `args.slot.id`.
- Use `extendDefaultEvent` to set correct defaults when a new event is created via click-to-create. The function receives `args` with `args.start` and `args.slot`. Compute `start` and `end` Date objects using the slot's hours (slot 1 → 7:00-13:00, slot 2 → 12:00-18:00), then return `{ title, start, end }` where `title` is formatted with `formatDate('HH:mm', start) + ' - ' + formatDate('HH:mm', end)`.
- Handle `onEventUpdate`: when a drag-to-move moves an event to a different slot, update the event's `title` to match the new slot's time range.
- Set `clickToCreate: true`, `dragToMove: true`, `dragToResize: false`.

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

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

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

## Related demos

- Previous: [Header and Footer Template](https://demo.mobiscroll.com/react/timeline/hour-day-week-month-quarter-year-header-footer-template.md) - Customize time-scale headers and add occupancy footers to a React Timeline — format cells from hour to year and display per-slot utilization.
- Next: [Resource Header Customization](https://demo.mobiscroll.com/react/timeline/resource-header-template.md) - Add custom filters, labels, or contextual UI to React Timeline resource headers — useful for room booking and resource scheduling views.
