diff --git a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php
index 0f756ef..8f42122 100644
--- a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php
+++ b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php
@@ -850,30 +850,33 @@ class InlineParagraphsWidget extends WidgetBase {
     $field_state['real_item_count'] = $this->realItemCount;
     static::setWidgetState($this->fieldParents, $field_name, $form_state, $field_state);
 
+    $elements += [
+      '#element_validate' => [[$this, 'multipleElementValidate']],
+      '#required' => $this->fieldDefinition->isRequired(),
+      '#field_name' => $field_name,
+      '#cardinality' => $cardinality,
+      '#max_delta' => $max - 1,
+    ];
+
     if ($this->realItemCount > 0) {
       $elements += array(
         '#theme' => 'field_multiple_value_form',
-        '#field_name' => $field_name,
-        '#cardinality' => $cardinality,
         '#cardinality_multiple' => $is_multiple,
-        '#required' => $this->fieldDefinition->isRequired(),
         '#title' => $title,
         '#description' => $description,
-        '#max_delta' => $max-1,
       );
     }
     else {
+      $classes = $this->fieldDefinition->isRequired() ? ['form-required'] : [];
       $elements += [
         '#type' => 'container',
         '#theme_wrappers' => ['container'],
-        '#field_name' => $field_name,
-        '#cardinality' => $cardinality,
         '#cardinality_multiple' => TRUE,
-        '#max_delta' => $max-1,
         'title' => [
           '#type' => 'html_tag',
           '#tag' => 'strong',
           '#value' => $title,
+          '#attributes' => ['class' => $classes],
         ],
         'text' => [
           '#type' => 'container',
@@ -1237,6 +1240,31 @@ class InlineParagraphsWidget extends WidgetBase {
   }
 
   /**
+   * Special handling to validate form elements with multiple values.
+   *
+   * @param array $elements
+   *   An associative array containing the substructure of the form to be
+   *   validated in this call.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $form
+   *   The complete form array.
+   */
+  public function multipleElementValidate(array $elements, FormStateInterface $form_state, array $form) {
+    $field_name = $this->fieldDefinition->getName();
+    $widget_state = static::getWidgetState($elements['#field_parents'], $field_name, $form_state);
+
+    $remove_mode_item_count = $this->getNumberOfParagraphsInMode($widget_state, 'remove');
+    $non_remove_mode_item_count = $widget_state['real_item_count'] - $remove_mode_item_count;
+
+    if ($elements['#required'] && $non_remove_mode_item_count < 1) {
+      $form_state->setError($elements, t('@name field is required.', ['@name' => $this->fieldDefinition->getLabel()]));
+    }
+
+    static::setWidgetState($elements['#field_parents'], $field_name, $form_state, $widget_state);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
@@ -1401,6 +1429,33 @@ class InlineParagraphsWidget extends WidgetBase {
   }
 
   /**
+   * Counts the number of paragraphs in a certain mode in a form substructure.
+   *
+   * @param array $widget_state
+   *   The widget state for the form substructure containing information about
+   *   the paragraphs within.
+   * @param string $mode
+   *   The mode to look for.
+   *
+   * @return int
+   *   The number of paragraphs is the given mode.
+   */
+  protected function getNumberOfParagraphsInMode(array $widget_state, $mode) {
+    if (!isset($widget_state['paragraphs'])) {
+      return 0;
+    }
+
+    $paragraphs_count = 0;
+    foreach ($widget_state['paragraphs'] as $paragraph) {
+      if ($paragraph['mode'] == $mode) {
+        $paragraphs_count++;
+      }
+    }
+
+    return $paragraphs_count;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public static function isApplicable(FieldDefinitionInterface $field_definition) {
diff --git a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
index d23abed..d61be57 100644
--- a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
+++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
@@ -894,30 +894,33 @@ class ParagraphsWidget extends WidgetBase {
     $field_state['real_item_count'] = $this->realItemCount;
     static::setWidgetState($this->fieldParents, $field_name, $form_state, $field_state);
 
+    $elements += [
+      '#element_validate' => [[$this, 'multipleElementValidate']],
+      '#required' => $this->fieldDefinition->isRequired(),
+      '#field_name' => $field_name,
+      '#cardinality' => $cardinality,
+      '#max_delta' => $max - 1,
+    ];
+
     if ($this->realItemCount > 0) {
       $elements += array(
         '#theme' => 'field_multiple_value_form',
-        '#field_name' => $field_name,
-        '#cardinality' => $cardinality,
         '#cardinality_multiple' => $is_multiple,
-        '#required' => $this->fieldDefinition->isRequired(),
         '#title' => $title,
         '#description' => $description,
-        '#max_delta' => $max-1,
       );
     }
     else {
+      $classes = $this->fieldDefinition->isRequired() ? ['form-required'] : [];
       $elements += [
         '#type' => 'container',
         '#theme_wrappers' => ['container'],
-        '#field_name' => $field_name,
-        '#cardinality' => $cardinality,
         '#cardinality_multiple' => TRUE,
-        '#max_delta' => $max-1,
         'title' => [
           '#type' => 'html_tag',
           '#tag' => 'strong',
           '#value' => $title,
+          '#attributes' => ['class' => $classes],
         ],
         'text' => [
           '#type' => 'container',
@@ -1290,6 +1293,31 @@ class ParagraphsWidget extends WidgetBase {
   }
 
   /**
+   * Special handling to validate form elements with multiple values.
+   *
+   * @param array $elements
+   *   An associative array containing the substructure of the form to be
+   *   validated in this call.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $form
+   *   The complete form array.
+   */
+  public function multipleElementValidate(array $elements, FormStateInterface $form_state, array $form) {
+    $field_name = $this->fieldDefinition->getName();
+    $widget_state = static::getWidgetState($elements['#field_parents'], $field_name, $form_state);
+
+    $remove_mode_item_count = $this->getNumberOfParagraphsInMode($widget_state, 'remove');
+    $non_remove_mode_item_count = $widget_state['real_item_count'] - $remove_mode_item_count;
+
+    if ($elements['#required'] && $non_remove_mode_item_count < 1) {
+      $form_state->setError($elements, t('@name field is required.', ['@name' => $this->fieldDefinition->getLabel()]));
+    }
+
+    static::setWidgetState($elements['#field_parents'], $field_name, $form_state, $widget_state);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
@@ -1469,6 +1497,33 @@ class ParagraphsWidget extends WidgetBase {
   }
 
   /**
+   * Counts the number of paragraphs in a certain mode in a form substructure.
+   *
+   * @param array $widget_state
+   *   The widget state for the form substructure containing information about
+   *   the paragraphs within.
+   * @param string $mode
+   *   The mode to look for.
+   *
+   * @return int
+   *   The number of paragraphs is the given mode.
+   */
+  protected function getNumberOfParagraphsInMode(array $widget_state, $mode) {
+    if (!isset($widget_state['paragraphs'])) {
+      return 0;
+    }
+
+    $paragraphs_count = 0;
+    foreach ($widget_state['paragraphs'] as $paragraph) {
+      if ($paragraph['mode'] == $mode) {
+        $paragraphs_count++;
+      }
+    }
+
+    return $paragraphs_count;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public static function isApplicable(FieldDefinitionInterface $field_definition) {
diff --git a/src/Tests/Classic/ParagraphsConfigTest.php b/src/Tests/Classic/ParagraphsConfigTest.php
index 8d2d79c..11ba142 100644
--- a/src/Tests/Classic/ParagraphsConfigTest.php
+++ b/src/Tests/Classic/ParagraphsConfigTest.php
@@ -157,7 +157,7 @@ class ParagraphsConfigTest extends ParagraphsTestBase {
       'title[0][value]' => 'test_title',
     ];
     $this->drupalPostForm(NULL, $edit, 'Save and publish');
-    $this->assertText('This value should not be null.');
+    $this->assertText('paragraphs field is required.');
     $this->drupalPostAjaxForm(NULL, [], 'paragraphs_paragraph_type_test_add_more');
     $this->drupalPostForm(NULL, $edit, 'Save and publish');
     $this->assertText('paragraphed_test test_title has been created.');
diff --git a/src/Tests/Classic/ParagraphsUiTest.php b/src/Tests/Classic/ParagraphsUiTest.php
new file mode 100644
index 0000000..1410b82
--- /dev/null
+++ b/src/Tests/Classic/ParagraphsUiTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\paragraphs\Tests\Classic;
+
+use Drupal\field_ui\Tests\FieldUiTestTrait;
+
+/**
+ * Tests the Paragraphs user interface.
+ *
+ * @group paragraphs
+ */
+class ParagraphsUiTest extends ParagraphsTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'paragraphs_demo',
+  ];
+
+  /**
+   * Tests displaying an error message a required paragraph field that is empty.
+   */
+  public function testEmptyRequiredField() {
+    $admin_user = $this->drupalCreateUser([
+      'administer node fields',
+      'administer paragraph form display',
+      'administer node form display',
+      'create paragraphed_content_demo content',
+      'edit any paragraphed_content_demo content',
+    ]);
+    $this->drupalLogin($admin_user);
+
+    // Add required field to paragraphed content type.
+    $bundle_path = 'admin/structure/types/manage/paragraphed_content_demo';
+    $field_name = 'content';
+    $field_title = 'Content Test';
+    $field_type = 'field_ui:entity_reference_revisions:paragraph';
+    $field_edit = [
+      'required' => TRUE,
+    ];
+    $this->fieldUIAddNewField($bundle_path, $field_name, $field_title, $field_type, [], $field_edit);
+
+    // Attempt to create a paragraphed node with an empty required field.
+    $title = 'Empty';
+    $this->drupalGet('node/add/paragraphed_content_demo');
+    $this->drupalPostForm(NULL, ['title[0][value]' => $title], t('Save'));
+    $this->assertText($field_title . ' field is required');
+
+    // Attempt to create a paragraphed node with only a paragraph in the
+    // "remove" mode in the required field.
+    $title = 'Remove mode';
+    $this->drupalGet('node/add/paragraphed_content_demo');
+    $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_image_text_add_more');
+    $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_0_remove');
+    $this->assertNoText($field_title . ' field is required');
+    $this->drupalPostForm(NULL, ['title[0][value]' => $title], t('Save'));
+    $this->assertText($field_title . ' field is required');
+  }
+
+}
diff --git a/src/Tests/Experimental/ParagraphsExperimentalUiTest.php b/src/Tests/Experimental/ParagraphsExperimentalUiTest.php
new file mode 100644
index 0000000..7a7695d
--- /dev/null
+++ b/src/Tests/Experimental/ParagraphsExperimentalUiTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\paragraphs\Tests\Experimental;
+
+use Drupal\field_ui\Tests\FieldUiTestTrait;
+
+/**
+ * Tests the Paragraphs user interface.
+ *
+ * @group paragraphs
+ */
+class ParagraphsExperimentalUiTest extends ParagraphsExperimentalTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'paragraphs_demo',
+  ];
+
+  /**
+   * Tests displaying an error message a required paragraph field that is empty.
+   */
+  public function testEmptyRequiredField() {
+    $admin_user = $this->drupalCreateUser([
+      'administer node fields',
+      'administer paragraph form display',
+      'administer node form display',
+      'create paragraphed_content_demo content',
+      'edit any paragraphed_content_demo content',
+    ]);
+    $this->drupalLogin($admin_user);
+
+    // Add required field to paragraphed content type.
+    $bundle_path = 'admin/structure/types/manage/paragraphed_content_demo';
+    $field_title = 'Content Test';
+    $field_type = 'field_ui:entity_reference_revisions:paragraph';
+    $field_edit = [
+      'required' => TRUE,
+    ];
+    $this->fieldUIAddNewField($bundle_path, 'content', $field_title, $field_type, [], $field_edit);
+
+    // Attempt to create a paragraphed node with an empty required field.
+    $title = 'Empty';
+    $this->drupalGet('node/add/paragraphed_content_demo');
+    $this->drupalPostForm(NULL, ['title[0][value]' => $title], t('Save'));
+    $this->assertText($field_title . ' field is required');
+
+    // Attempt to create a paragraphed node with only a paragraph in the
+    // "remove" mode in the required field.
+    $title = 'Remove mode';
+    $this->drupalGet('node/add/paragraphed_content_demo');
+    $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_image_text_add_more');
+    $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_0_remove');
+    $this->assertNoText($field_title . ' field is required');
+    $this->drupalPostForm(NULL, ['title[0][value]' => $title], t('Save'));
+    $this->assertText($field_title . ' field is required');
+  }
+
+}
