diff --git a/js/src/components/Utils/Breadcrumbs.vue b/js/src/components/Utils/Breadcrumbs.vue
new file mode 100644
index 000000000..a9f775a25
--- /dev/null
+++ b/js/src/components/Utils/Breadcrumbs.vue
@@ -0,0 +1,69 @@
+<template>
+  <nav class="flex mb-3" :aria-label="$t('Breadcrumbs')">
+    <ol class="inline-flex items-center space-x-1 md:space-x-3">
+      <li
+        class="inline-flex items-center"
+        v-for="(element, index) in links"
+        :key="index"
+        :aria-current="index > 0 ? 'page' : undefined"
+      >
+        <router-link
+          v-if="index === 0"
+          :to="element"
+          class="inline-flex items-center text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
+        >
+          {{ element.text }}
+        </router-link>
+        <div class="flex items-center" v-else-if="index === links.length - 1">
+          <svg
+            class="w-6 h-6 text-gray-400 rtl:rotate-180"
+            fill="currentColor"
+            viewBox="0 0 20 20"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              fill-rule="evenodd"
+              d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
+              clip-rule="evenodd"
+            ></path>
+          </svg>
+          <span
+            class="ltr:ml-1 rtl:mr-1 font-medium text-gray-400 md:ltr:ml-2 md:rtl:mr-2 dark:text-gray-500"
+            >{{ element.text }}</span
+          >
+        </div>
+        <div class="flex items-center" v-else>
+          <svg
+            class="w-6 h-6 text-gray-400 rtl:rotate-180"
+            fill="currentColor"
+            viewBox="0 0 20 20"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              fill-rule="evenodd"
+              d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
+              clip-rule="evenodd"
+            ></path>
+          </svg>
+          <router-link
+            :to="element"
+            class="ltr:ml-1 rtl:mr-1 font-medium text-gray-700 hover:text-gray-900 md:ltr:ml-2 md:rtl:mr-2 dark:text-gray-400 dark:hover:text-white"
+            >{{ element.text }}</router-link
+          >
+        </div>
+      </li>
+      <slot></slot>
+    </ol>
+  </nav>
+</template>
+<script lang="ts">
+import { Component, Prop, Vue } from "vue-property-decorator";
+import { Location } from "vue-router";
+
+type LinkElement = Location & { text: string };
+
+@Component
+export default class Breadcrumbs extends Vue {
+  @Prop({ type: Array, required: true }) links!: LinkElement[];
+}
+</script>
diff --git a/js/src/main.ts b/js/src/main.ts
index 317f2b37b..0e6695195 100644
--- a/js/src/main.ts
+++ b/js/src/main.ts
@@ -12,6 +12,7 @@ import { NotifierPlugin } from "./plugins/notifier";
 import filters from "./filters";
 import { i18n } from "./utils/i18n";
 import apolloProvider from "./vue-apollo";
+import Breadcrumbs from "@/components/Utils/Breadcrumbs.vue";
 import "./registerServiceWorker";
 import "./assets/tailwind.css";
 
@@ -25,6 +26,7 @@ Vue.use(VueScrollTo);
 Vue.use(VTooltip);
 Vue.use(VueAnnouncer);
 Vue.use(VueSkipTo);
