diff --git a/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue b/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a6a737298d496c3a8b7acdcd7fe4d485c77cace2
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/LessonEventLinkIterator.vue
@@ -0,0 +1,25 @@
+<script>
+export default {
+  name: "LessonEventLinkIterator",
+  props: {
+    items: {
+      type: Array,
+      required: true,
+    },
+    attr: {
+      type: String,
+      required: false,
+      default: "name",
+    },
+  },
+};
+</script>
+
+<template>
+  <span v-bind="$attrs">
+    <span v-for="(item, idx) in items" :key="idx">
+      <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+      {{ item[attr] }}{{ idx + 1 < items.length ? "," : "" }}
+    </span>
+  </span>
+</template>
diff --git a/aleksis/apps/chronos/frontend/components/LessonEventOldNew.vue b/aleksis/apps/chronos/frontend/components/LessonEventOldNew.vue
new file mode 100644
index 0000000000000000000000000000000000000000..11634a63827276f0cad99bed02f0651fb34940ed
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/LessonEventOldNew.vue
@@ -0,0 +1,42 @@
+<script>
+import LessonEventLinkIterator from "./LessonEventLinkIterator.vue";
+
+export default {
+  name: "LessonEventOldNew",
+  components: { LessonEventLinkIterator },
+  props: {
+    oldItems: {
+      type: Array,
+      required: true,
+    },
+    newItems: {
+      type: Array,
+      required: true,
+    },
+    attr: {
+      type: String,
+      required: false,
+      default: "name",
+    },
+  },
+};
+</script>
+
+<template>
+  <span v-bind="$attrs">
+    <span v-if="oldItems.length > 0 && newItems.length > 0">
+      <span class="text-decoration-line-through"
+        ><lesson-event-link-iterator :items="oldItems" :attr="attr"
+      /></span>
+      <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+      <span>→</span>
+      <lesson-event-link-iterator :items="newItems" :attr="attr" />
+    </span>
+    <span v-else-if="newItems.length > 0">
+      <lesson-event-link-iterator :items="newItems" :attr="attr" />
+    </span>
+    <span v-else>
+      <lesson-event-link-iterator :items="oldItems" :attr="attr" />
+    </span>
+  </span>
+</template>
diff --git a/aleksis/apps/chronos/frontend/components/LessonEventSubject.vue b/aleksis/apps/chronos/frontend/components/LessonEventSubject.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d102c9ddf6993ac72ebe60329a0c843589b8f0b0
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/LessonEventSubject.vue
@@ -0,0 +1,42 @@
+<script>
+export default {
+  name: "LessonEventSubject",
+  props: {
+    event: {
+      type: Object,
+      required: true,
+    },
+    attr: {
+      type: String,
+      required: false,
+      default: "name",
+    },
+  },
+};
+</script>
+
+<template>
+  <span v-bind="$attrs">
+    <span
+      v-if="
+        event.meta.subject && event.meta.amended && event.meta.amends.subject
+      "
+    >
+      <span class="text-decoration-line-through">
+        {{ event.meta.amends.subject[attr] }}</span
+      >
+      <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
+      <span>→</span>
+      {{ event.meta.subject[attr] }}
+    </span>
+    <span v-else-if="event.meta.subject">
+      {{ event.meta.subject[attr] }}
+    </span>
+    <span v-else-if="event.amended && event.amends.subject">
+      {{ event.meta.amends.subject[attr] }}
+    </span>
+    <span v-else>
+      {{ event[attr] }}
+    </span>
+  </span>
+</template>
diff --git a/aleksis/apps/chronos/frontend/components/LessonRelatedObjectChip.vue b/aleksis/apps/chronos/frontend/components/LessonRelatedObjectChip.vue
new file mode 100644
index 0000000000000000000000000000000000000000..657cfafd6d27c5da2e890581b53bddcf9841b777
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/LessonRelatedObjectChip.vue
@@ -0,0 +1,32 @@
+<script>
+export default {
+  name: "LessonRelatedObjectChip",
+  props: {
+    status: {
+      type: String,
+      default: "regular",
+      validator: (value) => ["new", "removed", "regular"].includes(value),
+    },
+    newIcon: {
+      type: String,
+      default: "mdi-plus",
+    },
+  },
+};
+</script>
+
+<template>
+  <v-chip
+    label
+    outlined
+    :class="{
+      'mr-2': true,
+      'text-decoration-line-through text--secondary': status === 'removed',
+    }"
+    :color="status === 'new' ? 'warning' : ''"
+  >
+    <v-icon left v-if="status === 'new'">{{ newIcon }}</v-icon>
+
+    <slot></slot>
+  </v-chip>
+</template>
diff --git a/aleksis/apps/chronos/frontend/components/calendar_feeds/details/LessonDetails.vue b/aleksis/apps/chronos/frontend/components/calendar_feeds/details/LessonDetails.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7448d65cbd3f83b5161d08c4a008009920857a32
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/calendar_feeds/details/LessonDetails.vue
@@ -0,0 +1,122 @@
+<template>
+  <base-calendar-feed-details
+    v-bind="$props"
+    :color="currentSubject ? currentSubject.colour_bg : null"
+  >
+    <template #title>
+      <div
+        :style="{
+          color: currentSubject ? currentSubject.colour_fg || 'white' : 'white',
+        }"
+      >
+        <lesson-event-subject :event="selectedEvent" />
+      </div>
+    </template>
+    <template #badge>
+      <cancelled-calendar-status-chip
+        v-if="selectedEvent.meta.cancelled"
+        class="ml-4"
+      />
+      <calendar-status-chip
+        color="warning"
+        icon="mdi-clipboard-alert-outline"
+        v-else-if="selectedEvent.meta.amended"
+        class="ml-4"
+      >
+        {{ $t("chronos.event.current_changes") }}
+      </calendar-status-chip>
+    </template>
+    <template #description>
+      <v-divider inset />
+      <v-list-item v-if="selectedEvent.meta.groups.length > 0">
+        <v-list-item-icon>
+          <v-icon color="primary">mdi-account-group-outline</v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            <lesson-related-object-chip
+              v-for="group in selectedEvent.meta.groups"
+              :key="group.id"
+              >{{ group.name }}</lesson-related-object-chip
+            >
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+      <v-list-item>
+        <v-list-item-icon>
+          <v-icon color="primary">mdi-human-male-board </v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            <span v-if="teachers.length === 0">{{
+              $t("chronos.event.no_teacher")
+            }}</span>
+            <lesson-related-object-chip
+              v-for="teacher in teachers"
+              :status="teacher.status"
+              :key="teacher.id"
+              new-icon="mdi-account-plus-outline"
+              >{{ teacher.full_name }}</lesson-related-object-chip
+            >
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+      <v-list-item>
+        <v-list-item-icon>
+          <v-icon color="primary">mdi-door </v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            <span v-if="rooms.length === 0" class="body-2 text--secondary">{{
+              $t("chronos.event.no_room")
+            }}</span>
+            <lesson-related-object-chip
+              v-for="room in rooms"
+              :status="room.status"
+              :key="room.id"
+              new-icon="mdi-door-open"
+              >{{ room.name }}</lesson-related-object-chip
+            >
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+      <v-divider inset />
+      <v-list-item v-if="selectedEvent.meta.comment">
+        <v-list-item-content>
+          <v-list-item-title>
+            <v-alert
+              dense
+              outlined
+              type="warning"
+              icon="mdi-information-outline"
+            >
+              {{ selectedEvent.meta.comment }}
+            </v-alert>
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+    </template>
+  </base-calendar-feed-details>
+</template>
+
+<script>
+import calendarFeedDetailsMixin from "aleksis.core/mixins/calendarFeedDetails.js";
+import BaseCalendarFeedDetails from "aleksis.core/components/calendar/BaseCalendarFeedDetails.vue";
+import CalendarStatusChip from "aleksis.core/components/calendar/CalendarStatusChip.vue";
+import CancelledCalendarStatusChip from "aleksis.core/components/calendar/CancelledCalendarStatusChip.vue";
+
+import LessonRelatedObjectChip from "../../LessonRelatedObjectChip.vue";
+import lessonEvent from "../mixins/lessonEvent";
+import LessonEventSubject from "../../LessonEventSubject.vue";
+export default {
+  name: "LessonDetails",
+  components: {
+    LessonEventSubject,
+    LessonRelatedObjectChip,
+    BaseCalendarFeedDetails,
+    CalendarStatusChip,
+    CancelledCalendarStatusChip,
+  },
+  mixins: [calendarFeedDetailsMixin, lessonEvent],
+};
+</script>
diff --git a/aleksis/apps/chronos/frontend/components/calendar_feeds/event_bar/LessonEventBar.vue b/aleksis/apps/chronos/frontend/components/calendar_feeds/event_bar/LessonEventBar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..aeaaafbe8893924247e2f96f5cbb671054208c2a
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/calendar_feeds/event_bar/LessonEventBar.vue
@@ -0,0 +1,81 @@
+<template>
+  <base-calendar-feed-event-bar :with-padding="false" v-bind="$props">
+    <template #icon> </template>
+    <template #title>
+      <div
+        :class="{
+          'px-1': true,
+          'orange-border':
+            selectedEvent.meta.amended && !selectedEvent.meta.cancelled,
+          'red-border': selectedEvent.meta.cancelled,
+        }"
+        :style="{
+          color: currentSubject ? currentSubject.colour_fg || 'white' : 'white',
+          height: '100%',
+          borderRadius: '4px',
+        }"
+        class="d-flex justify-center align-center flex-wrap"
+      >
+        <lesson-event-link-iterator
+          v-if="!selectedEvent.meta.is_member"
+          :items="selectedEvent.meta.groups"
+          attr="short_name"
+          class="mr-1"
+        />
+        <lesson-event-old-new
+          v-if="!selectedEvent.meta.is_teacher || newTeachers.length > 0"
+          :new-items="newTeachers"
+          :old-items="oldTeachers"
+          attr="short_name"
+          class="mr-1"
+        />
+
+        <lesson-event-subject
+          :event="selectedEvent"
+          attr="short_name"
+          class="font-weight-medium mr-1"
+        />
+        <lesson-event-old-new
+          :new-items="newRooms"
+          :old-items="oldRooms"
+          attr="short_name"
+        />
+      </div>
+    </template>
+  </base-calendar-feed-event-bar>
+</template>
+
+<script>
+import calendarFeedEventBarMixin from "aleksis.core/mixins/calendarFeedEventBar.js";
+import BaseCalendarFeedEventBar from "aleksis.core/components/calendar/BaseCalendarFeedEventBar.vue";
+import lessonEvent from "../mixins/lessonEvent";
+import LessonEventSubject from "../../LessonEventSubject.vue";
+import LessonEventLinkIterator from "../../LessonEventLinkIterator.vue";
+import LessonEventOldNew from "../../LessonEventOldNew.vue";
+
+export default {
+  name: "LessonEventBar",
+  components: {
+    LessonEventOldNew,
+    LessonEventLinkIterator,
+    LessonEventSubject,
+    BaseCalendarFeedEventBar,
+  },
+  computed: {
+    selectedEvent() {
+      return this.event;
+    },
+  },
+  mixins: [calendarFeedEventBarMixin, lessonEvent],
+};
+</script>
+
+<style scoped>
+.orange-border {
+  border: 4px orange solid;
+}
+
+.red-border {
+  border: 4px red solid;
+}
+</style>
diff --git a/aleksis/apps/chronos/frontend/components/calendar_feeds/mixins/lessonEvent.js b/aleksis/apps/chronos/frontend/components/calendar_feeds/mixins/lessonEvent.js
new file mode 100644
index 0000000000000000000000000000000000000000..64d2b60a12ca67c5b586f9ed9b9e029ad00948ec
--- /dev/null
+++ b/aleksis/apps/chronos/frontend/components/calendar_feeds/mixins/lessonEvent.js
@@ -0,0 +1,78 @@
+/**
+ * Mixin with common used API for custom lesson event calendar components
+ */
+const lessonEvent = {
+  methods: {
+    addStatuses(attr) {
+      let oldItems = this.getOldItems(attr);
+      let newItems = this.getNewItems(attr);
+      let oldIds = oldItems.map((item) => item.id);
+      let newIds = newItems.map((item) => item.id);
+      let itemsWithStatus = oldItems.concat(newItems).map((item) => {
+        let status = "regular";
+        if (newIds.includes(item.id) && !oldIds.includes(item.id)) {
+          status = "new";
+        } else if (
+          newIds.length > 0 &&
+          !newIds.includes(item.id) &&
+          oldIds.includes(item.id)
+        ) {
+          status = "removed";
+        }
+        return { ...item, status: status };
+      });
+      return itemsWithStatus;
+    },
+    getOldItems(attr) {
+      let oldItems = [];
+      if (this.selectedEvent.meta.amended) {
+        oldItems = this.selectedEvent.meta.amends[attr];
+      } else {
+        oldItems = this.selectedEvent.meta[attr];
+      }
+      return oldItems;
+    },
+    getNewItems(attr) {
+      let newItems = [];
+      if (this.selectedEvent.meta.amended) {
+        newItems = this.selectedEvent.meta[attr];
+      }
+      return newItems;
+    },
+  },
+  computed: {
+    teachers() {
+      return this.addStatuses("teachers");
+    },
+    oldTeachers() {
+      return this.getOldItems("teachers");
+    },
+    newTeachers() {
+      return this.getNewItems("teachers");
+    },
+
+    rooms() {
+      return this.addStatuses("rooms");
+    },
+    oldRooms() {
+      return this.getOldItems("rooms");
+    },
+    newRooms() {
+      return this.getNewItems("rooms");
+    },
+    currentSubject() {
+      if (this.selectedEvent.meta.subject) {
+        return this.selectedEvent.meta.subject;
+      } else if (
+        this.selectedEvent.meta.amended &&
+        this.selectedEvent.meta.amends.subject
+      ) {
+        return this.selectedEvent.meta.amends.subject;
+      } else {
+        return null;
+      }
+    },
+  },
+};
+
+export default lessonEvent;
diff --git a/aleksis/apps/chronos/frontend/messages/en.json b/aleksis/apps/chronos/frontend/messages/en.json
index d824f79087540ca0b92d4efe18e3755d562c370f..fb73dca543dfd16a3f9371e3f813fc3d8e4cbd58 100644
--- a/aleksis/apps/chronos/frontend/messages/en.json
+++ b/aleksis/apps/chronos/frontend/messages/en.json
@@ -13,6 +13,11 @@
     },
     "supervisions": {
       "menu_title_daily": "Daily supervisions"
+    },
+    "event": {
+      "no_teacher": "No teacher",
+      "no_room": "No room",
+      "current_changes": "Current changes"
     }
   }
 }
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 24815bb9b2352b1bd8bcff6e51bb635008b2bc84..47cbd7ba588faaf026473662319dab68c92d021c 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -1414,13 +1414,24 @@ class LessonEvent(CalendarEvent):
         blank=True,
     )
 
