diff --git a/aleksis/core/frontend/components/calendar/BaseCalendarFeedDetails.vue b/aleksis/core/frontend/components/calendar/BaseCalendarFeedDetails.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2db52ae5ef86ecf4534aee70ffd7d9939dffe453
--- /dev/null
+++ b/aleksis/core/frontend/components/calendar/BaseCalendarFeedDetails.vue
@@ -0,0 +1,64 @@
+<template>
+  <v-menu
+    v-model="model"
+    :close-on-content-click="false"
+    :activator="selectedElement"
+    offset-x
+  >
+    <v-card min-width="350px" flat>
+      <v-toolbar :color="selectedEvent.color" dark dense>
+        <v-toolbar-title>
+          <slot name="title" :selected-event="selectedEvent">{{
+            selectedEvent.name
+          }}</slot>
+        </v-toolbar-title>
+        <v-spacer></v-spacer>
+        <slot name="badge" :selected-event="selectedEvent">
+          <v-chip
+            v-if="selectedEvent.status === 'CANCELLED' && !withoutBadge"
+            color="error"
+            label
+          >
+            <v-avatar left>
+              <v-icon>mdi-cancel</v-icon>
+            </v-avatar>
+            {{ $t("calendar.cancelled") }}
+          </v-chip>
+        </slot>
+      </v-toolbar>
+      <slot name="time" :selected-event="selectedEvent">
+        <v-card-text v-if="!withoutTime">
+          <v-icon left>mdi-calendar-today-outline</v-icon>
+          <span v-if="selectedEvent.start !== selectedEvent.end">
+            {{ $d(selectedEvent.start, "shortDateTime") }} –
+            {{ $d(selectedEvent.end, "shortDateTime") }}
+          </span>
+          <span v-else> {{ $d(selectedEvent.start, "shortDateTime") }}</span>
+        </v-card-text>
+      </slot>
+      <slot name="description" :selected-event="selectedEvent">
+        <v-divider v-if="selectedEvent.description && !withoutDescription" />
+        <v-card-text
+          class="d-flex"
+          v-if="selectedEvent.description && !withoutDescription"
+        >
+          <div>
+            <v-icon left>mdi-card-text-outline</v-icon>
+          </div>
+          <div style="white-space: pre-line">
+            {{ selectedEvent.description }}
+          </div>
+        </v-card-text>
+      </slot>
+    </v-card>
+  </v-menu>
+</template>
+
+<script>
+import calendarFeedDetailsMixin from "../../mixins/calendarFeedDetails.js";
+
+export default {
+  name: "BaseCalendarFeedDetails",
+  mixins: [calendarFeedDetailsMixin],
+};
+</script>
diff --git a/aleksis/core/frontend/components/calendar/BaseCalendarFeedEventBar.vue b/aleksis/core/frontend/components/calendar/BaseCalendarFeedEventBar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..510e2af17160f6d2cbcacbf3d640cb18d1f9df5c
--- /dev/null
+++ b/aleksis/core/frontend/components/calendar/BaseCalendarFeedEventBar.vue
@@ -0,0 +1,36 @@
+<template>
+  <div
+    class="mx-1 text-truncate"
+    :style="
+      event.status === 'CANCELLED' ? 'text-decoration: line-through;' : ''
+    "
+  >
+    <slot name="time" v-bind="$props">
+      <span
+        v-if="
+          calendarType === 'month' && eventParsed.start.hasTime && !withoutTime
+        "
+        class="mr-1 font-weight-bold"
+        >{{ eventParsed.start.time }}</span
+      >
+    </slot>
+    <slot name="icon" v-bind="$props">
+      <v-icon v-if="icon" x-small color="white" class="mx-1 left">
+        {{ icon }}
+      </v-icon>
+    </slot>
+
+    <slot name="title" v-bind="$props">
+      {{ event.name }}
+    </slot>
+  </div>
+</template>
+
+<script>
+import calendarFeedEventBarMixin from "../../mixins/calendarFeedEventBar.js";
+
+export default {
+  name: "BaseCalendarFeedEventBar",
+  mixins: [calendarFeedEventBarMixin],
+};
+</script>
diff --git a/aleksis/core/frontend/components/calendar/CalendarOverview.vue b/aleksis/core/frontend/components/calendar/CalendarOverview.vue
index 9400c73349cc6c890960bac8120bf375584fc805..575475db559c39a24d8ffebf645b17d666594e91 100644
--- a/aleksis/core/frontend/components/calendar/CalendarOverview.vue
+++ b/aleksis/core/frontend/components/calendar/CalendarOverview.vue
@@ -107,16 +107,16 @@
             >
               <template #event="{ event, eventParsed, timed }">
                 <component