+Vue.component("breadcrumbs-nav", Breadcrumbs);
 
 // Register the router hooks with their names
 Component.registerHooks([
diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue
index 0bba7acde..d0e56dd43 100644
--- a/js/src/views/Account/children/EditIdentity.vue
+++ b/js/src/views/Account/children/EditIdentity.vue
@@ -1,28 +1,6 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.IDENTITIES }">{{
-            $t("Profiles")
-          }}</router-link>
-        </li>
-        <li class="is-active" v-if="isUpdate && identity">
-          <router-link
-            :to="{
-              name: RouteName.UPDATE_IDENTITY,
-              params: { identityName: identity.preferredUsername },
-            }"
-            >{{ identity.name }}</router-link
-          >
-        </li>
-        <li class="is-active" v-else>
-          <router-link :to="{ name: RouteName.CREATE_IDENTITY }">{{
-            $t("New profile")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav :links="breadcrumbsLinks" />
     <div class="root" v-if="identity">
       <h1 class="title">
         <span v-if="isUpdate">{{ identity.displayName() }}</span>
@@ -253,6 +231,7 @@ import { ServerParseError } from "@apollo/client/link/http";
 import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
 import pick from "lodash/pick";
 import { ActorType } from "@/types/enums";
+import { Location } from "vue-router";
 
 @Component({
   components: {
@@ -670,5 +649,29 @@ export default class EditIdentity extends mixins(identityEditionMixin) {
     this.oldDisplayName = null;
     this.avatarFile = null;
   }
+
+  get breadcrumbsLinks(): (Location & { text: string })[] {
+    const links = [
+      {
+        name: RouteName.IDENTITIES,
+        params: {},
+        text: this.$t("Profiles") as string,
+      },
+    ];
+    if (this.isUpdate && this.identity) {
+      links.push({
+        name: RouteName.UPDATE_IDENTITY,
+        params: { identityName: this.identity.preferredUsername },
+        text: this.identity.name,
+      });
+    } else {
+      links.push({
+        name: RouteName.CREATE_IDENTITY,
+        params: {},
+        text: this.$t("New profile") as string,
+      });
+    }
+    return links;
+  }
 }
 </script>
diff --git a/js/src/views/Admin/AdminGroupProfile.vue b/js/src/views/Admin/AdminGroupProfile.vue
index 43e85e879..a330a69f8 100644
--- a/js/src/views/Admin/AdminGroupProfile.vue
+++ b/js/src/views/Admin/AdminGroupProfile.vue
@@ -1,31 +1,19 @@
 <template>
   <div v-if="group" class="section">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.ADMIN_GROUPS,
-            }"
-            >{{ $t("Groups") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.PROFILES,
-              params: { id: group.id },
-            }"
-            >{{ group.name || usernameWithDomain(group) }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        {
+          name: RouteName.ADMIN_GROUPS,
+          text: $t('Groups'),
+        },
+        {
+          name: RouteName.PROFILES,
+          params: { id: group.id },
+          text: displayName(group),
+        },
+      ]"
+    />
     <div class="actor-card">
       <p v-if="group.suspended">
         <actor-card
@@ -305,7 +293,11 @@ import { formatBytes } from "@/utils/datetime";
 import { MemberRole } from "@/types/enums";
 import { SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor";
 import { IGroup } from "../../types/actor";
-import { usernameWithDomain, IActor } from "../../types/actor/actor.model";
+import {
+  usernameWithDomain,
+  displayName,
+  IActor,
+} from "../../types/actor/actor.model";
 import RouteName from "../../router/name";
 import ActorCard from "../../components/Account/ActorCard.vue";
 import EmptyContent from "../../components/Utils/EmptyContent.vue";
@@ -359,6 +351,8 @@ export default class AdminGroupProfile extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   RouteName = RouteName;
 
   EVENTS_PER_PAGE = EVENTS_PER_PAGE;
diff --git a/js/src/views/Admin/AdminProfile.vue b/js/src/views/Admin/AdminProfile.vue
index 32bb836e6..8677847ea 100644
--- a/js/src/views/Admin/AdminProfile.vue
+++ b/js/src/views/Admin/AdminProfile.vue
@@ -1,31 +1,20 @@
 <template>
   <div v-if="person" class="section">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.PROFILES,
-            }"
-            >{{ $t("Profiles") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.PROFILES,
-              params: { id: person.id },
-            }"
-            >{{ person.name || person.preferredUsername }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        {
+          name: RouteName.PROFILES,
+          text: $t('Profiles'),
+        },
+        {
+          name: RouteName.PROFILES,
+          params: { id: person.id },
+          text: displayName(person),
+        },
+      ]"
+    />
+
     <div class="actor-card">
       <actor-card
         :actor="person"
@@ -279,7 +268,7 @@ import {
   UNSUSPEND_PROFILE,
 } from "../../graphql/actor";
 import { IPerson } from "../../types/actor";
-import { usernameWithDomain } from "../../types/actor/actor.model";
+import { displayName, usernameWithDomain } from "../../types/actor/actor.model";
 import RouteName from "../../router/name";
 import ActorCard from "../../components/Account/ActorCard.vue";
 import EmptyContent from "../../components/Utils/EmptyContent.vue";
@@ -334,6 +323,8 @@ export default class AdminProfile extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   RouteName = RouteName;
 
   EVENTS_PER_PAGE = EVENTS_PER_PAGE;
diff --git a/js/src/views/Admin/AdminUserProfile.vue b/js/src/views/Admin/AdminUserProfile.vue
index c4fcf3e9f..6905783f3 100644
--- a/js/src/views/Admin/AdminUserProfile.vue
+++ b/js/src/views/Admin/AdminUserProfile.vue
@@ -1,31 +1,20 @@
 <template>
   <div v-if="user" class="section">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.USERS,
-            }"
-            >{{ $t("Users") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.ADMIN_USER_PROFILE,
-              params: { id: user.id },
-            }"
-            >{{ user.email }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        {
+          name: RouteName.USERS,
+          text: $t('Users'),
+        },
+        {
+          name: RouteName.ADMIN_USER_PROFILE,
+          params: { id: user.id },
+          text: user.email,
+        },
+      ]"
+    />
+
     <table v-if="metadata.length > 0" class="table is-fullwidth">
       <tbody>
         <tr v-for="{ key, value, link, elements, type } in metadata" :key="key">
diff --git a/js/src/views/Admin/Dashboard.vue b/js/src/views/Admin/Dashboard.vue
index d2894f0c1..75adc8e9f 100644
--- a/js/src/views/Admin/Dashboard.vue
+++ b/js/src/views/Admin/Dashboard.vue
@@ -1,19 +1,11 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.ADMIN_DASHBOARD }">{{
-            $t("Dashboard")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        { text: $t('Dashboard') },
+      ]"
+    />
     <section>
       <h1 class="title">{{ $t("Administration") }}</h1>
       <div class="tile is-ancestor" v-if="dashboard">
diff --git a/js/src/views/Admin/GroupProfiles.vue b/js/src/views/Admin/GroupProfiles.vue
index f6a8011c2..6b4c95173 100644
--- a/js/src/views/Admin/GroupProfiles.vue
+++ b/js/src/views/Admin/GroupProfiles.vue
@@ -1,19 +1,14 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.PROFILES }">{{
-            $t("Groups")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.MODERATION, text: $t('Moderation') },
+        {
+          name: RouteName.ADMIN_GROUPS,
+          text: $t('Groups'),
+        },
+      ]"
+    />
     <div class="buttons" v-if="showCreateGroupsButton">
       <router-link
         class="button is-primary"
diff --git a/js/src/views/Admin/Instance.vue b/js/src/views/Admin/Instance.vue
index bfb4bd18c..399b8a558 100644
--- a/js/src/views/Admin/Instance.vue
+++ b/js/src/views/Admin/Instance.vue
@@ -1,28 +1,12 @@
 <template>
   <div v-if="instance">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link :to="{ name: RouteName.INSTANCES }">{{
-            $t("Instances")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.INSTANCE,
-              params: { domain: instance.domain },
-            }"
-            >{{ instance.domain }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        { name: RouteName.INSTANCES, text: $t('Instances') },
+        { text: instance.domain },
+      ]"
+    />
     <h1 class="text-2xl">{{ instance.domain }}</h1>
     <div class="grid md:grid-cols-4 gap-2 content-center text-center mt-2">
       <div class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800">
diff --git a/js/src/views/Admin/Instances.vue b/js/src/views/Admin/Instances.vue
index e2c64940f..5856f5ef3 100644
--- a/js/src/views/Admin/Instances.vue
+++ b/js/src/views/Admin/Instances.vue
@@ -1,28 +1,16 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.INSTANCES }">{{
-            $t("Instances")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        { text: $t('Instances') },
+      ]"
+    />
     <section>
       <h1 class="title">{{ $t("Instances") }}</h1>
       <form @submit="followInstance" class="my-4">
-        <b-field
-          :label="$t('Follow a new instance')"
-          custom-class="add-relay"
-          horizontal
-        >
-          <b-field grouped expanded size="is-large">
+        <b-field :label="$t('Follow a new instance')" horizontal>
+          <b-field grouped group-multiline expanded size="is-large">
             <p class="control">
               <b-input
                 v-model="newRelayAddress"