+    @property
+    def actual_groups(self: "LessonEvent"):
+        return self.groups.all() if self.amends else self.real_amends.groups.all()
+
     @property
     def all_members(self: "LessonEvent") -> list[Person]:
-        return list(itertools.chain(*[list(g.members.all()) for g in self.groups.all()]))
+        return list(itertools.chain(*[list(g.members.all()) for g in self.actual_groups]))
+
+    @property
+    def all_teachers(self: "LessonEvent") -> list[Person]:
+        all_teachers = list(self.teachers.all())
+        if self.amends:
+            all_teachers += list(self.real_amends.teachers.all())
+        return all_teachers
 
     @property
     def group_names(self: "LessonEvent") -> str:
-        return ", ".join([g.name for g in self.groups.all()])
+        return ", ".join([g.name for g in self.actual_groups])
 
     @property
     def teacher_names(self: "LessonEvent") -> str:
@@ -1441,17 +1452,6 @@ class LessonEvent(CalendarEvent):
             return amended_room_names
         return my_room_names
 
-    @property
-    def group_names_with_amends(self: "LessonEvent") -> str:
-        my_group_names = self.group_names
-        amended_group_names = self.real_amends.group_names if self.amends else ""
-
-        if my_group_names and amended_group_names:
-            return _("{} (instead of {})").format(my_group_names, amended_group_names)
-        elif not my_group_names and amended_group_names:
-            return amended_group_names
-        return my_group_names
-
     @property
     def teacher_names_with_amends(self: "LessonEvent") -> str:
         my_teacher_names = self.teacher_names
