diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2526e5e33a5671ce10839dee46f9e68fd58cd6e3
--- /dev/null
+++ b/spec/javascripts/fixtures/gl_field_errors.html.haml
@@ -0,0 +1,15 @@
+%form.show-gl-field-errors{action: 'submit', method: 'post'}
+  .form-group
+    %input.required-text{required: true, type: 'text'} Text
+  .form-group
+    %input.email{type: 'email', title: 'Please provide a valid email address.', required: true } Email
+  .form-group
+    %input.password{type: 'password', required: true} Password
+  .form-group
+    %input.alphanumeric{type: 'text', pattern: '[a-zA-Z0-9]', required: true} Alphanumeric
+  .form-group
+    %input.hidden{ type:'hidden' }
+  .form-group
+    %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate
+  .form-group
+  %input.submit{type: 'submit'} Submit
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..36feb2b2aa59cd4e91f808d3073c0f6ca42102df
--- /dev/null
+++ b/spec/javascripts/gl_field_errors_spec.js.es6
@@ -0,0 +1,111 @@
+//= require jquery
+//= require gl_field_errors
+
+((global) => {
+  fixture.preload('gl_field_errors.html');
+
+  describe('GL Style Field Errors', function() {
+    beforeEach(function() {
+      fixture.load('gl_field_errors.html');
+      const $form = this.$form = $('form.show-gl-field-errors');
+      this.fieldErrors = new global.GlFieldErrors($form);
+    });
+
+    it('should properly initialize the form', function() {
+      expect(this.$form).toBeDefined();
+      expect(this.$form.length).toBe(1);
+      expect(this.fieldErrors).toBeDefined();
+      const inputs = this.fieldErrors.state.inputs;
+      expect(inputs.length).toBe(5);
+    });
+
+    it('should ignore elements with custom error handling', function() {
+      const customErrorFlag = 'no-gl-field-errors';
+      const customErrorElem = $(`.${customErrorFlag}`);
+
+      expect(customErrorElem.length).toBe(1);
+
+      const customErrors = this.fieldErrors.state.inputs.filter((input) => {
+       return input.inputElement.hasClass(customErrorFlag);
+      });
+      expect(customErrors.length).toBe(0);
+    });
+
+    it('should not show any errors before submit attempt', function() {
+      this.$form.find('.email').val('not-a-valid-email').keyup();
+      this.$form.find('.text-required').val('').keyup();
+      this.$form.find('.alphanumberic').val('?---*').keyup();
+
+      const errorsShown = this.$form.find('.gl-field-error-outline');
+      expect(errorsShown.length).toBe(0);
+    });
+
+    it('should show errors when input valid is submitted', function() {
+      this.$form.find('.email').val('not-a-valid-email').keyup();
+      this.$form.find('.text-required').val('').keyup();
+      this.$form.find('.alphanumberic').val('?---*').keyup();
+
+      this.$form.submit();
+
+      const errorsShown = this.$form.find('.gl-field-error-outline');
+      expect(errorsShown.length).toBe(4);
+    });
+
+    it('should properly track validity state on input after invalid submission attempt', function() {
+      this.$form.submit();
+
+      const emailInputModel = this.fieldErrors.state.inputs[1];
+      const fieldState = emailInputModel.state;
+      const emailInputElement = emailInputModel.inputElement;
+
+      // No input
+      expect(emailInputElement).toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(true);
+      expect(fieldState.valid).toBe(false);
+
+      // Then invalid input
+      emailInputElement.val('not-a-valid-email').keyup();
+      expect(emailInputElement).toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(false);
+      expect(fieldState.valid).toBe(false);
+
+      // Then valid input
+      emailInputElement.val('email@gitlab.com').keyup();
+      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(false);
+      expect(fieldState.valid).toBe(true);
+
+      // Then invalid input
+      emailInputElement.val('not-a-valid-email').keyup();
+      expect(emailInputElement).toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(false);
+      expect(fieldState.valid).toBe(false);
+
+      // Then empty input
+      emailInputElement.val('').keyup();
+      expect(emailInputElement).toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(true);
+      expect(fieldState.valid).toBe(false);
+
+      // Then valid input
+      emailInputElement.val('email@gitlab.com').keyup();
+      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
+      expect(fieldState.empty).toBe(false);
+      expect(fieldState.valid).toBe(true);
+    });
+
+    it('should properly infer error messages', function() {
+      this.$form.submit();
+      const trackedInputs = this.fieldErrors.state.inputs;
+      const inputHasTitle = trackedInputs[1];
+      const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error');
+      const inputNoTitle = trackedInputs[2];
+      const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error');
+
+      expect(noTitleErrorElem.text()).toBe('This field is required.');
+      expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
+    });
+
+  });
+
+})(window.gl || (window.gl = {}));