diff --git a/js/src/views/Admin/Profiles.vue b/js/src/views/Admin/Profiles.vue
index 1a61483fb..cfd4f8d49 100644
--- a/js/src/views/Admin/Profiles.vue
+++ b/js/src/views/Admin/Profiles.vue
@@ -1,19 +1,14 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.PROFILES }">{{
-            $t("Profiles")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.MODERATION, text: $t('Moderation') },
+        {
+          name: RouteName.PROFILES,
+          text: $t('Profiles'),
+        },
+      ]"
+    />
     <div v-if="persons">
       <b-switch v-model="local">{{ $t("Local") }}</b-switch>
       <b-switch v-model="suspended">{{ $t("Suspended") }}</b-switch>
diff --git a/js/src/views/Admin/Settings.vue b/js/src/views/Admin/Settings.vue
index 9a8e8c90c..137e7907a 100644
--- a/js/src/views/Admin/Settings.vue
+++ b/js/src/views/Admin/Settings.vue
@@ -1,19 +1,12 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ADMIN }">{{
-            $t("Admin")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.ADMIN_SETTINGS }">{{
-            $t("Instance settings")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.ADMIN, text: $t('Admin') },
+        { text: $t('Instance settings') },
+      ]"
+    />
+
     <section v-if="settingsToWrite">
       <form @submit.prevent="updateSettings">
         <b-field :label="$t('Instance Name')" label-for="instance-name">
diff --git a/js/src/views/Admin/Users.vue b/js/src/views/Admin/Users.vue
index 4f7a7ca3b..c018f3366 100644
--- a/js/src/views/Admin/Users.vue
+++ b/js/src/views/Admin/Users.vue
@@ -1,19 +1,14 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.USERS }">{{
-            $t("Users")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.MODERATION, text: $t('Moderation') },
+        {
+          name: RouteName.USERS,
+          text: $t('Users'),
+        },
+      ]"
+    />
     <div v-if="users">
       <b-table
         :data="users.elements"
diff --git a/js/src/views/Discussions/Create.vue b/js/src/views/Discussions/Create.vue
index bbcbf560a..2659ba27d 100644
--- a/js/src/views/Discussions/Create.vue
+++ b/js/src/views/Discussions/Create.vue
@@ -1,42 +1,29 @@
 <template>
   <section class="section container">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul v-if="group">
-        <li>
-          <router-link :to="{ name: RouteName.MY_GROUPS }">{{
-            $t("My groups")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.DISCUSSION_LIST,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Discussions") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.CREATE_DISCUSSION,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Create") }}</router-link
-          >
-        </li>
-      </ul>
-      <b-skeleton v-else-if="$apollo.loading" :animated="animated"></b-skeleton>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.MY_GROUPS,
+          text: $t('My groups'),
+        },
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.DISCUSSION_LIST,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Discussions'),
+        },
+        {
+          name: RouteName.CREATE_DISCUSSION,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Create'),
+        },
+      ]"
+    />
     <h1 class="title">{{ $t("Create a discussion") }}</h1>
 
     <form @submit.prevent="createDiscussion">
@@ -67,7 +54,12 @@
 
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
-import { IGroup, IPerson, usernameWithDomain } from "@/types/actor";
+import {
+  displayName,
+  IGroup,
+  IPerson,
+  usernameWithDomain,
+} from "@/types/actor";
 import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
 import { FETCH_GROUP } from "@/graphql/group";
 import { CREATE_DISCUSSION } from "@/graphql/discussion";
@@ -113,6 +105,8 @@ export default class CreateDiscussion extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   async createDiscussion(): Promise<void> {
     this.errors = { title: "" };
     try {
diff --git a/js/src/views/Discussions/Discussion.vue b/js/src/views/Discussions/Discussion.vue
index b61457dc7..b693b903c 100644
--- a/js/src/views/Discussions/Discussion.vue
+++ b/js/src/views/Discussions/Discussion.vue
@@ -1,46 +1,29 @@
 <template>
   <div class="container section" v-if="discussion">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MY_GROUPS }">{{
-            $t("My groups")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            v-if="discussion.actor"
-            :to="{
-              name: RouteName.GROUP,
-              params: {
-                preferredUsername: usernameWithDomain(discussion.actor),
-              },
-            }"
-            >{{ discussion.actor.name }}</router-link
-          >
-          <b-skeleton v-else-if="$apollo.loading" animated />
-        </li>
-        <li>
-          <router-link
-            v-if="discussion.actor"
-            :to="{
-              name: RouteName.DISCUSSION_LIST,
-              params: {
-                preferredUsername: usernameWithDomain(discussion.actor),
-              },
-            }"
-            >{{ $t("Discussions") }}</router-link
-          >
-          <b-skeleton animated v-else-if="$apollo.loading" />
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{ name: RouteName.DISCUSSION, params: { id: discussion.id } }"
-            >{{ discussion.title }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.MY_GROUPS,
+          text: $t('My groups'),
+        },
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.DISCUSSION_LIST,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Discussions'),
+        },
+        {
+          name: RouteName.DISCUSSION,
+          params: { id: discussion.id },
+          text: discussion.title,
+        },
+      ]"
+    />
     <b-message v-if="error" type="is-danger">
       {{ error }}
     </b-message>
@@ -148,7 +131,7 @@ import {
 } from "@/graphql/discussion";
 import { IDiscussion } from "@/types/discussions";
 import { Discussion as DiscussionModel } from "@/types/discussions";
-import { usernameWithDomain } from "@/types/actor";
+import { displayName, usernameWithDomain } from "@/types/actor";
 import DiscussionComment from "@/components/Discussion/DiscussionComment.vue";
 import { GraphQLError } from "graphql";
 import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment";
