diff --git a/reviewboard/actions/renderers.py b/reviewboard/actions/renderers.py
index 864626ea30fed0f52b65c66fb74a258463c5c13e..bc2b7e7c58a06da4bc87007f0b5dbc00984f0f96 100644
--- a/reviewboard/actions/renderers.py
+++ b/reviewboard/actions/renderers.py
@@ -523,3 +523,65 @@ class DetailedMenuActionGroupRenderer(MenuActionGroupRenderer):
 
     default_item_renderer_cls: ClassVar[type[BaseActionRenderer]] = \
         DetailedMenuItemActionRenderer
+
+
+class SidebarItemActionRenderer(BaseActionRenderer):
+    """Renderer for items in a sidebar.
+
+    An item in a sidebar contains a label, an optional icon, and an
+    optional URL.
+
+    If a URL is supplied and it matches the current page, the item's
+    presentation will show as active.
+
+    Version Added:
+        7.1
+    """
+
+    template_name = 'actions/sidebar_item_action.html'
+
+    def get_extra_context(
+        self,
+        *,
+        request: HttpRequest,
+        context: Context,
+    ) -> dict[str, Any]:
+        """Return extra template context for the action.
+
+        Args:
+            request (django.http.HttpRequest):
+                The HTTP request from the client.
+
+            context (django.template.Context):
+                The current rendering context.
+
+        Returns:
+            dict:
+            Extra context to use when rendering the action's template.
+        """
+        extra_context = super().get_extra_context(request=request,
+                                                  context=context)
+        extra_context['is_active'] = (
+            self.action.get_url(context=context) == request.path)
+
+        return extra_context
+
+
+class SidebarActionGroupRenderer(BaseActionGroupRenderer):
+    """Renderer for a group in a sidebar.
+
+    A rendered sidebar group may contain any number of items or nested
+    groups (though presentation may not be optimal if a subgroup contains
+    anther subgroup, due to space limitations in the sidebar).
+
+    Version Added:
+        7.1
+    """
+
+    default_item_renderer_cls: ClassVar[type[BaseActionRenderer]] = \
+        SidebarItemActionRenderer
+
+    default_subgroup_renderer_cls: ClassVar[ActionSubgroupRendererType] = \
+        'self'
+
+    template_name = 'actions/sidebar_group_action.html'
diff --git a/reviewboard/actions/tests/test_sidebar_action_group_renderer.py b/reviewboard/actions/tests/test_sidebar_action_group_renderer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8522712881dfe58f024b4987b1993251116adee7
--- /dev/null
+++ b/reviewboard/actions/tests/test_sidebar_action_group_renderer.py
@@ -0,0 +1,210 @@
+"""Unit tests for reviewboard.actions.renderers.SidebarActionGroupRenderer.
+
+Version Added:
+    7.1
+"""
+
+from __future__ import annotations
+
+from django.template import Context
+from django.utils.safestring import SafeString
+
+from reviewboard.actions.base import (ActionPlacement,
+                                      AttachmentPoint,
+                                      BaseAction,
+                                      BaseGroupAction)
+from reviewboard.actions.renderers import SidebarActionGroupRenderer
+from reviewboard.actions.tests.base import TestActionsRegistry
+from reviewboard.testing import TestCase
+
+
+class _MySidebarGroupAction(BaseGroupAction):
+    action_id = 'test-sidebar-group'
+    label = 'My Group'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER)
+    ]
+
+
+class _MySidebarItem1(BaseAction):
+    action_id = 'test-sidebar-item1'
+    label = 'Item 1'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarGroupAction.action_id),
+    ]
+
+
+class _MySidebarSubGroup1Action(BaseGroupAction):
+    action_id = 'test-sidebar-subgroup1'
+    label = 'My Sub-Group 1'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarGroupAction.action_id),
+    ]
+
+
+class _MySidebarItem2(BaseAction):
+    action_id = 'test-sidebar-item2'
+    label = 'Item 2'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarSubGroup1Action.action_id),
+    ]
+
+
+class _MySidebarSubGroup2Action(BaseGroupAction):
+    action_id = 'test-sidebar-subgroup2'
+    label = 'My Sub-Group 2'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarGroupAction.action_id),
+    ]
+
+
+class _MySidebarItem3(BaseAction):
+    action_id = 'test-sidebar-item3'
+    label = 'Item 3'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarSubGroup2Action.action_id),
+    ]
+
+
+class SidebarActionGroupRendererTests(TestCase):
+    """Unit tests for SidebarActionGroupRenderer.
+
+    Version Added:
+        7.1
+    """
+
+    def test_render(self) -> None:
+        """Testing SidebarActionGroupRenderer.render"""
+        group_action = _MySidebarGroupAction()
+        subgroup1_action = _MySidebarSubGroup1Action()
+        subgroup2_action = _MySidebarSubGroup2Action()
+        item1_action = _MySidebarItem1()
+        item2_action = _MySidebarItem2()
+        item3_action = _MySidebarItem3()
+
+        placement = group_action.get_placement(AttachmentPoint.HEADER)
+
+        registry = TestActionsRegistry()
+        registry.register(group_action)
+        registry.register(item1_action)
+        registry.register(subgroup1_action)
+        registry.register(subgroup2_action)
+        registry.register(item2_action)
+        registry.register(item3_action)
+
+        renderer = SidebarActionGroupRenderer(action=group_action,
+                                              placement=placement)
+        request = self.create_http_request()
+        context = Context({
+            'request': request,
+        })
+
+        html = renderer.render(request=request,
+                               context=context)
+
+        self.assertIsInstance(html, SafeString)
+        self.assertHTMLEqual(
+            html,
+            """
+            <li class="rb-c-actions__action rb-c-sidebar__section"
+                id="action-header-test-sidebar-group"
+                aria-labelledby="test-sidebar-group__label"
+                role="group">
+             <header class="rb-c-sidebar__section-header"
+                     id="test-sidebar-group__label">
+              My Group
+             </header>
+             <ul class="rb-c-sidebar__items">
+              <li class="rb-c-sidebar__nav-item"
+                  id="action-header-test-sidebar-item1">
+               <a class="rb-c-sidebar__item-label">
+                Item 1
+               </a>
+              </li>
+              <li class="rb-c-actions__action rb-c-sidebar__section"
+                  id="action-header-test-sidebar-subgroup1"
+                  aria-labelledby="test-sidebar-subgroup1__label"
+                  role="group">
+               <header class="rb-c-sidebar__section-header"
+                       id="test-sidebar-subgroup1__label">
+                My Sub-Group 1
+               </header>
+               <ul class="rb-c-sidebar__items">
+                <li class="rb-c-sidebar__nav-item"
+                    id="action-header-test-sidebar-item2">
+                 <a class="rb-c-sidebar__item-label">
+                  Item 2
+                 </a>
+                </li>
+               </ul>
+              </li>
+              <li class="rb-c-actions__action rb-c-sidebar__section"
+                  id="action-header-test-sidebar-subgroup2"
+                  aria-labelledby="test-sidebar-subgroup2__label"
+                  role="group">
+               <header class="rb-c-sidebar__section-header"
+                       id="test-sidebar-subgroup2__label">
+                My Sub-Group 2
+               </header>
+               <ul class="rb-c-sidebar__items">
+                <li class="rb-c-sidebar__nav-item"
+                    id="action-header-test-sidebar-item3">
+                 <a class="rb-c-sidebar__item-label">
+                  Item 3
+                 </a>
+                </li>
+               </ul>
+              </li>
+             </ul>
+            </li>
+            """)
+
+    def test_render_js(self) -> None:
+        """Testing SidebarActionGroupRenderer.render_js"""
+        group_action = _MySidebarGroupAction()
+        subgroup1_action = _MySidebarSubGroup1Action()
+        subgroup2_action = _MySidebarSubGroup2Action()
+        item1_action = _MySidebarItem1()
+        item2_action = _MySidebarItem2()
+        item3_action = _MySidebarItem3()
+        placement = group_action.get_placement(AttachmentPoint.HEADER)
+
+        registry = TestActionsRegistry()
+        registry.register(group_action)
+        registry.register(item1_action)
+        registry.register(subgroup1_action)
+        registry.register(subgroup2_action)
+        registry.register(item2_action)
+        registry.register(item3_action)
+
+        renderer = SidebarActionGroupRenderer(action=group_action,
+                                              placement=placement)
+        request = self.create_http_request()
+        context = Context({
+            'request': request,
+        })
+
+        js = renderer.render_js(request=request,
+                                context=context)
+
+        self.assertIsInstance(js, SafeString)
+        self.assertHTMLEqual(
+            js,
+            """
+            page.addActionView(new RB.Actions.ActionView({
+                "attachmentPointID": "header",
+                el: $('#action-header-test-sidebar-group'),
+                model: page.getAction("test-sidebar-group"),
+            }));
+            """)
diff --git a/reviewboard/actions/tests/test_sidebar_item_action_renderer.py b/reviewboard/actions/tests/test_sidebar_item_action_renderer.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6d57fa24fff36d1c014961809903390b481df1b
--- /dev/null
+++ b/reviewboard/actions/tests/test_sidebar_item_action_renderer.py
@@ -0,0 +1,146 @@
+"""Unit tests for reviewboard.actions.renderers.SidebarItemActionRenderer.
+
+Version Added:
+    7.1
+"""
+
+from __future__ import annotations
+
+from django.template import Context
+from django.utils.safestring import SafeString
+
+from reviewboard.actions.base import (ActionPlacement,
+                                      AttachmentPoint,
+                                      BaseAction,
+                                      BaseGroupAction)
+from reviewboard.actions.renderers import SidebarItemActionRenderer
+from reviewboard.actions.tests.base import TestActionsRegistry
+from reviewboard.testing import TestCase
+
+
+class _MySidebarGroupAction(BaseGroupAction):
+    action_id = 'test-sidebar-group'
+    label = 'My Group'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER)
+    ]
+
+
+class _MySidebarItem(BaseAction):
+    action_id = 'test-sidebar-item'
+    label = 'Item 1'
+    url_name = 'dashboard'
+
+    placements = [
+        ActionPlacement(attachment=AttachmentPoint.HEADER,
+                        parent_id=_MySidebarGroupAction.action_id),
+    ]
+
+
+class SidebarItemActionRendererTests(TestCase):
+    """Unit tests for SidebarItemActionRenderer.
+
+    Version Added:
+        7.1
+    """
+
+    def test_render(self) -> None:
+        """Testing SidebarItemActionRenderer.render"""
+        group_action = _MySidebarGroupAction()
+        item_action = _MySidebarItem()
+        placement = item_action.get_placement(AttachmentPoint.HEADER)
+
+        registry = TestActionsRegistry()
+        registry.register(group_action)
+        registry.register(item_action)
+
+        renderer = SidebarItemActionRenderer(action=item_action,
+                                             placement=placement)
+        request = self.create_http_request()
+        context = Context({
+            'request': request,
+        })
+
+        html = renderer.render(request=request,
+                               context=context)
+
+        self.assertIsInstance(html, SafeString)
+        self.assertHTMLEqual(
+            html,
+            """
+            <li class="rb-c-sidebar__nav-item"
+                id="action-header-test-sidebar-item">
+             <a class="rb-c-sidebar__item-label"
+                href="/dashboard/">
+              Item 1
+             </a>
+            </li>
+            """)
+
+    def test_render_with_current_url(self) -> None:
+        """Testing SidebarItemActionRenderer.render with URL as current page
+        """
+        group_action = _MySidebarGroupAction()
+        item_action = _MySidebarItem()
+        placement = item_action.get_placement(AttachmentPoint.HEADER)
+
+        registry = TestActionsRegistry()
+        registry.register(group_action)
+        registry.register(item_action)
+
+        renderer = SidebarItemActionRenderer(action=item_action,
+                                             placement=placement)
+        request = self.create_http_request(path='/dashboard/',
+                                           url_name='dashboard')
+        context = Context({
+            'request': request,
+        })
+
+        html = renderer.render(request=request,
+                               context=context)
+
+        self.assertIsInstance(html, SafeString)
+        self.assertHTMLEqual(
+            html,
+            """
+            <li class="rb-c-sidebar__nav-item"
+                id="action-header-test-sidebar-item"
+                aria-current="page">
+             <a class="rb-c-sidebar__item-label"
+                href="/dashboard/">
+              Item 1
+             </a>
+            </li>
+            """)
+
+    def test_render_js(self) -> None:
+        """Testing SidebarItemActionRenderer.render_js"""
+        group_action = _MySidebarGroupAction()
+        item_action = _MySidebarItem()
+        placement = item_action.get_placement(AttachmentPoint.HEADER)
+
+        registry = TestActionsRegistry()
+        registry.register(group_action)
+        registry.register(item_action)
+
+        renderer = SidebarItemActionRenderer(action=item_action,
+                                             placement=placement)
+        request = self.create_http_request()
+        context = Context({
+            'request': request,
+        })
+
+        js = renderer.render_js(request=request,
+                                context=context)
+
+        self.assertIsInstance(js, SafeString)
+        self.assertHTMLEqual(
+            js,
+            """
+            page.addActionView(new RB.Actions.ActionView({
+                "attachmentPointID": "header",
+                el: $('#action-header-test-sidebar-item'),
+                model: page.getAction("test-sidebar-item"),
+            }));
+            """)
diff --git a/reviewboard/static/rb/css/ui/sidebars.less b/reviewboard/static/rb/css/ui/sidebars.less
index 82d06942f05f883d8a62ba9761c5b44f4c1b7062..670dba61a57f0581b29108680869fc0f4d8acda2 100644
--- a/reviewboard/static/rb/css/ui/sidebars.less
+++ b/reviewboard/static/rb/css/ui/sidebars.less
@@ -65,12 +65,14 @@
         .rb-c-sidebar__nav-item,
         .rb-c-sidebar__nav-section-header {
           &:hover,
+          &[aria-current="page"],
           &.-is-active {
             border-top-right-radius: 0;
             border-bottom-right-radius: 0;
           }
 
           &:hover,
+          &[aria-current="page"],
           &.-is-active {
             /* Ensure the sidebar can overlay the border. */
             border-right: var(--ink-u-border-thin) transparent solid;
@@ -153,10 +155,10 @@
  *
  * Structure:
  *     <ul class="rb-c-sidebar__items">
- *      <li class="rb-c-sidebar__item">
- *       ...
- *      </li>
- *      ...
+ *      {
+ *       <li class="rb-c-sidebar__item">...</li>
+ *       <li class="rb-c-sidebar__section">...</li>
+ *      } [0+]
  *     </ul>
  */
 .rb-c-sidebar__items {
@@ -376,8 +378,21 @@
       display: list-item;
     });
   }
