diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
index ee58bbbfbadf224402dc4917f90ff623c82a85b3..e8aec6e0fd2cb2d29f6368862c979e38a626af38 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -54,31 +54,7 @@
                         {% endif %}
                     </h6>
 
-                    <h3>Proposal Information</h3>
-                    <div class="grid grid--proposal-info">
-                        <div>
-                            <h5>Requested Funding</h5>
-                            <p>{{ object.value }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Project Duration</h5>
-                            <p>{{ object.value }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Legal Name</h5>
-                            <p>{{ object.full_name }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Email</h5>
-                            <p>{{ object.email }}</p>
-                        </div>
-                    </div>
-                    <div class="rich-text rich-text--answers">
-                        {{ object.render_answers }}
-                    </div>
+                    {% include "funds/includes/rendered_answers.html" %}
                 </div>
             {% endif %}
 
diff --git a/opentech/apply/funds/templates/funds/includes/rendered_answers.html b/opentech/apply/funds/templates/funds/includes/rendered_answers.html
new file mode 100644
index 0000000000000000000000000000000000000000..e5b7f88b2266a90a71dc67d714ee0dbfa40e05b9
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/includes/rendered_answers.html
@@ -0,0 +1,26 @@
+{% load workflow_tags %}
+<h3>Proposal Information</h3>
+<div class="grid grid--proposal-info">
+    <div>
+        <h5>Requested Funding</h5>
+        <p>{{ object.value }}</p>
+    </div>
+
+    <div>
+        <h5>Project Duration</h5>
+        <p>{{ object.value }}</p>
+    </div>
+
+    <div>
+        <h5>Legal Name</h5>
+        <p>{{ object.full_name }}</p>
+    </div>
+
+    <div>
+        <h5>Email</h5>
+        <p>{{ object.email }}</p>
+    </div>
+</div>
+<div class="rich-text rich-text--answers">
+    {{ object.render_answers }}
+</div>
diff --git a/opentech/apply/funds/templates/funds/revisions_compare.html b/opentech/apply/funds/templates/funds/revisions_compare.html
new file mode 100644
index 0000000000000000000000000000000000000000..8b07bf11c95192a6acf639bed11ac52131938ae7
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/revisions_compare.html
@@ -0,0 +1,13 @@
+{% extends "base-apply.html" %}
+{% block content %}
+<div class="wrapper wrapper--breakout wrapper--admin">
+    <div class="wrapper wrapper--medium">
+        <h2 class="heading heading--no-margin">Comparing Revisions</h2>
+        <h5>For <a href="{% url "funds:submission" submission.id %}">{{ submission.title }}</a></h5>
+    </div>
+</div>
+
+<div class="wrapper wrapper--medium wrapper--tabs">
+    {% include "funds/includes/rendered_answers.html" with object=diff %}
+</div>
+{% endblock %}
diff --git a/opentech/apply/funds/templates/funds/revisions_list.html b/opentech/apply/funds/templates/funds/revisions_list.html
index ff39b209942ed67ef14ddfee7dd601ada431c436..e4ba90161be5948ab61c37bc108e918de6ada55b 100644
--- a/opentech/apply/funds/templates/funds/revisions_list.html
+++ b/opentech/apply/funds/templates/funds/revisions_list.html
@@ -8,13 +8,15 @@
 </div>
 
 <div class="wrapper wrapper--medium wrapper--tabs">
+    {% with base_revision=revisions.0.id %}
     {% for revision in revisions %}
         <div>
             {{ revision.date }} by {{ revision.author }}
             {% if not forloop.first %}
-                <a class="button button--primary" href="#">Compare</a>
+                <a class="button button--primary" href="{% url 'apply:revisions:compare' submission_pk=submission.id from=revision.id to=base_revision %}">Compare</a>
             {% endif %}
         </div>
     {% endfor %}
+    {% endwith %}
 </div>
 {% endblock %}
diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py
index 60bc05307ba9eaa5254a1d560726950dbc287c0f..ede94849cb543a6a8dfc73317ba83edb50d4fefa 100644
--- a/opentech/apply/funds/urls.py
+++ b/opentech/apply/funds/urls.py
@@ -1,6 +1,7 @@
 from django.urls import include, path
 
 from .views import (
+    RevisionCompareView,
     RevisionListView,
     SubmissionDetailView,
     SubmissionEditView,
@@ -10,7 +11,8 @@ from .views import (
 
 
 revision_urls = ([
-    path('', RevisionListView.as_view(), name='list')
+    path('', RevisionListView.as_view(), name='list'),
+    path('compare/<int:to>/<int:from>', RevisionCompareView.as_view(), name='compare'),
 ], 'revisions')
 
 
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index 457164ae291ff6fe66b1d1f591f54f7caae38c40..7eb7feb7308011b46ee8de20d91f4ad12532e35b 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -1,3 +1,5 @@
+from difflib import SequenceMatcher
+
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import PermissionDenied
 from django.http import HttpResponseRedirect
@@ -257,3 +259,43 @@ class RevisionListView(ListView):
             submission=self.submission,
             **kwargs,
         )
+
+
+class RevisionCompareView(TemplateView):
+    template_name = 'funds/revisions_compare.html'
+
+    def compare_answer(self, answer_a, answer_b):
+        if not answer_a and not answer_b:
+            return answer_b
+        diff = SequenceMatcher(answer_a, answer_b)
+        output = []
+        for opcode, a0, a1, b0, b1 in diff.get_opcodes():
+            if opcode == 'equal':
+                output.append(diff.a[a0:a1])
+            elif opcode == 'insert':
+                output.append('<span class="added">' + diff.b[b0:b1] + '</span>')
+            elif opcode == 'delete':
+                output.append('<span class="deleted">' + diff.a[a0:a1] + "</span>")
+            elif opcode == 'replace':
+                raise NotImplementedError("what to do with 'replace' opcode?")
+            else:
+                raise RuntimeError("unexpected opcode")
+        return ''.join(output)
+
+    def compare(self, from_data, to_data):
+        diffed_form_data = {
+            field: self.compare_answer(from_data.form_data.get(field), to_data.form_data[field])
+            for field in to_data.form_data
+        }
+        to_data.form_data = diffed_form_data
+        return to_data
+
+    def get_context_data(self, **kwargs):
+        from_revision = ApplicationSubmission.objects.get(id=self.kwargs['from'])
+        to_revision = ApplicationSubmission.objects.get(id=self.kwargs['to'])
+        diff = self.compare(from_revision, to_revision)
+        return super().get_context_data(
+            submission = ApplicationSubmission.objects.get(id=self.kwargs['submission_pk']),
+            diff=diff,
+            **kwargs,
+        )