@@ -250,6 +233,7 @@ export default class Discussion extends mixins(GroupMixin) {
   RouteName = RouteName;
 
   usernameWithDomain = usernameWithDomain;
+  displayName = displayName;
   error: string | null = null;
 
   async reply(): Promise<void> {
diff --git a/js/src/views/Discussions/DiscussionsList.vue b/js/src/views/Discussions/DiscussionsList.vue
index 63e016138..c9e63677d 100644
--- a/js/src/views/Discussions/DiscussionsList.vue
+++ b/js/src/views/Discussions/DiscussionsList.vue
@@ -1,32 +1,23 @@
 <template>
   <div class="container section" v-if="group">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MY_GROUPS }">{{
-            $t("My groups")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.DISCUSSION_LIST,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Discussions") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.MY_GROUPS,
+          text: $t('My groups'),
+        },
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.DISCUSSION_LIST,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Discussions'),
+        },
+      ]"
+    />
     <section v-if="isCurrentActorAGroupMember">
       <p>
         {{
@@ -82,7 +73,13 @@
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
 import { FETCH_GROUP } from "@/graphql/group";
-import { IActor, IGroup, IPerson, usernameWithDomain } from "@/types/actor";
+import {
+  displayName,
+  IActor,
+  IGroup,
+  IPerson,
+  usernameWithDomain,
+} from "@/types/actor";
 import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue";
 import RouteName from "../../router/name";
 import { MemberRole } from "@/types/enums";
@@ -166,6 +163,7 @@ export default class DiscussionsList extends Vue {
   RouteName = RouteName;
 
   usernameWithDomain = usernameWithDomain;
+  displayName = displayName;
 
   DISCUSSIONS_PER_PAGE = DISCUSSIONS_PER_PAGE;
 
diff --git a/js/src/views/Event/GroupEvents.vue b/js/src/views/Event/GroupEvents.vue
index 5c3567bed..642892a9b 100644
--- a/js/src/views/Event/GroupEvents.vue
+++ b/js/src/views/Event/GroupEvents.vue
@@ -1,27 +1,19 @@
 <template>
   <div class="container section" v-if="group">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.TODO_LISTS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Events") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.EVENTS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Events'),
+        },
+      ]"
+    />
     <section>
       <h1 class="title" v-if="group">
         {{
@@ -89,7 +81,7 @@ import { PERSON_MEMBERSHIPS } from "@/graphql/actor";
 import GroupMixin from "@/mixins/group";
 import { IMember } from "@/types/actor/member.model";
 import { FETCH_GROUP_EVENTS } from "@/graphql/event";
-import { usernameWithDomain } from "../../types/actor";
+import { displayName, usernameWithDomain } from "../../types/actor";
 
 const EVENTS_PAGE_LIMIT = 10;
 
@@ -143,6 +135,8 @@ export default class GroupEvents extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   RouteName = RouteName;
 
   EVENTS_PAGE_LIMIT = EVENTS_PAGE_LIMIT;
diff --git a/js/src/views/Event/Participants.vue b/js/src/views/Event/Participants.vue
index 247f7fa71..b4ab755f6 100644
--- a/js/src/views/Event/Participants.vue
+++ b/js/src/views/Event/Participants.vue
@@ -1,32 +1,20 @@
 <template>
   <section class="section container" v-if="event">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MY_EVENTS }">{{
-            $t("My events")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.EVENT,
-              params: { uuid: event.uuid },
-            }"
-            >{{ event.title }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.PARTICIPANTS,
-              params: { uuid: event.uuid },
-            }"
-            >{{ $t("Participants") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        { name: RouteName.MY_EVENTS, text: $t('My events') },
+        {
+          name: RouteName.EVENT,
+          params: { uuid: event.uuid },
+          text: event.title,
+        },
+        {
+          name: RouteName.PARTICIPANTS,
+          params: { uuid: event.uuid },
+          text: $t('Participants'),
+        },
+      ]"
+    />
     <h1 class="title">{{ $t("Participants") }}</h1>
     <div class="level">
       <div class="level-left">
diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue
index ec29e8af1..2ed8013ac 100644
--- a/js/src/views/Group/Group.vue
+++ b/js/src/views/Group/Group.vue
@@ -1,27 +1,17 @@
 <template>
   <div class="container is-widescreen">
     <div class="header">
-      <nav class="breadcrumb" :aria-label="$t('Breadcrumbs')">
-        <ul>
-          <li>
-            <router-link :to="{ name: RouteName.MY_GROUPS }">{{
-              $t("My groups")
-            }}</router-link>
-          </li>
-          <li class="is-active">
-            <router-link
-              aria-current-value="location"
-              v-if="group && group.preferredUsername"
-              :to="{
-                name: RouteName.GROUP,
-                params: { preferredUsername: usernameWithDomain(group) },
-              }"
-              >{{ group.name }}</router-link
-            >
-            <b-skeleton v-else :animated="true"></b-skeleton>
-          </li>
-        </ul>
-      </nav>
+      <breadcrumbs-nav
+        v-if="group"
+        :links="[
+          { name: RouteName.MY_GROUPS, text: $t('My groups') },
+          {
+            name: RouteName.GROUP,
+            params: { preferredUsername: usernameWithDomain(group) },
+            text: displayName(group),
+          },
+        ]"
+      />
       <b-loading :active.sync="$apollo.loading"></b-loading>
       <header class="block-container presentation" v-if="group">
         <div class="banner-container">
@@ -776,6 +766,8 @@ export default class Group extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   PostVisibility = PostVisibility;
 
   Openness = Openness;
diff --git a/js/src/views/Group/GroupFollowers.vue b/js/src/views/Group/GroupFollowers.vue
index 8887b8a29..18c004cd3 100644
--- a/js/src/views/Group/GroupFollowers.vue
+++ b/js/src/views/Group/GroupFollowers.vue
@@ -1,36 +1,25 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul v-if="group">
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Settings") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.GROUP_FOLLOWERS_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Followers") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.GROUP_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Settings'),
+        },
+        {
+          name: RouteName.GROUP_FOLLOWERS_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Followers'),
+        },
+      ]"
+    />
     <b-loading :active="$apollo.loading" />
     <section
       class="container section"
@@ -138,7 +127,7 @@ import GroupMixin from "@/mixins/group";
 import { mixins } from "vue-class-component";
 import { GROUP_FOLLOWERS, UPDATE_FOLLOWER } from "@/graphql/followers";
 import RouteName from "../../router/name";
-import { usernameWithDomain } from "../../types/actor";
+import { displayName, usernameWithDomain } from "../../types/actor";
 import EmptyContent from "@/components/Utils/EmptyContent.vue";
 import { IFollower } from "@/types/actor/follower.model";
 import { Paginate } from "@/types/paginate";