@@ -1476,6 +1476,7 @@ class LessonEvent(CalendarEvent):
 
     @property
     def real_amends(self: "LessonEvent") -> "LessonEvent":
+        # FIXME THIS IS AWFUL SLOW
         if self.amends:
             return LessonEvent.objects.get(pk=self.amends.pk)
         return self
@@ -1504,11 +1505,13 @@ class LessonEvent(CalendarEvent):
     @classmethod
     def value_color(cls, reference_object: "LessonEvent", request) -> str:
         """Get the color of the event."""
-        return (
-            reference_object.subject.colour_bg
-            if reference_object.subject
-            else super().value_color(reference_object, request)
-        )
+        if reference_object.cancelled:
+            return "#eeeeee"
+        if reference_object.subject:
+            return reference_object.subject.colour_bg
+        if reference_object.amends and reference_object.real_amends.subject:
+            return reference_object.real_amends.subject.colour_bg
+        return super().value_color(reference_object, request)
 
     @classmethod
     def value_organizer(cls, reference_object: "LessonEvent", request) -> str:
@@ -1537,6 +1540,47 @@ class LessonEvent(CalendarEvent):
             return "CANCELLED"
         return "CONFIRMED"
 
+    @classmethod
+    def value_meta(cls, reference_object: "LessonEvent", request) -> str:
+        """Get the meta of the event."""
+        real_amends = reference_object.real_amends
+
+        return {
+            "amended": bool(reference_object.amends),
+            "amends": cls.value_meta(real_amends, request) if reference_object.amends else None,
+            "teachers": [
+                {
+                    "id": t.pk,
+                    "first_name": t.first_name,
+                    "last_name": t.last_name,
+                    "full_name": t.full_name,
+                    "short_name": t.short_name,
+                }
+                for t in reference_object.teachers.all()
+            ],
+            "is_teacher": request.user.person in reference_object.all_teachers,
+            "groups": [
+                {"id": g.pk, "name": g.name, "short_name": g.short_name}
+                for g in reference_object.actual_groups
+            ],
+            "is_member": request.user.person in reference_object.all_members,
+            "rooms": [
+                {"id": r.pk, "name": r.name, "short_name": r.short_name}
+                for r in reference_object.rooms.all()
+            ],
+            "subject": {
+                "id": reference_object.subject.pk,
+                "name": reference_object.subject.name,
+                "short_name": reference_object.subject.short_name,
+                "colour_fg": reference_object.subject.colour_fg,
+                "colour_bg": reference_object.subject.colour_bg,
+            }
+            if reference_object.subject
+            else None,
+            "comment": reference_object.comment,
+            "cancelled": reference_object.cancelled,
+        }
+
     @classmethod
     def get_objects(cls, request) -> Iterable:
         """Return all objects that should be included in the calendar."""