diff --git a/reviewboard/attachments/models.py b/reviewboard/attachments/models.py
index 7a1bbe3d16d39ddf580536759586c84fd8589bdd..ac7c4f8ba4d2ebbbb74b0e5dd2b663b37eca21a0 100644
--- a/reviewboard/attachments/models.py
+++ b/reviewboard/attachments/models.py
@@ -275,3 +275,31 @@ class FileAttachment(models.Model):
 
     class Meta:
         get_latest_by = 'attachment_revision'
+
+
+def get_latest_file_attachments(file_attachments):
+    """Filter the list of file attachments to only return the latest revisions.
+
+    Args:
+        file_attachments (list of
+        reviewboard.attachments.models.FileAttachment):
+            The file attachments to filter.
+
+    Returns:
+        list of reviewboard.attachments.models.FileAttachment:
+        The list of file attachments that are the latest revisions in their
+        respective histories.
+    """
+    file_attachment_histories = FileAttachmentHistory.objects.filter(
+        file_attachments__in=file_attachments)
+    latest = {
+        data['id']: data['latest_revision']
+        for data in file_attachment_histories.values('id', 'latest_revision')
+    }
+
+    return [
+        f
+        for f in file_attachments
+        if (not f.is_from_diff and
+            f.attachment_revision == latest[f.attachment_history_id])
+    ]
diff --git a/reviewboard/reviews/ui/base.py b/reviewboard/reviews/ui/base.py
index b8e59f5b25abe89bbb56ba0abcb9d6719393600b..a276ae767ba07ff5504f1fbeebb4c448beb458eb 100644
--- a/reviewboard/reviews/ui/base.py
+++ b/reviewboard/reviews/ui/base.py
@@ -15,7 +15,8 @@ from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext as _
 
 from reviewboard.attachments.mimetypes import MIMETYPE_EXTENSIONS, score_match
-from reviewboard.attachments.models import FileAttachment
+from reviewboard.attachments.models import (FileAttachment,
+                                            get_latest_file_attachments)
 from reviewboard.reviews.context import make_review_request_context
 from reviewboard.reviews.markdown_utils import (markdown_render_conditional,
                                                 normalize_text_for_edit)
@@ -144,6 +145,14 @@ class ReviewUI(object):
                 'review_ui_inline': False,
             })
 
+            prev_file_attachment, next_file_attachment = \
+                self._get_adjacent_file_attachments(review_request_details)
+
+            context.update({
+                'next_file_attachment': next_file_attachment,
+                'prev_file_attachment': prev_file_attachment,
+            })
+
         try:
             context.update(self.get_extra_context(request))
         except Exception as e:
@@ -314,6 +323,43 @@ class ReviewUI(object):
                 comment.issue_status),
         }
 