@@ -181,6 +170,8 @@ export default class GroupFollowers extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   followers!: Paginate<IFollower>;
 
   mounted(): void {
diff --git a/js/src/views/Group/GroupMembers.vue b/js/src/views/Group/GroupMembers.vue
index 3904299bf..a72185f26 100644
--- a/js/src/views/Group/GroupMembers.vue
+++ b/js/src/views/Group/GroupMembers.vue
@@ -1,36 +1,25 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul v-if="group">
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Settings") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.GROUP_MEMBERS_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Members") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.GROUP_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Settings'),
+        },
+        {
+          name: RouteName.GROUP_MEMBERS_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Members'),
+        },
+      ]"
+    />
     <b-loading :active="$apollo.loading" />
     <section
       class="container section"
@@ -312,6 +301,8 @@ export default class GroupMembers extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   mounted(): void {
     const roleQuery = this.$route.query.role as string;
     if (Object.values(MemberRole).includes(roleQuery as MemberRole)) {
diff --git a/js/src/views/Group/GroupSettings.vue b/js/src/views/Group/GroupSettings.vue
index 894c38402..da70ab3ef 100644
--- a/js/src/views/Group/GroupSettings.vue
+++ b/js/src/views/Group/GroupSettings.vue
@@ -1,37 +1,25 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            v-if="group"
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name || usernameWithDomain(group) }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Settings") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.GROUP_PUBLIC_SETTINGS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Group settings") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.GROUP_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Settings'),
+        },
+        {
+          name: RouteName.GROUP_PUBLIC_SETTINGS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Group settings'),
+        },
+      ]"
+    />
     <b-loading :active="$apollo.loading" />
     <section
       class="container section"
@@ -197,7 +185,12 @@ import { mixins } from "vue-class-component";
 import GroupMixin from "@/mixins/group";
 import { GroupVisibility, Openness } from "@/types/enums";
 import { UPDATE_GROUP } from "../../graphql/group";
-import { Group, IGroup, usernameWithDomain } from "../../types/actor";
+import {
+  Group,
+  IGroup,
+  usernameWithDomain,
+  displayName,
+} from "../../types/actor";
 import { Address, IAddress } from "../../types/address.model";
 import { CONFIG } from "@/graphql/config";
 import { IConfig } from "@/types/config.model";
@@ -234,6 +227,8 @@ export default class GroupSettings extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   GroupVisibility = GroupVisibility;
 
   Openness = Openness;
diff --git a/js/src/views/Group/Timeline.vue b/js/src/views/Group/Timeline.vue
index b66b3d111..e4b9944fb 100644
--- a/js/src/views/Group/Timeline.vue
+++ b/js/src/views/Group/Timeline.vue
@@ -1,27 +1,21 @@
 <template>
   <div class="container section">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul v-if="group">
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.TIMELINE,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Activity") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="group"
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.TIMELINE,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Activity'),
+        },
+      ]"
+    />
+
     <section class="timeline">
       <b-field>
         <b-radio-button v-model="activityType" :native-value="undefined">
@@ -160,7 +154,7 @@
 </template>
 <script lang="ts">
 import { GROUP_TIMELINE } from "@/graphql/group";
-import { IGroup, usernameWithDomain } from "@/types/actor";
+import { IGroup, usernameWithDomain, displayName } from "@/types/actor";
 import { ActivityType } from "@/types/enums";
 import { Paginate } from "@/types/paginate";
 import { Component, Prop, Vue } from "vue-property-decorator";
@@ -234,6 +228,8 @@ export default class Timeline extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   ActivityType = ActivityType;
 
   ActivityAuthorFilter = ActivityAuthorFilter;
diff --git a/js/src/views/Moderation/Logs.vue b/js/src/views/Moderation/Logs.vue
index 4e6be0c7a..728781ce8 100644
--- a/js/src/views/Moderation/Logs.vue
+++ b/js/src/views/Moderation/Logs.vue
@@ -1,19 +1,17 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.REPORT_LOGS }">{{
-            $t("Moderation log")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.MODERATION,
+          text: $t('Moderation'),
+        },
+        {
+          name: RouteName.REPORT_LOGS,
+          text: $t('Moderation log'),
+        },
+      ]"
+    />
     <section v-if="actionLogs.total > 0 && actionLogs.elements.length > 0">
       <ul>
         <li v-for="log in actionLogs.elements" :key="log.id">
diff --git a/js/src/views/Moderation/Report.vue b/js/src/views/Moderation/Report.vue
index 59a94bf8b..71094d796 100644
--- a/js/src/views/Moderation/Report.vue
+++ b/js/src/views/Moderation/Report.vue
@@ -1,27 +1,23 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs" v-if="report">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li>
-          <router-link :to="{ name: RouteName.REPORTS }">{{
-            $t("Reports")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{ name: RouteName.REPORT, params: { id: report.id } }"
-            >{{
-              $t("Report #{reportNumber}", { reportNumber: report.id })
-            }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      v-if="report"
+      :links="[
+        {
+          name: RouteName.MODERATION,
+          text: $t('Moderation'),
+        },
+        {
+          name: RouteName.REPORTS,
+          text: $t('Reports'),
+        },
+        {
+          name: RouteName.REPORT,
+          params: { id: report.id },
+          text: $t('Report #{reportNumber}', { reportNumber: report.id }),
+        },
+      ]"
+    />
     <section>
       <b-message
         title="Error"
diff --git a/js/src/views/Moderation/ReportList.vue b/js/src/views/Moderation/ReportList.vue
index a1e278b9b..577842fdb 100644
--- a/js/src/views/Moderation/ReportList.vue
+++ b/js/src/views/Moderation/ReportList.vue
@@ -1,19 +1,17 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.MODERATION }">{{
-            $t("Moderation")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.REPORTS }">{{
-            $t("Reports")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.MODERATION,
+          text: $t('Moderation'),
+        },
+        {
+          name: RouteName.REPORTS,
+          text: $t('Reports'),
+        },
+      ]"
+    />
     <section>
       <div class="flex flex-wrap gap-2">
         <b-field :label="$t('Report status')">
