diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..8318fe435b3911f5ca44d8b4597e8e675a1ab65d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/requires_input.js.coffee
@@ -0,0 +1,39 @@
+# Requires Input behavior
+#
+# When called on a form with input fields with the `required` attribute, the
+# form's submit button will be disabled until all required fields have values.
+#
+#= require extensions/jquery
+#
+# ### Example Markup
+#
+#   <form class="js-requires-input">
+#     <input type="text" required="required">
+#     <input type="submit" value="Submit">
+#   </form>
+#
+$.fn.requiresInput = ->
+  $form   = $(this)
+  $button = $('button[type=submit], input[type=submit]', $form)
+
+  required      = '[required=required]'
+  fieldSelector = "input#{required}, select#{required}, textarea#{required}"
+
+  requireInput = ->
+    # Collect the input values of *all* required fields
+    values = _.map $(fieldSelector, $form), (field) -> field.value
+
+    # Disable the button if any required fields are empty
+    if values.length && _.any(values, _.isEmpty)
+      $button.disable()
+    else
+      $button.enable()
+
+  # Set initial button state
+  requireInput()
+
+  $form.on 'change input', fieldSelector, requireInput
+
+# Triggered on standard document `ready` and on Turbolinks `page:load` events
+$(document).on 'ready page:load', ->
+  $('form.js-requires-input').requiresInput()
diff --git a/spec/javascripts/behaviors/requires_input_spec.js.coffee b/spec/javascripts/behaviors/requires_input_spec.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..61a176321739d59fa916777fb8e6d767ed9e382b
--- /dev/null
+++ b/spec/javascripts/behaviors/requires_input_spec.js.coffee
@@ -0,0 +1,49 @@
+#= require behaviors/requires_input
+
+describe 'requiresInput', ->
+  fixture.preload('behaviors/requires_input.html')
+
+  beforeEach ->
+    fixture.load('behaviors/requires_input.html')
+
+  it 'disables submit when any field is required', ->
+    $('.js-requires-input').requiresInput()
+
+    expect($('.submit')).toBeDisabled()
+
+  it 'enables submit when no field is required', ->
+    $('*[required=required]').removeAttr('required')
+
+    $('.js-requires-input').requiresInput()
+
+    expect($('.submit')).not.toBeDisabled()
+
+  it 'enables submit when all required fields are pre-filled', ->
+    $('*[required=required]').remove()
+
+    $('.js-requires-input').requiresInput()
+
+    expect($('.submit')).not.toBeDisabled()
+
+  it 'enables submit when all required fields receive input', ->
+    $('.js-requires-input').requiresInput()
+
+    $('#required1').val('input1').change()
+    expect($('.submit')).toBeDisabled()
+
+    $('#optional1').val('input1').change()
+    expect($('.submit')).toBeDisabled()
+
+    $('#required2').val('input2').change()
+    $('#required3').val('input3').change()
+    $('#required4').val('input4').change()
+    $('#required5').val('1').change()
+
+    expect($('.submit')).not.toBeDisabled()
+
+  it 'is called on page:load event', ->
+    spy = spyOn($.fn, 'requiresInput')
+
+    $(document).trigger('page:load')
+
+    expect(spy).toHaveBeenCalled()
diff --git a/spec/javascripts/fixtures/behaviors/requires_input.html.haml b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c3f905e912ebd81b34ef9d2f8501fce9510e4eaa
--- /dev/null
+++ b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
@@ -0,0 +1,18 @@
+%form.js-requires-input
+  %input{type: 'text', id: 'required1', required: 'required'}
+  %input{type: 'text', id: 'required2', required: 'required'}
+  %input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
+  %input{type: 'text', id: 'optional1'}
+
+  %textarea{id: 'required4', required: 'required'}
+  %textarea{id: 'optional2'}
+
+  %select{id: 'required5', required: 'required'}
+    %option Zero
+    %option{value: '1'} One
+  %select{id: 'optional3', required: 'required'}
+    %option Zero
+    %option{value: '1'} One
+
+  %button.submit{type: 'submit', value: 'Submit'}
+  %input.submit{type: 'submit', value: 'Submit'}