diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1a7340fd9d033b40aa5f52df459b0c4315864e87..ba6a9d2aa109df57123e8cc05e4dadbebbf7f852 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,14 @@ and this project adheres to `Semantic Versioning`_.
 Unreleased
 ----------
 
+Added
+~~~~~
+
+* Use identicons where avatars are missing.
+* Display personal photos instead of avatars based on a site preference.
+* Add an account menu in the top navbar.
+* Create a reusable snippet for avatar content.
+
 Changed
 ~~~~~~~
 
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 7938539c53327936d86d6c08334834e8e2a4fb0a..da2d102535b5daa91c8484475ec843c16e4d66aa 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -49,86 +49,6 @@ MENUS = {
                 ),
             ],
         },
-        {
-            "name": _("Account"),
-            "url": "#",
-            "icon": "person",
-            "root": True,
-            "validators": ["menu_generator.validators.is_authenticated"],
-            "submenu": [
-                {
-                    "name": _("Stop impersonation"),
-                    "url": "impersonate-stop",
-                    "icon": "stop",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.is_impersonate",
-                    ],
-                },
-                {
-                    "name": _("Logout"),
-                    "url": "logout",
-                    "icon": "exit_to_app",
-                    "validators": ["menu_generator.validators.is_authenticated"],
-                },
-                {
-                    "name": _("2FA"),
-                    "url": "two_factor:profile",
-                    "icon": "phonelink_lock",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                    ],
-                },
-                {
-                    "name": _("Change password"),
-                    "url": "account_change_password",
-                    "icon": "lock",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        (
-                            "aleksis.core.util.predicates.permission_validator",
-                            "core.can_change_password",
-                        ),
-                    ],
-                },
-                {
-                    "name": _("Me"),
-                    "url": "person",
-                    "icon": "insert_emoticon",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Preferences"),
-                    "url": "preferences_person",
-                    "icon": "settings",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Third-party accounts"),
-                    "url": "socialaccount_connections",
-                    "icon": "public",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-                {
-                    "name": _("Authorized applications"),
-                    "url": "oauth2_provider:authorized-token-list",
-                    "icon": "touch_app",
-                    "validators": [
-                        "menu_generator.validators.is_authenticated",
-                        "aleksis.core.util.core_helpers.has_person",
-                    ],
-                },
-            ],
-        },
         {
             "name": _("Admin"),
             "url": "#",
@@ -329,4 +249,78 @@ MENUS = {
             ],
         },
     ],
+    "NAVBAR_ACCOUNT_MENU": [
+        {
+            "name": _("Stop impersonation"),
+            "url": "impersonate-stop",
+            "icon": "stop",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.is_impersonate",
+            ],
+        },
+        {
+            "name": _("Account"),
+            "url": "person",
+            "icon": "person",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("Preferences"),
+            "url": "preferences_person",
+            "icon": "settings",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("2FA"),
+            "url": "two_factor:profile",
+            "icon": "phonelink_lock",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+            ],
+        },
+        {
+            "name": _("Change password"),
+            "url": "account_change_password",
+            "icon": "lock",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "core.can_change_password",
+                ),
+            ],
+        },
+        {
+            "name": _("Third-party accounts"),
+            "url": "socialaccount_connections",
+            "icon": "public",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "name": _("Authorized applications"),
+            "url": "oauth2_provider:authorized-token-list",
+            "icon": "touch_app",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+        },
+        {
+            "divider": True,
+            "name": _("Logout"),
+            "url": "logout",
+            "icon": "exit_to_app",
+            "validators": ["menu_generator.validators.is_authenticated"],
+        },
+    ],
 }
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 812a9d254ca4bff2e9372ed7969d0cb2b811c176..67867a6cd211b55eaf8e9321a750af66d65c30eb 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1,4 +1,5 @@
 # flake8: noqa: DJ01
+import base64
 import hmac
 from datetime import date, datetime, timedelta
 from typing import Any, Iterable, List, Optional, Sequence, Union
@@ -24,6 +25,7 @@ from django.utils.functional import classproperty
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
+import customidenticon
 import jsonstore
 from cachalot.api import cachalot_disabled
 from cache_memoize import cache_memoize
@@ -314,7 +316,12 @@ class Person(ExtensibleModel):
 
     @property
     def initials(self):
-        return f"{self.first_name[0]}{self.last_name[0]}".upper()
+        initials = ""
+        if self.first_name:
+            initials += self.first_name[0]
+        if self.last_name:
+            initials += self.last_name[0]
+        return initials.upper() or "?"
 
     user_info_tracker = FieldTracker(fields=("first_name", "last_name", "email", "user_id"))
 