+    def _get_adjacent_file_attachments(self, review_request_details):
+        """Return the next and previous file attachments.
+
+        The next and previous file attachments are the file attachments that
+        occur before and after this one in the review request details view.
+
+        Args:
+            review_request_details (reviewboard.reviews.models.base_review_request_details.BaseReviewRequestDetails):
+                The review request or draft.
+
+        Returns:
+            tuple:
+            A 2-tuple of the previous and next file attachments, which will
+            either be ``None`` (if there isn't a previous or next file
+            attachment) or
+            :py:class:`~reviewboard.attachments.models.FileAttachment`
+            instances.
+        """
+        file_attachments = iter(get_latest_file_attachments(
+            review_request_details.get_file_attachments()))
+
+        prev_obj = None
+        next_obj = None
+
+        for obj in file_attachments:
+            if obj.pk == self.obj.pk:
+                break
+
+            prev_obj = obj
+
+        try:
+            next_obj = next(file_attachments)
+        except StopIteration:
+            pass
+
+        return prev_obj, next_obj
+
 
 class FileAttachmentReviewUI(ReviewUI):
     """Base class for Review UIs for file attachments.
diff --git a/reviewboard/reviews/views.py b/reviewboard/reviews/views.py
index ec145eb9cb6c15352e46e0d4ea03ae6d6242b395..405a26def187c9bb35ae3b112a46f00dcdead0c8 100644
--- a/reviewboard/reviews/views.py
+++ b/reviewboard/reviews/views.py
@@ -35,7 +35,7 @@ from reviewboard.accounts.decorators import (check_login_required,
                                              valid_prefs_required)
 from reviewboard.accounts.models import ReviewRequestVisit, Profile
 from reviewboard.attachments.models import (FileAttachment,
-                                            FileAttachmentHistory)
+                                            get_latest_file_attachments)
 from reviewboard.diffviewer.diffutils import (convert_to_unicode,
                                               get_file_chunks_in_range,
                                               get_last_header_before_line,
@@ -319,22 +319,6 @@ def new_review_request(request,
     }))
 
 
-def _get_latest_file_attachments(file_attachments):
-    file_attachment_histories = FileAttachmentHistory.objects.filter(
-        file_attachments__in=file_attachments)
-    latest = dict([
-        (data['id'], data['latest_revision'])
-        for data in file_attachment_histories.values('id', 'latest_revision')
-    ])
-
-    return [
-        f
-        for f in file_attachments
-        if (not f.is_from_diff and
-            f.attachment_revision == latest[f.attachment_history_id])
-    ]
-
-
 @check_login_required
 @check_local_site_access
 def review_detail(request,
@@ -504,7 +488,7 @@ def review_detail(request,
 
     # Time to render the page!
     file_attachments = \
-        _get_latest_file_attachments(data.active_file_attachments)
+        get_latest_file_attachments(data.active_file_attachments)
     social_page_image_url = _get_social_page_image_url(
         file_attachments)
 
@@ -636,8 +620,7 @@ class ReviewsDiffViewerView(DiffViewerView):
         file_attachments = list(review_request_details.get_file_attachments())
         screenshots = list(review_request_details.get_screenshots())
 
-        latest_file_attachments = \
-            _get_latest_file_attachments(file_attachments)
+        latest_file_attachments = get_latest_file_attachments(file_attachments)
         social_page_image_url = _get_social_page_image_url(
             latest_file_attachments)
 
diff --git a/reviewboard/static/rb/css/pages/reviews.less b/reviewboard/static/rb/css/pages/reviews.less
index 35dc72581e39a46a834e6e1423f94599bb0a0a40..e39a210b56b9d1c833afd7029f7a91f3981e0952 100644
--- a/reviewboard/static/rb/css/pages/reviews.less
+++ b/reviewboard/static/rb/css/pages/reviews.less
@@ -216,6 +216,59 @@
   }
 }
 
+.review-ui-next-attachment,
+.review-ui-prev-attachment {
+  background: @box-bg;
+  border: 1px solid @box-border-color;
+  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
+  padding: 2px;
+  position: fixed;
+  top: 50%;
+  transition: transform 0.3s, background 0.3s, margin 0.3s;
+  z-index: @z-index-base;
+
+  &:hover {
+    background: darken(@box-bg, 10%);
+    transform: translateY(-50%);
+  }
+
+  a {
+    color: black;
+    font-size: 1.5em;
+    text-decoration: none;
+  }
+
+  .file-container {
+    display: inline-block;
+    float: none;
+    vertical-align: middle;
+  }
+}
+
+
+.review-ui-next-attachment {
+  border-right-width: 0;
+  border-radius: @box-border-radius 0 0 @box-border-radius;
+  margin-right: @page-container-padding;
+  right: -2px;
+  transform: translateY(-50%) translateX(100%);
+
+  &:hover {
+    margin-right: 0;
+  }
+}
+
+.review-ui-prev-attachment {
+  border-left-width: 0;
+  border-radius: 0 @box-border-radius @box-border-radius 0;
+  margin-left: @page-container-padding;
+  left: -2px;
+  transform: translateY(-50%) translateX(-100%);
+
+  &:hover {
+    margin-left: 0;
+  }
+}
 
 /****************************************************************************
  * Entries (status updates, reviews, change descriptions)
diff --git a/reviewboard/templates/reviews/ui/base.html b/reviewboard/templates/reviews/ui/base.html
index db483d5fbfd10028e034d3fc7ee4cbd1f8ec2917..dc05c0e2838c392efb77a8e760f1711b34a7c9b6 100644
--- a/reviewboard/templates/reviews/ui/base.html
+++ b/reviewboard/templates/reviews/ui/base.html
@@ -13,6 +13,42 @@
 {% block content %}
 {%  definevar "review_ui_box_content" %}{% block review_ui_box_content %}{% endblock %}{% enddefinevar %}
 
+{%  if prev_file_attachment %}
+<div class="review-ui-prev-attachment">
+ <a href="{% url 'file-attachment' review_request_id=review_request.display_id file_attachment_id=prev_file_attachment.pk %}">
+  <div class="file-container">
+   <div class="file">
+    <div class="file-thumbnail-container">
+     <div class="file-thumbnail">{{prev_file_attachment.thumbnail}}</div>
+    </div>
+    <div class="file-caption-container">
+     <div class="file-caption">{{prev_file_attachment}}</div>
+    </div>
+   </div>
+  </div>
+  <span class="fa fa-chevron-left" aria-hidden="true"></span>
+ </a>
+</div>
+{%  endif %}
+
+{%  if next_file_attachment %}
+<div class="review-ui-next-attachment">
+ <a href="{% url 'file-attachment' review_request_id=review_request.display_id file_attachment_id=next_file_attachment.pk %}">
+  <span class="fa fa-chevron-right" aria-hidden="true"></span>
+  <div class="file-container">
+   <div class="file">
+    <div class="file-thumbnail-container">
+     <div class="file-thumbnail">{{next_file_attachment.thumbnail}}</div>
+    </div>
+    <div class="file-caption-container">
+     <div class="file-caption">{{next_file_attachment}}</div>
+    </div>
+   </div>
+  </div>
+ </a>
+</div>
+{%  endif %}
+
 <div id="review-request" class="review-ui-box{% if review_ui_box_content %} has-review-ui-box-content{% endif %}">
 {%  include "reviews/trophy_box.html" %}
  <div id="review-request-banners"></div>
