diff --git a/reviewboard/accounts/actions.py b/reviewboard/accounts/actions.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a21fa7a80bf15b2db6f7ad9ea989aeaa9ce0d98
--- /dev/null
+++ b/reviewboard/accounts/actions.py
@@ -0,0 +1,324 @@
+"""Built-in actions for the accounts app.
+
+Version Added:
+    6.0
+"""
+
+from typing import TYPE_CHECKING
+
+from django.conf import settings
+from django.template import Context
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+
+from reviewboard import get_manual_url
+from reviewboard.actions import (AttachmentPoint,
+                                 BaseAction,
+                                 BaseMenuAction)
+
+
+if TYPE_CHECKING:
+    MixinParent = BaseAction
+else:
+    MixinParent = object
+
+
+class LoggedInUserMixin(MixinParent):
+    """Mixin for actions that only render for logged-in users.
+
+    Version Added:
+        6.0
+    """
+
+    def should_render(
+        self,
+        context: Context,
+    ) -> bool:
+        """Return whether this action should render.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            bool:
+            ``True`` if the action should render.
+        """
+        request = context['request']
+        return request.user.is_authenticated
+
+
+class AccountMenuAction(LoggedInUserMixin, BaseMenuAction):
+    """A menu for account-related actions.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'account-menu'
+    attachment = AttachmentPoint.HEADER
+    label = ''
+    template_name = 'accounts/account_menu_action.html'
+
+
+class LoginAction(BaseAction):
+    """Action for logging in.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'login'
+    label = _('Log in')
+    attachment = AttachmentPoint.HEADER
+
+    def get_url(
+        self,
+        context: Context,
+    ) -> str:
+        """Return the URL for the action.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            str:
+            The URL to use for the action.
+        """
+        request = context['request']
+        return '%s?next=%s' % (reverse('login'), request.path)
+
+    def should_render(
+        self,
+        context: Context,
+    ) -> bool:
+        """Return whether this action should render.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            bool:
+            ``True`` if the action should render.
+        """
+        request = context['request']
+        return not request.user.is_authenticated
+
+
+class LogoutAction(LoggedInUserMixin, BaseAction):
+    """Action for logging out.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'logout'
+    parent_id = 'account-menu'
+    label = _('Log out')
+    attachment = AttachmentPoint.HEADER
+    url_name = 'logout'
+
+
+class AdminAction(BaseAction):
+    """Action for the "Admin" page.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'admin'
+    parent_id = 'account-menu'
+    label = _('Admin')
+    attachment = AttachmentPoint.HEADER
+    url_name = 'admin-dashboard'
+
+    def should_render(
+        self,
+        context: Context,
+    ) -> bool:
+        """Return whether this action should render.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            bool:
+            ``True`` if the action should render.
+        """
+        request = context['request']
+        return request.user.is_staff
+
+
+class MyAccountAction(LoggedInUserMixin, BaseAction):
+    """Action for the "My Account" page.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'my-account'
+    parent_id = 'account-menu'
+    label = _('My account')
+    attachment = AttachmentPoint.HEADER
+    url_name = 'user-preferences'
+
+
+class SupportMenuAction(BaseMenuAction):
+    """A menu for support options.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'support-menu'
+    label = _('Support')
+    attachment = AttachmentPoint.HEADER
+
+
+class DocumentationAction(BaseAction):
+    """Action for accessing Review Board documentation.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'documentation'
+    parent_id = 'support-menu'
+    label = _('Documentation')
+    attachment = AttachmentPoint.HEADER
+
+    def get_url(
+        self,
+        context: Context,
+    ) -> str:
+        """Return the URL for the action.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            str:
+            The URL to use for the action.
+        """
+        return get_manual_url()
+
+
+class SupportAction(BaseAction):
+    """Action for linking to support.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'support'
+    parent_id = 'support-menu'
+    label = _('Get Support')
+    attachment = AttachmentPoint.HEADER
+    url_name = 'support'
+
+
+class FollowMenuAction(BaseMenuAction):
+    """A menu for follow options.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-menu'
+    label = _('Follow')
+    attachment = AttachmentPoint.HEADER
+
+    def should_render(
+        self,
+        context: Context,
+    ) -> bool:
+        """Return whether this action should render.
+
+        Args:
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            bool:
+            ``True`` if the action should render.
+        """
+        return not getattr(settings, 'DISABLE_FOLLOW_MENU', False)
+
+
+class FollowNewsAction(BaseAction):
+    """Action for following via News.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-rss'
+    parent_id = 'follow-menu'
+    label = _('Review Board News')
+    icon_class = 'fa fa-rss'
+    url = 'https://www.reviewboard.org/news/'
+    attachment = AttachmentPoint.HEADER
+
+
+class FollowTwitterAction(BaseAction):
+    """Action for following via Twitter.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-twitter'
+    parent_id = 'follow-menu'
+    label = _('Twitter')
+    icon_class = 'fa fa-twitter'
+    url = 'https://twitter.com/reviewboard/'
+    attachment = AttachmentPoint.HEADER
+
+
+class FollowFacebookAction(BaseAction):
+    """Action for following via Facebook.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-facebook'
+    parent_id = 'follow-menu'
+    label = _('Facebook')
+    icon_class = 'fa fa-facebook'
+    url = 'https://facebook.com/reviewboard.org'
+    attachment = AttachmentPoint.HEADER
+
+
+class FollowRedditAction(BaseAction):
+    """Action for following via Reddit.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-reddit'
+    parent_id = 'follow-menu'
+    label = _('Reddit')
+    icon_class = 'fa fa-reddit'
+    url = 'https://reddit.com/r/reviewboard'
+    attachment = AttachmentPoint.HEADER
+
+
+class FollowYouTubeAction(BaseAction):
+    """Action for following via YouTube.
+
+    Version Added:
+        6.0
+    """
+
+    action_id = 'follow-youtube'
+    parent_id = 'follow-menu'
+    label = _('YouTube')
+    icon_class = 'fa fa-youtube'
+    url = 'https://www.youtube.com/channel/UCTnwzlRTtx8wQOmyXiA_iCg'
+    attachment = AttachmentPoint.HEADER
diff --git a/reviewboard/actions/base.py b/reviewboard/actions/base.py
index 190cc4b087596d9d486855ba6791d48f50abaebf..c3cf64ba3a1411908aa284a3b93d8569f09d4321 100644
--- a/reviewboard/actions/base.py
+++ b/reviewboard/actions/base.py
@@ -13,6 +13,8 @@ from django.template import Context
 from django.template.loader import render_to_string
 from django.utils.safestring import SafeText, mark_safe
 
+from reviewboard.site.urlresolvers import local_site_reverse
+
 if TYPE_CHECKING:
     # This is available only in django-stubs.
     from django.utils.functional import _StrOrPromise
@@ -126,6 +128,14 @@ class BaseAction:
     #:     str
     url: str = '#'
 
+    #: A URL name to resolve.
+    #:
+    #: If this is not None, it will take precedence over :py:attr:`url`.
+    #:
+    #: Type:
+    #:     str
+    url_name: Optional[str] = None
+
     #: Whether this action is visible.
     #:
     #: Type:
@@ -260,8 +270,13 @@ class BaseAction:
             str:
             The URL to use for the action.
         """