@@ -334,6 +341,20 @@ class Person(ExtensibleModel):
             q = q.union(group.child_groups_recursive)
         return q
 
+    @property
+    @cache_memoize(60 * 60)
+    def identicon_url(self):
+        identicon = customidenticon.create(self.full_name, border=35)
+        base64_data = base64.b64encode(identicon).decode("ascii")
+        return f"data:image/png;base64,{base64_data}"
+
+    @property
+    def avatar_url(self):
+        if self.avatar:
+            return self.avatar.url
+        else:
+            return self.identicon_url
+
     def save(self, *args, **kwargs):
         # Determine all fields that were changed since last load
         changed = self.user_info_tracker.changed()
diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index f1fb0227eab16a152f29918e91b37d0808471812..c7ad53cec6e0d09406a02da80c43f72fc7865e30 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -422,6 +422,16 @@ class PersonChangeNotificationContact(StringPreference):
     required = False
 
 
+@site_preferences_registry.register
+class PersonPreferPhoto(BooleanPreference):
+    """Preference, whether personal photos should be displayed instead of avatars."""
+
+    section = account
+    name = "person_prefer_photo"
+    default = False
+    verbose_name = _("Prefer personal photos over avatars")
+
+
 @site_preferences_registry.register
 class PDFFileExpirationDuration(IntegerPreference):
     """PDF file expiration duration."""
diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js
index 9b4c133e1ae7db5906645963cfd7182da313ca01..afc7b50fd530574753bc71f3c81cdd53c769e0a4 100644
--- a/aleksis/core/static/js/main.js
+++ b/aleksis/core/static/js/main.js
@@ -121,6 +121,10 @@ $(document).ready(function () {
 
     // Initialize dropdown [MAT]
     $('.dropdown-trigger').dropdown();
+    $('.navbar-dropdown-trigger').dropdown({
+        "coverTrigger": false,
+        "constrainWidth": false,
+    });
 
     // If JS is activated, the language form will be auto-submitted
     $('.language-field select').change(function () {
diff --git a/aleksis/core/static/public/style.scss b/aleksis/core/static/public/style.scss
index 3fb108dad7c03c66791fb86ebb51e599764bc747..888dad2b0bdaa3b6832c41c039c21bc084954696 100644
--- a/aleksis/core/static/public/style.scss
+++ b/aleksis/core/static/public/style.scss
@@ -77,6 +77,9 @@ header, main, footer {
 .materialize-circle {
   @extend .circle;
 }
+.collection .collection-item.avatar > .materialize-circle > .materialize-circle {
+  left: 0;
+}
 
 /**********/
 /* HEADER */
@@ -211,25 +214,6 @@ div#search-results {
 }
 
 
-// Sidenav trigger
-
-header a.sidenav-trigger {
-  position: absolute;
-  left: 7.5%;
-  top: 0;
-
-  height: 64px;
-  font-size: 38px;
-
-  float: none;
-
-  text-align: center;
-  color: white;
-
-  z-index: 2;
-}
-
-
 // Footer
 
 .footer-icon {
@@ -828,8 +812,8 @@ $person-logo-size: 20vh;
 
   & img {
     border-radius: 50%;
-    width: 20vh;
-    height: 20vh;
+    width: 100%;
+    height: 100%;
     object-fit: cover;
   }
 }
@@ -844,6 +828,64 @@ $person-logo-size: 20vh;
   user-select: none;
   cursor: default;
   border-radius: 50%;
+  height: unset;
+}
+
+.nav-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 1rem;
+  > a {
+    position: static!important;
+    transform: none!important;
+  }
+  & .nav-spacer {
+    width: 60px;
+  }
+  & ul.account-nav {
+    display: flex;
+    margin-inline: 7.5px;
+    & > li > a {
+      padding: 0 7.5px;
+    }
+  }
+}
+
+.nav-wrapper .navbar-dropdown-trigger {
+  cursor: pointer;
+  height: 100%;
+  display: grid;
+}
+
+.navbar-dropdown-trigger .clip-circle {
+  margin: auto;
+  width: $navbar-height*0.75;
+  height: $navbar-height*0.75;
+  cursor: pointer;
+
+  &.no-image, &.no-image > i.material-icons {
+    font-size: calc(#{$navbar-height} * 0.75 * 0.5);
+    color: #6f6f6f;
+    background: #f2f2f2;
+    line-height: $navbar-height*0.75;
+    width: $navbar-height*0.75;
+    cursor: pointer;
+  }
+}
+
+i.material-icons.new-notification {
+  position: relative;
+  &:after {
+    content: "";
+    position: absolute;
+    width: 12px;
+    height: 12px;
+    bottom: 27%;
+    right: -4%;
+    background-color: $secondary-color;
+    border-radius: 50%;
+  }
 }
 
 #hero-bg {
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 745a6668cdc42eb265536950a0acbecfb57ee537..d3a1ada6436769ab7a449b27051823ee49897c44 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -61,32 +61,59 @@
 <body {% if no_menu %}class="without-menu"{% endif %}>
 
 <header>
-  <!-- Menu button (sidenav) -->
-  <div class="container">
-    <a href="#" data-target="slide-out" class="top-nav sidenav-trigger hide-on-large-only">
-      <i class="material-icons">menu</i>
-    </a>
-  </div>
-
   <!-- Nav bar (logged in as, logout) -->
   <nav class="nav-extended">
     <div class="nav-wrapper">
+      <a href="#" data-target="slide-out" class="top-nav sidenav-trigger hide-on-large-only">
+        <i class="material-icons">menu</i>
+      </a>
+
       <a class="brand-logo" href="/">{{ request.site.preferences.general__title }}</a>
 
-      <ul id="nav-mobile" class="right hide-on-med-and-down">
-        {% if user.is_authenticated %}
-          <li>{% trans "Logged in as" %} {{ user.get_username }}</li>
+      {% if user.is_authenticated %}
+        <ul class="account-nav">
+          {% trans "Notifications" as notifications_text %}
           <li>
-            <a href="{% url 'logout' %}">{% trans "Logout" %} <i class="material-icons right">exit_to_app</i></a>
+            <a href="{% url "notifications" %}" class="tooltipped" data-position="bottom"
+               data-tooltip="{{ notifications_text }}" aria-label="{{ notifications_text }}">
+              <i class="material-icons {% if request.user.person.unread_notifications_count > 0 %}new-notification{% endif %}">
+                notifications
+              </i>
+            </a>
           </li>
-        {% endif %}
-      </ul>
+          <li>
+            <a href="#!" class="navbar-dropdown-trigger" data-target="account-dropdown">
+              {{ request.user.person.identicon }}
+              {% include "core/partials/avatar_content.html" with person_or_user=request.user.person %}
+            </a>
+          </li>
+        </ul>
+      {% else %}
+        <span class="nav-spacer"></span>
+      {% endif %}
     </div>
     <div class="nav-content">
       {% block nav_content %}{% endblock %}
     </div>
   </nav>
 
+  {% get_menu "NAVBAR_ACCOUNT_MENU" as account_menu %}
+  <ul id="account-dropdown" class="dropdown-content">
+    {% for item in account_menu %}
+      {% if item.divider %}
+        <li class="divider"></li>
+      {% endif %}
+      <li>
+        <a href="{{ item.url }}">
+          {% if item.icon %}
+            <i class="material-icons">{{ item.icon }}</i>
+          {% endif %}
+          {{ item.name }}
+        </a>
+      </li>
+    {% endfor %}
+  </ul>
+
   <!-- Main nav (sidenav) -->
   {% if not no_menu %}
     <ul id="slide-out" class="sidenav sidenav-fixed">
diff --git a/aleksis/core/templates/core/partials/avatar_content.html b/aleksis/core/templates/core/partials/avatar_content.html
new file mode 100644
index 0000000000000000000000000000000000000000..deef48489f4ed118a4420f05ea226f87d7c0b895
--- /dev/null
+++ b/aleksis/core/templates/core/partials/avatar_content.html
@@ -0,0 +1,29 @@
+{% load rules i18n %}
+{% has_perm 'core.view_avatar_rule' request.user person_or_user as can_view_avatar %}
+{% has_perm 'core.view_photo_rule' request.user person_or_user as can_view_photo %}
+{% if SITE_PREFERENCES.account__person_prefer_photo and person_or_user.photo and can_view_photo %}
+  <div class="{% firstof class "clip-circle" %}">
+    <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.photo.url }}"
+         alt="{{ person_or_user.full_name }}" {% if title %} title="{{ person_or_user.full_name }}"{% endif %}/>
+  </div>
+{% elif person_or_user.identicon_url %}
+  {# If this is a person #}
+  <div class="{% firstof class "clip-circle" %}">
+    {% if can_view_avatar %}
+      <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.avatar_url }}"
+           alt="{{ person_or_user.full_name }} ({% trans "Avatar" %})" {% if title %}
+           title="{{ person_or_user.full_name }} ({% trans "Avatar" %})"{% endif %}/>
+    {% else %}
+
+      <img class="{% firstof img_class "hundred-percent" %}" src="{{ person_or_user.identicon_url }}"
+           alt="{{ person_or_user.full_name }} ({% trans "Identicon" %})" {% if title %}
+           title="{{ person_or_user.full_name }} ({% trans "Identicon" %})"{% endif %} />
+    {% endif %}
+  </div>
+
+{% else %}
+  {# There is a user without a person #}
+  <div class="{% firstof class "clip-circle" %} no-image">
+    <i class="material-icons">person</i>
+  </div>
+{% endif %}
diff --git a/aleksis/core/templates/core/person/collection.html b/aleksis/core/templates/core/person/collection.html
index fb8518e97e3e8d770260c8e6b004df420d8efa15..c86da61e43ae19ceaa0f601c2f3ec012357bc04d 100644
--- a/aleksis/core/templates/core/person/collection.html
+++ b/aleksis/core/templates/core/person/collection.html
@@ -3,13 +3,7 @@
 <div class="collection person-collection">
   {% for person in persons %}
     <a class="collection-item avatar waves-effect" href="{% url "person_by_id" person.pk %}">
-      {% has_perm 'core.view_photo_rule' user person as can_view_photo %}
-      {% if person.photo and can_view_photo %}
-        <img class="circle" src="{{ person.photo.url }}"
-             alt="{{ person.first_name }} {{ person.last_name }}"/>
-      {% else %}
-        <i class="material-icons materialize-circle">person</i>
-      {% endif %}
+      {% include "core/partials/avatar_content.html" with person_or_user=person class="materialize-circle" img_class="materialize-circle" %}
       {{ person }}
     </a>
   {% endfor %}
diff --git a/aleksis/core/templates/core/person/full.html b/aleksis/core/templates/core/person/full.html
index 23f8ba29b30188dd4cdf5b76af23991a0f618119..3972661fa81828e12b47fa606a3fc59b37ba9e69 100644
--- a/aleksis/core/templates/core/person/full.html
+++ b/aleksis/core/templates/core/person/full.html
@@ -59,19 +59,7 @@
 
   <header class="person-container">
     <div class="image-wrapper">
-      {% has_perm 'core.view_avatar_rule' user person as can_view_avatar %}
-      {% if person.avatar and can_view_avatar %}
-        <div class="clip-circle materialboxed z-depth-2">
-          <img class="hundred-percent" src="{{ person.avatar.url }}"
-               alt="{{ person.first_name }} {{ person.last_name }}"/>
-        </div>
-
-      {% else %}
-
-        <div class="clip-circle no-image z-depth-2">
-          {{ person.initials }}
-        </div>
-      {% endif %}
+      {% include "core/partials/avatar_content.html" with class="clip-circle materialboxed z-depth-2" person_or_user=person title=True %}
     </div>
     <h1>
       {{ person.first_name }} {{ person.last_name }}
@@ -209,19 +197,22 @@
         {% endif %}
       </table>
       </div>
+      {% has_perm 'core.view_avatar_rule' user person as can_view_avatar %}
       {% has_perm 'core.view_photo_rule' user person as can_view_photo %}
-      {% if person.photo and can_view_photo %}
+      {% if person.photo and can_view_photo and not SITE_PREFERENCES.account__person_prefer_photo %}
         <div class="card">
           <div class="card-image">
             <img src="{{ person.photo.url }}" alt="{{ person.first_name }} {{ person.last_name }}" class="materialboxed">
             <span class="card-title">{{ person.first_name }} {{ person.last_name }}</span>
           </div>
         </div>
-
-      {% else %}
-        <div class="card-panel">
-          <i class="material-icons left">image_not_supported</i>
-          {% trans "This person didn't upload a personal photo." %}
+      {% elif person.avatr and can_view_avatar %}
+        <div class="card">
+          <div class="card-image">
+            <img src="{{ person.avatar.url }}"
+                 alt="{{ person.first_name }} {{ person.last_name }}  ({% trans "Avatar" %})" class="materialboxed">
+            <span class="card-title">{{ person.first_name }} {{ person.last_name }} ({% trans "Avatar" %})</span>
+          </div>
         </div>
       {% endif %}
     </div>
diff --git a/pyproject.toml b/pyproject.toml
index 12199f73cd28724dffa7087dcb6628ea68943fe1..e74135d61fd57aa8c53b16d8342b55eb81c825bb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -126,6 +126,7 @@ python-gnupg = "^0.4.7"
 sentry-sdk = {version = "^1.4.3", optional = true}
 django-cte = "^1.1.5"
 pycountry = "^22.0.0"
+customidenticon = "^0.1.5"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]