-}
 
+  /* Styling for subsections. */
+  & .rb-c-sidebar__section {
+    @_sidebars-vars: #rb-ns-ui.sidebars();
+
+    .rb-c-sidebar__items {
+      padding-left: @_sidebars-vars[@icon-max-size];
+    }
+
+    .rb-c-sidebar__section-header {
+      font-size: 100%;
+      text-transform: none;
+    }
+  }
+}
 
 /**
  * The header for a section.
@@ -453,6 +468,7 @@
   cursor: pointer;
 
   &:hover,
+  &[aria-current="page"],
   &.-is-active {
     @_border-radius: @_sidebars-vars[@active-border-radius];
 
@@ -463,6 +479,7 @@
     color: var(--ink-p-fg);
   }
 
+  &[aria-current="page"],
   &.-is-active {
     z-index: @z-index-deco;
   }
diff --git a/reviewboard/templates/actions/sidebar_group_action.html b/reviewboard/templates/actions/sidebar_group_action.html
new file mode 100644
index 0000000000000000000000000000000000000000..2a8666717b5b01931789cbdd3bcfe6f480e1b3ed
--- /dev/null
+++ b/reviewboard/templates/actions/sidebar_group_action.html
@@ -0,0 +1,24 @@
+{% extends "actions/group_action.html" %}
+{% load djblets_utils %}
+
+
+{% block action_container_tag %}li{% endblock %}
+{% block action_container_css_class %}rb-c-sidebar__section{% endblock %}
+{% block action_has_container %}true{% endblock %}
+
+
+{% block action_container_attrs %}
+{{block.super}}
+{{action_attrs}}
+ aria-labelledby="{{action.action_id}}__label"
+{% endblock action_container_attrs %}
+
+
+{% block action_content %}
+{%  definevar "sidebar_items" %}{{block.super}}{% enddefinevar %}
+<header class="rb-c-sidebar__section-header"
+        id="{{action.action_id}}__label">{{label}}</header>
+<ul class="rb-c-sidebar__items">
+{%  block action_sidebar_items %}{{sidebar_items}}{% endblock %}
+</ul>
+{% endblock action_content %}
diff --git a/reviewboard/templates/actions/sidebar_item_action.html b/reviewboard/templates/actions/sidebar_item_action.html
new file mode 100644
index 0000000000000000000000000000000000000000..cbd8801946b819c71e0eaeaa26346bef9c4cd805
--- /dev/null
+++ b/reviewboard/templates/actions/sidebar_item_action.html
@@ -0,0 +1,14 @@
+{% extends "actions/action_base.html" %}
+
+
+{% block action_content %}
+<li class="rb-c-sidebar__nav-item"
+    {% if is_active %}aria-current="page"{% endif %}
+    {{action_attrs}}>
+{%  block action_sidebar_item_content %}
+ <a class="rb-c-sidebar__item-label"{% if url and url != "#" %} href="{{url}}"{% endif %}>
+  {{label}}
+ </a>
+{%  endblock action_sidebar_item_content %}
+</li>
+{% endblock action_content %}