@@ -93,7 +91,7 @@
   </div>
 </template>
 <script lang="ts">
-import { Component, Ref, Vue } from "vue-property-decorator";
+import { Component, Vue } from "vue-property-decorator";
 import { IReport } from "@/types/report.model";
 import { REPORTS } from "@/graphql/report";
 import ReportCard from "@/components/Report/ReportCard.vue";
diff --git a/js/src/views/Posts/Edit.vue b/js/src/views/Posts/Edit.vue
index 8dcb2aadd..7719011fc 100644
--- a/js/src/views/Posts/Edit.vue
+++ b/js/src/views/Posts/Edit.vue
@@ -2,43 +2,7 @@
   <div>
     <form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator">
       <div class="container section">
-        <nav class="breadcrumb" aria-label="breadcrumbs" v-if="actualGroup">
-          <ul>
-            <li>
-              <router-link
-                v-if="actualGroup"
-                :to="{
-                  name: RouteName.GROUP,
-                  params: {
-                    preferredUsername: usernameWithDomain(actualGroup),
-                  },
-                }"
-                >{{
-                  actualGroup.name || actualGroup.preferredUsername
-                }}</router-link
-              >
-              <b-skeleton v-else :animated="true"></b-skeleton>
-            </li>
-            <li>
-              <router-link
-                v-if="actualGroup"
-                :to="{
-                  name: RouteName.POSTS,
-                  params: {
-                    preferredUsername: usernameWithDomain(actualGroup),
-                  },
-                }"
-                >{{ $t("Posts") }}</router-link
-              >
-              <b-skeleton v-else :animated="true"></b-skeleton>
-            </li>
-            <li class="is-active">
-              <span v-if="preferredUsername">{{ $t("New post") }}</span>
-              <span v-else-if="slug">{{ $t("Edit post") }}</span>
-              <b-skeleton v-else :animated="true"></b-skeleton>
-            </li>
-          </ul>
-        </nav>
+        <breadcrumbs-nav v-if="actualGroup" :links="breadcrumbLinks" />
         <h1 class="title" v-if="isUpdate === true">
           {{ $t("Edit post") }}
         </h1>
@@ -174,7 +138,7 @@ import { CREATE_POST, UPDATE_POST } from "../../graphql/post";
 
 import { IPost } from "../../types/post.model";
 import Editor from "../../components/Editor.vue";
-import { IActor, usernameWithDomain } from "../../types/actor";
+import { displayName, IActor, usernameWithDomain } from "../../types/actor";
 import TagInput from "../../components/Event/TagInput.vue";
 import RouteName from "../../router/name";
 import Subtitle from "../../components/Utils/Subtitle.vue";
@@ -366,6 +330,39 @@ export default class EditPost extends mixins(GroupMixin, PostMixin) {
     }
     return this.group;
   }
+
+  get breadcrumbLinks() {
+    const links = [
+      {
+        name: RouteName.GROUP,
+        params: {
+          preferredUsername: usernameWithDomain(this.actualGroup),
+        },
+        text: displayName(this.actualGroup),
+      },
+      {
+        name: RouteName.POSTS,
+        params: {
+          preferredUsername: usernameWithDomain(this.actualGroup),
+        },
+        text: this.$t("Posts"),
+      },
+    ];
+    if (this.preferredUsername) {
+      links.push({
+        text: this.$t("New post") as string,
+        name: RouteName.POST_EDIT,
+        params: { preferredUsername: usernameWithDomain(this.actualGroup) },
+      });
+    } else {
+      links.push({
+        text: this.$t("Edit post") as string,
+        name: RouteName.POST_EDIT,
+        params: { preferredUsername: usernameWithDomain(this.actualGroup) },
+      });
+    }
+    return links;
+  }
 }
 </script>
 <style lang="scss" scoped>
diff --git a/js/src/views/Posts/List.vue b/js/src/views/Posts/List.vue
index 502bf3a71..50bd858f7 100644
--- a/js/src/views/Posts/List.vue
+++ b/js/src/views/Posts/List.vue
@@ -1,31 +1,19 @@
 <template>
   <div class="container section" v-if="group">
-    <nav class="breadcrumb" aria-label="breadcrumbs" v-if="group">
-      <ul>
-        <li>
-          <router-link
-            v-if="group"
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name || group.preferredUsername }}</router-link
-          >
-          <b-skeleton v-else :animated="true"></b-skeleton>
-        </li>
-        <li class="is-active">
-          <router-link
-            v-if="group"
-            :to="{
-              name: RouteName.POSTS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Posts") }}</router-link
-          >
-          <b-skeleton v-else :animated="true"></b-skeleton>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.POSTS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Posts'),
+        },
+      ]"
+    />
     <section>
       <div class="intro">
         <p v-if="isCurrentActorMember">
@@ -84,7 +72,7 @@ import { IMember } from "@/types/actor/member.model";
 import { FETCH_GROUP_POSTS } from "../../graphql/post";
 import { Paginate } from "../../types/paginate";
 import { IPost } from "../../types/post.model";
-import { usernameWithDomain } from "../../types/actor";
+import { usernameWithDomain, displayName } from "../../types/actor";
 import RouteName from "../../router/name";
 import MultiPostListItem from "../../components/Post/MultiPostListItem.vue";
 
@@ -148,6 +136,8 @@ export default class PostList extends mixins(GroupMixin) {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   POSTS_PAGE_LIMIT = POSTS_PAGE_LIMIT;
 
   get isCurrentActorMember(): boolean {
diff --git a/js/src/views/Resources/ResourceFolder.vue b/js/src/views/Resources/ResourceFolder.vue
index 539eaf0f0..6772ce9a8 100644
--- a/js/src/views/Resources/ResourceFolder.vue
+++ b/js/src/views/Resources/ResourceFolder.vue
@@ -1,80 +1,38 @@
 <template>
   <div class="container section" v-if="resource">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(resource.actor) },
-            }"
-            >{{ resource.actor.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.RESOURCE_FOLDER_ROOT,
-              params: { preferredUsername: usernameWithDomain(resource.actor) },
-            }"
-            >{{ $t("Resources") }}</router-link
-          >
-        </li>
-        <li
-          :class="{
-            'is-active':
-              index + 1 === ResourceMixin.resourcePathArray(resource).length,
-          }"
-          v-for="(pathFragment, index) in filteredPath"
-          :key="pathFragment"
-        >
-          <router-link
-            :to="{
-              name: RouteName.RESOURCE_FOLDER,
-              params: {
-                path: ResourceMixin.resourcePathArray(resource).slice(
-                  0,
-                  index + 1
-                ),
-                preferredUsername: usernameWithDomain(resource.actor),
-              },
-            }"
-            >{{ pathFragment }}</router-link
-          >
-        </li>
-        <li>
-          <b-dropdown aria-role="list">
-            <b-button class="button is-primary" slot="trigger">+</b-button>
+    <breadcrumbs-nav :links="breadcrumbLinks">
+      <li>
+        <b-dropdown aria-role="list">
+          <b-button class="button is-primary" slot="trigger">+</b-button>
 