-        assert self.url is not None
-        return self.url
+        assert self.url_name or self.url
+
+        if self.url_name:
+            return local_site_reverse(self.url_name,
+                                      request=context.get('request'))
+        else:
+            return self.url
 
     def get_visible(
         self,
diff --git a/reviewboard/actions/registry.py b/reviewboard/actions/registry.py
index d0c10992e1060e3a027fdc6e9490f49199dff1eb..94610d9bb2df84f7311a90ddee2e4b108970cb6b 100644
--- a/reviewboard/actions/registry.py
+++ b/reviewboard/actions/registry.py
@@ -48,6 +48,20 @@ class ActionsRegistry(OrderedRegistry):
             reviewboard.actions.base.BaseAction:
             The built-in actions.
         """
+        from reviewboard.accounts.actions import (AccountMenuAction,
+                                                  AdminAction,
+                                                  DocumentationAction,
+                                                  FollowFacebookAction,
+                                                  FollowMenuAction,
+                                                  FollowRedditAction,
+                                                  FollowNewsAction,
+                                                  FollowTwitterAction,
+                                                  FollowYouTubeAction,
+                                                  LoginAction,
+                                                  LogoutAction,
+                                                  MyAccountAction,
+                                                  SupportAction,
+                                                  SupportMenuAction)
         from reviewboard.reviews.actions import (AddGeneralCommentAction,
                                                  ArchiveAction,
                                                  ArchiveMenuAction,
@@ -67,6 +81,22 @@ class ActionsRegistry(OrderedRegistry):
         # The order here is important, and will reflect the order that items
         # appear in the UI.
         builtin_actions: List[BaseAction] = [
+            # Header bar
+            AccountMenuAction(),
+            LoginAction(),
+            MyAccountAction(),
+            AdminAction(),
+            LogoutAction(),
+            SupportMenuAction(),
+            DocumentationAction(),
+            SupportAction(),
+            FollowMenuAction(),
+            FollowNewsAction(),
+            FollowTwitterAction(),
+            FollowFacebookAction(),
+            FollowRedditAction(),
+            FollowYouTubeAction(),
+
             # Review request actions (left side)
             StarAction(),
             ArchiveMenuAction(),
diff --git a/reviewboard/static/rb/css/mixins/theme.less b/reviewboard/static/rb/css/mixins/theme.less
index 5a19571606d657d8d724e651fee2309c9f5c9a7a..0fa15b95677f65b3d16702d76921a9347c10a23a 100644
--- a/reviewboard/static/rb/css/mixins/theme.less
+++ b/reviewboard/static/rb/css/mixins/theme.less
@@ -21,6 +21,22 @@
              @topbar-menu-bg: @topbar-bg,
              @topbar-menu-selected-bg: lighten(@topbar-bg, 10%)) {
   #accountnav {
+    .rb-c-actions__action:hover {
+      border-color: @topbar-border-color;
+    }
+
+    .rb-c-menu {
+      border-color: @topbar-border-color;
+    }
+
+    .rb-c-menu__item {
+      background: @topbar-menu-bg;
+
+      &:hover, &:focus {
+        background: @topbar-menu-selected-bg;
+      }
+    }
+
     li {
       &:hover {
         background: @topbar-menu-selected-bg;
diff --git a/reviewboard/static/rb/css/pages/base.less b/reviewboard/static/rb/css/pages/base.less
index f78dc6ded0cfea27c0b1c57a4e7d11b3167c76fd..a323c8186d1bb3453e77fe7c460ef517a1a4a1c9 100644
--- a/reviewboard/static/rb/css/pages/base.less
+++ b/reviewboard/static/rb/css/pages/base.less
@@ -295,43 +295,63 @@ textarea {
 
 
 /*
- * The account and support menu drop-down region of the header bar.
+ * Top-bar actions.
  */
-#accountnav {
-  list-style: none;
-  margin: 0;
-  padding: 0;
-  z-index: @z-index-base;
-  float: right;
-
-  #rb-ns-pages.base.on-shell-mobile-mode({
-    display: none;
-  });
-
-  li {
-    border: 1px transparent solid;
-    border-top: 0;
-    display: block;
-    float: right;
-    margin: -@headerbar-padding 0;
+#topbar .rb-c-actions {
+  menu {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
     padding: 0;
-    position: relative;
     white-space: nowrap;
+  }
 
-    &:hover {
-      border-color: #888;
+  &__action {
+    display: inline-block;
+  }
 
-      ul {
-        border: 1px #888 solid;
-        display: block;
-        margin-right: -1px;
-        z-index: @z-index-menu;
+  .rb-c-menu {
+    margin: 0;
+  }
 
-        li {
-          border: 0;
-        }
-      }
+  .rb-c-menu__item {
+    margin: 0;
+    padding: 0;
+
+    &:last-child {
+      border-radius: 0 0 @box-border-radius @box-border-radius;
     }
+  }
+}
+
+
+/**
+ * Header actions.
+ *
+ * Structure:
+ *     <div class="rb-c-actions" role="presentation">
+ *      <menu class="rb-c-actions__content" role="menu">
+ *       ...
+ *      </menu>
+ *     </div>
+ */
+#headerbar .rb-c-actions {
+  float: right;
+
+  /**
+   * A header action.
+   *
+   * Structure:
+   *     <li class="rb-c-actions__action" role="presentation">
+   *      <a href="#" role="menuitem">...</a>
+   *     </li>
+   */
+  &__action {
+    border: 1px transparent solid;
+    border-top: 0;
+    box-sizing: border-box;
+    display: inline-block;
+    position: relative;
 
     a {
       color: inherit;
@@ -343,49 +363,32 @@ textarea {
       height: 32px;
       vertical-align: middle;
       line-height: 32px;
-
-      &.user-nav-item {
-        padding-left: 0.2em;
-      }
     }
+  }
 
-    img {
-      display: inline;
-      vertical-align: top;
-      margin: 0;
-      padding: 0;
-    }
-
-    ul {
-      display: none;
-      margin: 0;
-      min-width: @headerbar-menu-min-width;
-      padding: 0;
-      position: absolute;
-      right: 0;
-      border-radius: 0 0 @box-border-radius @box-border-radius;
-
-      li {
-        float: none;
-        margin: 0;
-        padding: 0;
-
-        &:last-child {
-          border-radius: 0 0 @box-border-radius @box-border-radius;
-        }
+  &__content {
+    box-sizing: border-box;
+    list-style: none;
+    margin: -@headerbar-padding 0;
+    padding: 0;
+    white-space: nowrap;
+    z-index: @z-index-base;
 
-        a {
-          margin: 0;
-        }
-      }
+    img {
+      vertical-align: middle;
     }
+  }
 
-    .fa {
-      margin-right: 0.2em;
-      min-width: 14px;
-      text-align: center;
-    }
+  .rb-c-menu {
+    border: 1px #888 solid;
+    border-radius: 0 0 @box-border-radius @box-border-radius;
+    margin-right: -1px;
+    right: 0;
   }
+
+  #rb-ns-pages.base.on-shell-mobile-mode({
+    display: none;
+  });
 }
 
 
diff --git a/reviewboard/templates/accounts/account_menu_action.html b/reviewboard/templates/accounts/account_menu_action.html
new file mode 100644
index 0000000000000000000000000000000000000000..c54f9b62be7396240f30c2fb6825af41e39b7bec
--- /dev/null
+++ b/reviewboard/templates/accounts/account_menu_action.html
@@ -0,0 +1,13 @@
+{% load actions avatars %}
+<li class="rb-c-actions__action" id="{{action.get_dom_element_id}}"
+    {% if not visible %} style="display: none;"{% endif %}
+    role="menuitem">
+ <a href="#" role="presentation" aria-label="{{label}}">
+{% if siteconfig_settings.avatars_enabled %}
+{%  avatar user 32 %}
+{% endif %}
+  {{request.user.username}}
+  <span class="rb-icon rb-icon-dropdown-arrow"></span>
+ </a>
+{% child_actions_html %}
+</li>
diff --git a/reviewboard/templates/base/_nav_support_menu.html b/reviewboard/templates/base/_nav_support_menu.html
deleted file mode 100644
index 832d45e2bf05ea271bcbbf097807323625542287..0000000000000000000000000000000000000000
--- a/reviewboard/templates/base/_nav_support_menu.html
+++ /dev/null
@@ -1,56 +0,0 @@
-{% load avatars djblets_utils i18n rb_extensions %}
-
-{% if not settings.DISABLE_FOLLOW_MENU %}
-<li>
- <a href="#">
-  {% trans "Follow" %}
-  <span class="rb-icon rb-icon-dropdown-arrow"></span>
- </a>
- <ul>
-  <li><a href="https://www.reviewboard.org/news/"><span class="fa fa-rss"></span> {% trans "Review Board News" %}</a></li>
-  <li><a href="https://twitter.com/reviewboard/"><span class="fa fa-twitter"></span> {% trans "Twitter" %}</a></li>
-  <li><a href="https://www.facebook.com/reviewboard.org"><span class="fa fa-facebook"></span> {% trans "Facebook" %}</a></li>
-  <li><a href="https://reddit.com/r/reviewboard"><span class="fa fa-reddit"></span> {% trans "Reddit" %}</a></li>
-  <li><a href="https://www.youtube.com/channel/UCTnwzlRTtx8wQOmyXiA_iCg"><span class="fa fa-youtube"></span> {% trans "YouTube" %}</a></li>
- </ul>
-</li>
-{% endif %}
-<li>
- <a href="#">
-  {% trans "Support" %}
-  <span class="rb-icon rb-icon-dropdown-arrow"></span>
- </a>
- <ul>
-  <li><a href="{{RB_MANUAL_URL}}">{% trans "Documentation" %}</a></li>
-  <li><a href="{% url 'support' %}">{% trans "Get Support" %}</a></li>
- </ul>
-</li>
-{% if request.user.is_authenticated %}
-<li>
-{%  spaceless %}
- <a class="user-nav-item" href="{% url 'user' request.user.username %}">
-{%   if siteconfig_settings.avatars_enabled %}
-  {% avatar user 32 %}
-{%   endif %}
-   {{request.user.username}}
-   <span class="rb-icon rb-icon-dropdown-arrow"></span>
- </a>
-{%  endspaceless %}
- <ul>
-{%  if not is_read_only %}
-  <li><a href="{% url 'user-preferences' %}">{% trans "My account" %}</a></li>
-{%  endif %}
-{%  if request.user.is_staff %}
-  <li><a href="{% url 'admin-dashboard' %}">{% trans "Admin" %}</a></li>
-{%  endif %}
-  <li><a href="{% url 'logout' %}">{% trans "Log out" %}</a></li>
- </ul>
-</li>
-{% else %}
-<li><a href="{% url 'login' %}?next={{request.path}}">{% trans "Log in" %}</a></li>
-{#  XXX Using default sucks, but siteconfig defaults don't #}
-{#      work from templates.                               #}
-{%  if auth_backends.0.supports_registration and siteconfig_settings.auth_enable_registration|default_if_none:1 and not siteconfig_settings.site_read_only %}
-<li><a href="{% url 'register' %}">{% trans "Register" %}</a></li>
-{%  endif %}
-{% endif %}{# !is_authenticated #}
diff --git a/reviewboard/templates/base/headerbar.html b/reviewboard/templates/base/headerbar.html
index b11244b91fdefd0a57cbbb46cab8b8c3c7faac53..cf5df8c060d4f325dab777cb87c4a7e41c566a28 100644
--- a/reviewboard/templates/base/headerbar.html
+++ b/reviewboard/templates/base/headerbar.html
@@ -1,14 +1,14 @@
-{% load djblets_utils gravatars i18n rb_extensions %}
+{% load actions djblets_utils gravatars i18n rb_extensions %}
 
 <div id="headerbar">
 {% include "base/branding.html" %}
  <div id="nav_toggle" role="button" aria-label="{% trans 'Toggle navigation' %}"><span class="fa fa-navicon"></span></div>
- <nav aria-label="{% trans 'Account and product' %}">
-  <ul id="accountnav" role="menubar">
-{% include "base/_nav_support_menu.html" %}
+ <nav class="rb-c-actions" aria-label="{% trans 'Account and product' %}" role="presentation">
+  <menu class="rb-c-actions__content" id="accountnav" role="menu">
+{% actions_html "header" %}
 {% header_action_hooks %}
 {% header_dropdown_action_hooks %}
-  </ul>
+  </menu>
  </nav>
 
  <div id="search">