-                  :is="eventBarComponentForFeed(event.category)"
+                  :is="eventBarComponentForFeed(event.calendarFeedName)"
                   :event="event"
                   :event-parsed="eventParsed"
-                  :calendarType="currentCalendarType"
+                  :calendar-type="currentCalendarType"
                 />
               </template>
             </v-calendar>
             <component
               v-if="calendarFeeds && selectedEvent"
-              :is="detailComponentForFeed(selectedEvent.category)"
+              :is="detailComponentForFeed(selectedEvent.calendarFeedName)"
               v-model="selectedOpen"
               :selected-element="selectedElement"
               :selected-event="selectedEvent"
@@ -190,6 +190,7 @@ export default {
           cf.feed.events.map((event) => ({
             ...event,
             category: cf.verboseName,
+            calendarFeedName: cf.name,
             start: new Date(event.start),
             end: new Date(event.end),
             color: event.color ? event.color : cf.color,
@@ -228,9 +229,9 @@ export default {
       if (
         this.calendarFeeds &&
         feedName &&
-        Object.keys(calendarFeedDetailComponents).includes(feedName + "Details")
+        Object.keys(calendarFeedDetailComponents).includes(feedName + "details")
       ) {
-        return calendarFeedDetailComponents[feedName + "Details"];
+        return calendarFeedDetailComponents[feedName + "details"];
       }
       return GenericCalendarFeedDetails;
     },
@@ -239,10 +240,10 @@ export default {
         this.calendarFeeds &&
         feedName &&
         Object.keys(calendarFeedEventBarComponents).includes(
-          feedName + "EventBar"
+          feedName + "eventbar"
         )
       ) {
-        return calendarFeedEventBarComponents[feedName + "EventBar"];
+        return calendarFeedEventBarComponents[feedName + "eventbar"];
       }
       return GenericCalendarFeedEventBar;
     },
diff --git a/aleksis/core/frontend/components/calendar/GenericCalendarFeedDetails.vue b/aleksis/core/frontend/components/calendar/GenericCalendarFeedDetails.vue
index 048561859506f6330b5d64346effa9a8fda6a071..4c890629fb1662ccfa21102194d6dbbf4f2c40df 100644
--- a/aleksis/core/frontend/components/calendar/GenericCalendarFeedDetails.vue
+++ b/aleksis/core/frontend/components/calendar/GenericCalendarFeedDetails.vue
@@ -1,47 +1,14 @@
 <template>
-  <v-menu
-    v-model="model"
-    :close-on-content-click="false"
-    :activator="selectedElement"
-    offset-x
-  >
-    <v-card min-width="350px" flat>
-      <v-toolbar :color="selectedEvent.color" dark dense>
-        <v-toolbar-title>{{ selectedEvent.name }}</v-toolbar-title>
-        <v-spacer></v-spacer>
-        <v-chip v-if="selectedEvent.status === 'CANCELLED'" color="error" label>
-          <v-avatar left>
-            <v-icon>mdi-cancel</v-icon>
-          </v-avatar>
-          {{ $t("calendar.cancelled") }}
-        </v-chip>
-      </v-toolbar>
-      <v-card-text>
-        <v-icon left>mdi-calendar-today-outline</v-icon>
-        <span v-if="selectedEvent.start !== selectedEvent.end">
-          {{ $d(selectedEvent.start, "shortDateTime") }} –
-          {{ $d(selectedEvent.end, "shortDateTime") }}
-        </span>
-        <span v-else> {{ $d(selectedEvent.start, "shortDateTime") }}</span>
-      </v-card-text>
-      <v-divider v-if="selectedEvent.description" />
-      <v-card-text class="d-flex" v-if="selectedEvent.description">
-        <div>
-          <v-icon left>mdi-card-text-outline</v-icon>
-        </div>
-        <div style="white-space: pre-line">
-          {{ selectedEvent.description }}
-        </div>
-      </v-card-text>
-    </v-card>
-  </v-menu>
+  <base-calendar-feed-details v-bind="$props" />
 </template>
 
 <script>
 import calendarFeedDetailsMixin from "../../mixins/calendarFeedDetails.js";
+import BaseCalendarFeedDetails from "./BaseCalendarFeedDetails.vue";
 
 export default {
   name: "GenericCalendarFeedDetails",
+  components: { BaseCalendarFeedDetails },
   mixins: [calendarFeedDetailsMixin],
 };
 </script>
diff --git a/aleksis/core/frontend/components/calendar/GenericCalendarFeedEventBar.vue b/aleksis/core/frontend/components/calendar/GenericCalendarFeedEventBar.vue
index 1ada87ae0221b3308f2df1a59ca21f31120235f6..251b7a8b929f3b67ada0e4a207aefef16d500149 100644
--- a/aleksis/core/frontend/components/calendar/GenericCalendarFeedEventBar.vue
+++ b/aleksis/core/frontend/components/calendar/GenericCalendarFeedEventBar.vue
@@ -1,24 +1,14 @@
 <template>
-  <div
-    class="mx-1 truncate"
-    :style="
-      event.status === 'CANCELLED' ? 'text-decoration: line-through;' : ''
-    "
-  >
-    <span
-      v-if="calendarType === 'month' && eventParsed.start.hasTime"
-      class="mr-1 font-weight-bold"
-      >{{ eventParsed.start.time }}</span
-    >
-    {{ event.name }}
-  </div>
+  <base-calendar-feed-event-bar v-bind="$props" />
 </template>
 
 <script>
 import calendarFeedEventBarMixin from "../../mixins/calendarFeedEventBar.js";
+import BaseCalendarFeedEventBar from "./BaseCalendarFeedEventBar.vue";
 
 export default {
   name: "GenericCalendarFeedEventBar",
+  components: { BaseCalendarFeedEventBar },
   mixins: [calendarFeedEventBarMixin],
 };
 </script>
diff --git a/aleksis/core/frontend/components/calendar_feeds/details/BirthdaysDetails.vue b/aleksis/core/frontend/components/calendar_feeds/details/BirthdaysDetails.vue
index c87cdf957558d84be79d56a2d18749ff158e0982..f651ff2d11fa189b057b1adc6a613b288ae84604 100644
--- a/aleksis/core/frontend/components/calendar_feeds/details/BirthdaysDetails.vue
+++ b/aleksis/core/frontend/components/calendar_feeds/details/BirthdaysDetails.vue
@@ -1,33 +1,24 @@
 <template>
-  <v-menu
-    v-model="model"
-    :close-on-content-click="false"
-    :activator="selectedElement"
-    offset-x
-  >
-    <v-card color="grey lighten-4" min-width="350px" flat>
-      <v-toolbar :color="selectedEvent.color" dark>
-        <v-toolbar-title>{{ selectedEvent.name }}</v-toolbar-title>
-        <v-spacer></v-spacer>
-        <v-btn icon @click="model = false">
-          <v-icon>mdi-close</v-icon>
-        </v-btn>
-      </v-toolbar>
+  <base-calendar-feed-details v-bind="$props" without-time>
+    <template #description="{ selectedEvent }">
+      <v-divider />
       <v-card-text>
         <span>
           <v-icon class="mr-2">mdi-cake-variant-outline</v-icon>
           {{ $d(selectedEvent.start) }}
         </span>
       </v-card-text>
-    </v-card>
-  </v-menu>
+    </template>
+  </base-calendar-feed-details>
 </template>
 
 <script>
 import calendarFeedDetailsMixin from "../../../mixins/calendarFeedDetails.js";
+import BaseCalendarFeedDetails from "../../calendar/BaseCalendarFeedDetails.vue";
 
 export default {
   name: "BirthdaysDetails",
+  components: { BaseCalendarFeedDetails },
   mixins: [calendarFeedDetailsMixin],
 };
 </script>
diff --git a/aleksis/core/frontend/components/calendar_feeds/event_bar/BirthdaysEventBar.vue b/aleksis/core/frontend/components/calendar_feeds/event_bar/BirthdaysEventBar.vue
index 4f6b466fa4bd36e0afabcd0a828eb7e43c14a38b..d0f89cfae0695cba0033722dda8d66d5686205a6 100644
--- a/aleksis/core/frontend/components/calendar_feeds/event_bar/BirthdaysEventBar.vue
+++ b/aleksis/core/frontend/components/calendar_feeds/event_bar/BirthdaysEventBar.vue
@@ -1,21 +1,17 @@
 <template>
-  <v-row align="center">
-    <v-col cols="2">
-      <v-icon x-small color="white" class="ml-2"
-        >mdi-cake-variant-outline</v-icon
-      >
-    </v-col>
-    <v-col cols="10" class="text-truncate">
-      {{ event.name }}
-    </v-col>
-  </v-row>
+  <base-calendar-feed-event-bar
+    v-bind="$props"
+    icon="mdi-cake-variant-outline"
+  />
 </template>
 
 <script>
 import calendarFeedEventBarMixin from "../../../mixins/calendarFeedEventBar.js";
+import BaseCalendarFeedEventBar from "../../calendar/BaseCalendarFeedEventBar.vue";
 
 export default {
   name: "BirthdaysEventBar",
+  components: { BaseCalendarFeedEventBar },
   mixins: [calendarFeedEventBarMixin],
 };
 </script>
diff --git a/aleksis/core/frontend/mixins/calendarFeedDetails.js b/aleksis/core/frontend/mixins/calendarFeedDetails.js
index a6881a9abfd8bc7412eb722c124e8e6eff707105..ef874b5c5600d487472ca73b957b4ed3fad801bc 100644
--- a/aleksis/core/frontend/mixins/calendarFeedDetails.js
+++ b/aleksis/core/frontend/mixins/calendarFeedDetails.js
@@ -12,6 +12,21 @@ const calendarFeedDetailsMixin = {
       type: Object,
     },
     value: { type: Boolean, required: true },
+    withoutTime: {
+      required: true,
+      type: Boolean,
+      default: false,
+    },
+    withoutDescription: {
+      required: true,
+      type: Boolean,
+      default: false,
+    },
+    withoutBadge: {
+      required: true,
+      type: Boolean,
+      default: false,
+    },
   },
   computed: {
     model: {
diff --git a/aleksis/core/frontend/mixins/calendarFeedEventBar.js b/aleksis/core/frontend/mixins/calendarFeedEventBar.js
index 3b6590b0f68e75090b200bd0aa0fcc747cfdef51..5a0367c2b7590dce12f6eb5d85097e23bd1b1ced 100644
--- a/aleksis/core/frontend/mixins/calendarFeedEventBar.js
+++ b/aleksis/core/frontend/mixins/calendarFeedEventBar.js
@@ -15,6 +15,16 @@ const calendarFeedEventBarMixin = {
       required: true,
       type: String,
     },
+    icon: {
+      required: false,
+      type: String,
+      default: "",
+    },
+    withoutTime: {
+      required: true,
+      type: Boolean,
+      default: false,
+    },
   },
 };
 
diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js
index c8b575edaa6f688818c5b03bddeabd10c54470f8..f1402e7702877f33967158820fda5ecf4f4141db 100644
--- a/aleksis/core/vite.config.js
+++ b/aleksis/core/vite.config.js
@@ -76,7 +76,7 @@ function generateComponentsImportCode(
     for (file of files) {
       let componentName = file.split(".")[0];
       code += `import ${componentName} from '${componentsPath + file}';\n`;
-      code += `${exportName}["${componentName}"] = ${componentName};\n`;
+      code += `${exportName}["${componentName.toLowerCase()}"] = ${componentName};\n`;
     }
   }
   return code;