-            <b-dropdown-item aria-role="listitem" @click="createFolderModal">
-              <b-icon icon="folder" />
-              {{ $t("New folder") }}
-            </b-dropdown-item>
-            <b-dropdown-item
-              aria-role="listitem"
-              @click="createLinkResourceModal = true"
-            >
-              <b-icon icon="link" />
-              {{ $t("New link") }}
-            </b-dropdown-item>
-            <hr
-              role="presentation"
-              class="dropdown-divider"
-              v-if="resourceProviders.length"
-            />
-            <b-dropdown-item
-              aria-role="listitem"
-              v-for="resourceProvider in resourceProviders"
-              :key="resourceProvider.software"
-              @click="createResourceFromProvider(resourceProvider)"
-            >
-              <b-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" />
-              {{ createSentenceForType(resourceProvider.software) }}
-            </b-dropdown-item>
-          </b-dropdown>
-        </li>
-      </ul>
-    </nav>
+          <b-dropdown-item aria-role="listitem" @click="createFolderModal">
+            <b-icon icon="folder" />
+            {{ $t("New folder") }}
+          </b-dropdown-item>
+          <b-dropdown-item
+            aria-role="listitem"
+            @click="createLinkResourceModal = true"
+          >
+            <b-icon icon="link" />
+            {{ $t("New link") }}
+          </b-dropdown-item>
+          <hr
+            role="presentation"
+            class="dropdown-divider"
+            v-if="resourceProviders.length"
+          />
+          <b-dropdown-item
+            aria-role="listitem"
+            v-for="resourceProvider in resourceProviders"
+            :key="resourceProvider.software"
+            @click="createResourceFromProvider(resourceProvider)"
+          >
+            <b-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" />
+            {{ createSentenceForType(resourceProvider.software) }}
+          </b-dropdown-item>
+        </b-dropdown>
+      </li>
+    </breadcrumbs-nav>
     <section>
       <p v-if="resource.path === '/'" class="module-description">
         {{
@@ -276,7 +234,7 @@ import ResourceItem from "@/components/Resource/ResourceItem.vue";
 import FolderItem from "@/components/Resource/FolderItem.vue";
 import Draggable from "vuedraggable";
 import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
-import { IActor, usernameWithDomain } from "../../types/actor";
+import { displayName, IActor, usernameWithDomain } from "../../types/actor";
 import RouteName from "../../router/name";
 import {
   IResource,
@@ -764,6 +722,40 @@ export default class Resources extends Mixins(ResourceMixin) {
       }
     }
   }
+
+  get breadcrumbLinks() {
+    if (!this.resource?.actor) return [];
+    const resourceActor = this.resource.actor;
+    const links = [
+      {
+        name: RouteName.GROUP,
+        params: { preferredUsername: usernameWithDomain(this.resource.actor) },
+        text: displayName(this.resource.actor),
+      },
+      {
+        name: RouteName.RESOURCE_FOLDER_ROOT,
+        params: { preferredUsername: usernameWithDomain(this.resource.actor) },
+        text: this.$t("Resources") as string,
+      },
+    ];
+
+    links.push(
+      ...this.filteredPath.map((pathFragment, index) => {
+        return {
+          name: RouteName.RESOURCE_FOLDER,
+          params: {
+            path: ResourceMixin.resourcePathArray(this.resource).slice(
+              0,
+              index + 1
+            ) as unknown as string,
+            preferredUsername: usernameWithDomain(resourceActor),
+          },
+          text: pathFragment,
+        };
+      })
+    );
+    return links;
+  }
 }
 </script>
 <style lang="scss" scoped>
diff --git a/js/src/views/Settings/AccountSettings.vue b/js/src/views/Settings/AccountSettings.vue
index 4cdb3a9ec..dbef8d39e 100644
--- a/js/src/views/Settings/AccountSettings.vue
+++ b/js/src/views/Settings/AccountSettings.vue
@@ -1,19 +1,17 @@
 <template>
   <div v-if="loggedUser">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{
-            $t("Account")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS_GENERAL }">{{
-            $t("General")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.ACCOUNT_SETTINGS,
+          text: $t('Account'),
+        },
+        {
+          name: RouteName.ACCOUNT_SETTINGS_GENERAL,
+          text: $t('General'),
+        },
+      ]"
+    />
     <section>
       <div class="setting-title">
         <h2>{{ $t("Email") }}</h2>
diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue
index efd052f90..2c463c97d 100644
--- a/js/src/views/Settings/Notifications.vue
+++ b/js/src/views/Settings/Notifications.vue
@@ -1,19 +1,17 @@
 <template>
   <div v-if="loggedUser">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{
-            $t("Account")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.NOTIFICATIONS }">{{
-            $t("Notifications")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.ACCOUNT_SETTINGS,
+          text: $t('Account'),
+        },
+        {
+          name: RouteName.NOTIFICATIONS,
+          text: $t('Notifications'),
+        },
+      ]"
+    />
     <section>
       <div class="setting-title">
         <h2>{{ $t("Browser notifications") }}</h2>
diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue
index 95543b864..d2980a9d5 100644
--- a/js/src/views/Settings/Preferences.vue
+++ b/js/src/views/Settings/Preferences.vue
@@ -1,19 +1,17 @@
 <template>
   <div>
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{
-            $t("Account")
-          }}</router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.PREFERENCES }">{{
-            $t("Preferences")
-          }}</router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.ACCOUNT_SETTINGS,
+          text: $t('Account'),
+        },
+        {
+          name: RouteName.PREFERENCES,
+          text: $t('Preferences'),
+        },
+      ]"
+    />
     <div>
       <b-field :label="$t('Language')" label-for="setting-language">
         <b-select
diff --git a/js/src/views/Todos/Todo.vue b/js/src/views/Todos/Todo.vue
index b8970e8a9..cdfe834f0 100644
--- a/js/src/views/Todos/Todo.vue
+++ b/js/src/views/Todos/Todo.vue
@@ -1,35 +1,29 @@
 <template>
   <section class="section container" v-if="todo">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: {
-                preferredUsername: todo.todoList.actor.preferredUsername,
-              },
-            }"
-            >{{ todo.todoList.actor.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.TODO_LIST,
-              params: { id: todo.todoList.id },
-            }"
-          >
-            {{ todo.todoList.title }}
-          </router-link>
-        </li>
-        <li class="is-active">
-          <router-link :to="{ name: RouteName.TODO }" aria-current="page">
-            {{ todo.title }}
-          </router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: {
+            preferredUsername: usernameWithDomain(todo.todoList.actor),
+          },
+          text: displayName(todo.todoList.actor),
+        },
+        {
+          name: RouteName.TODO_LISTS,
+          params: {
+            preferredUsername: usernameWithDomain(todo.todoList.actor),
+          },
+          text: $t('Task lists'),
+        },
+        {
+          name: RouteName.TODO_LIST,
+          params: { id: todo.todoList.id },
+          text: todo.todoList.title,
+        },
+        { name: RouteName.TODO, text: todo.title },
+      ]"
+    />
     <full-todo :todo="todo" />
   </section>
 </template>
@@ -39,6 +33,7 @@ import { GET_TODO } from "@/graphql/todos";
 import { ITodo } from "@/types/todos";
 import FullTodo from "@/components/Todo/FullTodo.vue";
 import RouteName from "../../router/name";
+import { displayName, usernameWithDomain } from "@/types/actor";
 
 @Component({
   components: {
@@ -70,5 +65,9 @@ export default class Todo extends Vue {
   todo!: ITodo;
 
   RouteName = RouteName;
+
+  usernameWithDomain = usernameWithDomain;
+
+  displayName = displayName;
 }
 </script>
diff --git a/js/src/views/Todos/TodoList.vue b/js/src/views/Todos/TodoList.vue
index 1aaa103ce..a48aab3c7 100644
--- a/js/src/views/Todos/TodoList.vue
+++ b/js/src/views/Todos/TodoList.vue
@@ -1,34 +1,24 @@
 <template>
   <section class="container section" v-if="todoList">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: todoList.actor.preferredUsername },
-            }"
-            >{{ todoList.actor.name }}</router-link
-          >
-        </li>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.TODO_LISTS,
-              params: { preferredUsername: todoList.actor.preferredUsername },
-            }"
-            >{{ $t("Task lists") }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{ name: RouteName.TODO_LIST, params: { id: todoList.id } }"
-          >
-            {{ todoList.title }}
-          </router-link>
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(todoList.actor) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.TODO_LISTS,
+          params: { preferredUsername: usernameWithDomain(todoList.actor) },
+          text: $t('Task lists'),
+        },
+        {
+          name: RouteName.TODO_LIST,
+          params: { id: todoList.id },
+          text: todoList.title,
+        },
+      ]"
+    />
     <h2 class="title">{{ todoList.title }}</h2>
     <div v-for="todo in todoList.todos.elements" :key="todo.id">
       <compact-todo :todo="todo" />
@@ -48,7 +38,7 @@ import { ITodo } from "@/types/todos";
 import { CREATE_TODO, FETCH_TODO_LIST } from "@/graphql/todos";
 import CompactTodo from "@/components/Todo/CompactTodo.vue";
 import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
-import { IActor } from "@/types/actor";
+import { displayName, IActor, usernameWithDomain } from "@/types/actor";
 import { ITodoList } from "@/types/todolist";
 import RouteName from "../../router/name";
 import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
@@ -89,6 +79,10 @@ export default class TodoList extends Vue {
 
   RouteName = RouteName;
 
+  displayName = displayName;
+
+  usernameWithDomain = usernameWithDomain;
+
   async createNewTodo(): Promise<void> {
     await this.$apollo.mutate({
       mutation: CREATE_TODO,
diff --git a/js/src/views/Todos/TodoLists.vue b/js/src/views/Todos/TodoLists.vue
index c58d7372f..14b26b926 100644
--- a/js/src/views/Todos/TodoLists.vue
+++ b/js/src/views/Todos/TodoLists.vue
@@ -1,27 +1,19 @@
 <template>
   <div class="container section" v-if="group">
-    <nav class="breadcrumb" aria-label="breadcrumbs">
-      <ul>
-        <li>
-          <router-link
-            :to="{
-              name: RouteName.GROUP,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ group.name }}</router-link
-          >
-        </li>
-        <li class="is-active">
-          <router-link
-            :to="{
-              name: RouteName.TODO_LISTS,
-              params: { preferredUsername: usernameWithDomain(group) },
-            }"
-            >{{ $t("Task lists") }}</router-link
-          >
-        </li>
-      </ul>
-    </nav>
+    <breadcrumbs-nav
+      :links="[
+        {
+          name: RouteName.GROUP,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: displayName(group),
+        },
+        {
+          name: RouteName.TODO_LISTS,
+          params: { preferredUsername: usernameWithDomain(group) },
+          text: $t('Task lists'),
+        },
+      ]"
+    />
     <section>
       <p>
         {{
@@ -61,7 +53,7 @@
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
 import { FETCH_GROUP } from "@/graphql/group";
-import { IGroup, usernameWithDomain } from "@/types/actor";
+import { IGroup, usernameWithDomain, displayName } from "@/types/actor";
 import { CREATE_TODO_LIST } from "@/graphql/todos";
 import CompactTodo from "@/components/Todo/CompactTodo.vue";
 import { ITodoList } from "@/types/todolist";
@@ -108,6 +100,8 @@ export default class TodoLists extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  displayName = displayName;
+
   get todoLists(): ITodoList[] {
     return this.group.todoLists.elements;
   }