diff --git a/focal_point.module b/focal_point.module
index 5557735..1b123f7 100644
--- a/focal_point.module
+++ b/focal_point.module
@@ -42,8 +42,8 @@ function focal_point_entity_presave(EntityInterface $entity) {
       if ($value->getType() == 'image' && $entity->hasField($value->getName())) {
         // Loop through all values for this field. Its cardinality might be > 1.
         foreach ($entity->{$value->getName()} as $item) {
-          if (isset($item->focal_point)) {
-            list($x, $y) = explode(',', $item->focal_point);
+          if (isset($item->preview['focal_point'])) {
+            list($x, $y) = explode(',', $item->preview['focal_point']);
             $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
             $crop = \Drupal::service('focal_point.manager')->getCropEntity($item->entity, $crop_type);
             \Drupal::service('focal_point.manager')->saveCropEntity($x, $y, $item->width, $item->height, $crop);
diff --git a/src/Controller/FocalPointPreviewController.php b/src/Controller/FocalPointPreviewController.php
index ceefc78..0ff4d57 100644
--- a/src/Controller/FocalPointPreviewController.php
+++ b/src/Controller/FocalPointPreviewController.php
@@ -3,6 +3,7 @@
 namespace Drupal\focal_point\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\focal_point\Element\FocalPoint;
 use Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget;
 use Drupal\image\Entity\ImageStyle;
 use Drupal\file\Entity\File;
@@ -199,7 +200,7 @@ class FocalPointPreviewController extends ControllerBase {
   protected function validTokenProvided() {
     try {
       $token = \Drupal::request()->query->get('focal_point_token');
-      return FocalPointImageWidget::validatePreviewToken($token);
+      return FocalPoint::validatePreviewToken($token);
     }
     catch (\InvalidArgumentException $e) {
       return FALSE;
diff --git a/src/Element/FocalPoint.php b/src/Element/FocalPoint.php
new file mode 100644
index 0000000..1f83f17
--- /dev/null
+++ b/src/Element/FocalPoint.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Drupal\focal_point\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\FormElement;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+
+/**
+ * Focal point render element.
+ *
+ * @RenderElement("focal_point")
+ */
+class FocalPoint extends FormElement {
+
+  const PREVIEW_TOKEN_NAME = 'focal_point_preview';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return [
+      '#selection_element' => NULL,
+      '#process' => [[$class, 'processFocalPoint']],
+      '#default_value' => NULL,
+      '#file_id' => NULL,
+      '#display_preview' => TRUE,
+    ];
+  }
+
+  /**
+   * Creates the focal point element.
+   *
+   * @param array $element
+   *   The render element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   * @param array $complete_form
+   *   Complete form.
+   *
+   * @return array
+   *   The render element.
+   */
+  public static function processFocalPoint(&$element, FormStateInterface $form_state, &$complete_form) {
+
+    $element_selector = 'focal-point-' . implode('-', $element['#parents']);
+
+    $previewLink = [];
+    // Even for image fields with a cardinality higher than 1 the correct fid
+    // can always be found in $item['fids'][0].
+    if ($element['#display_preview'] && !empty($element['#file_id'])) {
+      // Replace comma (,) with an x to make javascript handling easier.
+      $preview_focal_point_value = str_replace(',', 'x', $element['#default_value']);
+
+      // Create a token to be used during an access check on the preview page.
+      $token = self::getPreviewToken();
+
+      $previewLink = [
+        '#type' => 'link',
+        '#title' => t('Preview'),
+        '#url' => new Url('focal_point.preview',
+          [
+            'fid' => $element['#file_id'],
+            'focal_point_value' => $preview_focal_point_value,
+          ],
+          [
+            'query' => array('focal_point_token' => $token),
+          ]),
+        '#attributes' => [
+          'class' => array('focal-point-preview-link'),
+          'data-selector' => $element_selector,
+          'target' => '_blank',
+        ],
+      ];
+    }
+
+    $focalPointElement = array(
+      'indicator' => array(
+        '#theme_wrappers' => array('container'),
+        '#attributes' => array(
+          'class' => array('focal-point-indicator'),
+          'data-selector' => $element_selector,
+        ),
+        '#markup' => '',
+        '#weight' => $element['#selection_element']['#weight'] - 1,
+
+      ),
+      'selection_element' => $element['#selection_element'],
+      'preview_link' => $previewLink,
+      'focal_point' => array(
+        '#type' => 'textfield',
+        '#title' => 'Focal point',
+        '#description' => new TranslatableMarkup('Specify the focus of this image in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75'),
+        '#default_value' => $element['#default_value'],
+        '#element_validate' => array('\Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget::validateFocalPoint'),
+        '#attributes' => array(
+          'class' => array('focal-point', $element_selector),
+          'data-selector' => $element_selector,
+        ),
+        '#attached' => array(
+          'library' => array('focal_point/drupal.focal_point'),
+        ),
+      ),
+    );
+
+    $element += $focalPointElement;
+
+    return $element;
+  }
+
+  /**
+   * Create and return a token to use for accessing the preview page.
+   *
+   * @return string
+   *   A valid token.
+   *
+   * @codeCoverageIgnore
+   */
+  public static function getPreviewToken() {
+    return \Drupal::csrfToken()->get(self::PREVIEW_TOKEN_NAME);
+  }
+
+  /**
+   * Validate a preview token.
+   *
+   * @param string $token
+   *   A drupal generated token.
+   *
+   * @return bool
+   *   True if the token is valid.
+   *
+   * @codeCoverageIgnore
+   */
+  public static function validatePreviewToken($token) {
+    return \Drupal::csrfToken()->validate($token, self::PREVIEW_TOKEN_NAME);
+  }
+
+}
diff --git a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
index 3dae58c..0ef0aae 100644
--- a/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
+++ b/src/Plugin/Field/FieldWidget/FocalPointImageWidget.php
@@ -6,7 +6,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\crop\Entity\Crop;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
-use Drupal\Core\Url;
 
 /**
  * Plugin implementation of the 'image_fp' widget.
@@ -19,8 +18,6 @@ use Drupal\Core\Url;
  */
 class FocalPointImageWidget extends ImageWidget {
 
-  const PREVIEW_TOKEN_NAME = 'focal_point_preview';
-
   /**
    * {@inheritdoc}
    *
@@ -35,87 +32,27 @@ class FocalPointImageWidget extends ImageWidget {
 
     $item = $element['#value'];
     $item['fids'] = $element['fids']['#value'];
-    $element_selector = 'focal-point-' . implode('-', $element['#parents']);
 
-    $default_focal_point_value = isset($item['focal_point']) ? $item['focal_point'] : \Drupal::config('focal_point.settings')->get('default_value');
+    $default_focal_point_value = isset($item['focal_point']) ? $item['focal_point'] : \Drupal::config('focal_point.settings')
+      ->get('default_value');
+    $fid = isset($item['fids'][0]) ? $item['fids'][0] : '';
+
+    $display_preview_link = \Drupal::config('focal_point.preview')
+      ->get('display_link');
 
-    // Add the focal point indicator to preview.
+    // Replace preview by focal_point element.
     if (isset($element['preview'])) {
-      $indicator = array(
-        '#theme_wrappers' => array('container'),
-        '#attributes' => array(
-          'class' => array('focal-point-indicator'),
-          'data-selector' => $element_selector,
-          'data-delta' => $element['#delta'],
-        ),
-        '#markup' => '',
-      );
 
-      $preview = array(
-        'indicator' => $indicator,
-        'thumbnail' => $element['preview'],
+      $element['preview'] = array(
+        '#type' => 'focal_point',
+        '#selection_element' => $element['preview'],
+        '#default_value' => $default_focal_point_value,
+        '#file_id' => $fid,
+        '#weight' => isset($element['preview']['#weight']) ? $element['preview']['#weight'] : 0,
+        '#display_preview' => $display_preview_link,
       );
-
-      $display_preview_link = \Drupal::config('focal_point.preview')->get('display_link');
-
-      // Even for image fields with a cardinality higher than 1 the correct fid
-      // can always be found in $item['fids'][0].
-      $fid = isset($item['fids'][0]) ? $item['fids'][0] : '';
-      if ($display_preview_link && !empty($fid)) {
-        // Replace comma (,) with an x to make javascript handling easier.
-        $preview_focal_point_value = str_replace(',', 'x', $default_focal_point_value);
-
-        // Create a token to be used during an access check on the preview page.
-        $token = self::getPreviewToken();
-
-        $preview_link = [
-          '#type' => 'link',
-          '#title' => t('Preview'),
-          '#url' => new Url('focal_point.preview',
-            [
-              'fid' => $fid,
-              'focal_point_value' => $preview_focal_point_value,
-            ],
-            [
-              'query' => array('focal_point_token' => $token),
-            ]),
-          '#attributes' => [
-            'class' => array('focal-point-preview-link'),
-            'data-selector' => $element_selector,
-            'data-field-name' => $element['#field_name'],
-            'target' => '_blank',
-          ],
-        ];
-
-        $preview['preview_link'] = $preview_link;
-      }
-
-      // Use the existing preview weight value so that the focal point indicator
-      // and thumbnail appear in the correct order.
-      $preview['#weight'] = isset($element['preview']['#weight']) ? $element['preview']['#weight'] : 0;
-      unset($preview['thumbnail']['#weight']);
-
-      $element['preview'] = $preview;
     }
 
-    // Add the focal point field.
-    $element_selector = 'focal-point-' . implode('-', $element['#parents']);
-    $element['focal_point'] = array(
-      '#type' => 'textfield',
-      '#title' => 'Focal point',
-      '#description' => new TranslatableMarkup('Specify the focus of this image in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75'),
-      '#default_value' => $default_focal_point_value,
-      '#element_validate' => array('\Drupal\focal_point\Plugin\Field\FieldWidget\FocalPointImageWidget::validateFocalPoint'),
-      '#attributes' => array(
-        'class' => array('focal-point', $element_selector),
-        'data-selector' => $element_selector,
-        'data-field-name' => $element['#field_name'],
-      ),
-      '#attached' => array(
-        'library' => array('focal_point/drupal.focal_point'),
-      ),
-    );
-
     return $element;
   }
 
@@ -146,8 +83,10 @@ class FocalPointImageWidget extends ImageWidget {
         }
       }
       else {
-        \Drupal::logger('focal_point')->notice("Attempted to get a focal point value for an invalid or temporary file.");
-        $return['focal_point'] = \Drupal::config('focal_point.settings')->get('default_value');
+        \Drupal::logger('focal_point')
+          ->notice("Attempted to get a focal point value for an invalid or temporary file.");
+        $return['focal_point'] = \Drupal::config('focal_point.settings')
+          ->get('default_value');
       }
     }
     return $return;
@@ -159,37 +98,12 @@ class FocalPointImageWidget extends ImageWidget {
    * Validate callback for the focal point field.
    */
   public static function validateFocalPoint($element, FormStateInterface $form_state) {
-    if (empty($element['#value']) || (FALSE === \Drupal::service('focal_point.manager')->validateFocalPoint($element['#value']))) {
+    if (empty($element['#value']) || (FALSE === \Drupal::service('focal_point.manager')
+          ->validateFocalPoint($element['#value']))
+    ) {
       $replacements = ['@title' => strtolower($element['#title'])];
       $form_state->setError($element, new TranslatableMarkup('The @title field should be in the form "leftoffset,topoffset" where offsets are in percentages. Ex: 25,75.', $replacements));
     }
   }
 
-  /**
-   * Create and return a token to use for accessing the preview page.
-   *
-   * @return string
-   *   A valid token.
-   *
-   * @codeCoverageIgnore
-   */
-  public static function getPreviewToken() {
-    return \Drupal::csrfToken()->get(self::PREVIEW_TOKEN_NAME);
-  }
-
-  /**
-   * Validate a preview token.
-   *
-   * @param string $token
-   *   A drupal generated token.
-   *
-   * @return bool
-   *   True if the token is valid.
-   *
-   * @codeCoverageIgnore
-   */
-  public static function validatePreviewToken($token) {
-    return \Drupal::csrfToken()->validate($token, self::PREVIEW_TOKEN_NAME);
-  }
-
 }
diff --git a/tests/src/Unit/FocalPointUnitTestCase.php b/tests/src/Unit/FocalPointUnitTestCase.php
index eee7db9..4564027 100644
--- a/tests/src/Unit/FocalPointUnitTestCase.php
+++ b/tests/src/Unit/FocalPointUnitTestCase.php
@@ -15,7 +15,6 @@ use Drupal\Core\Config\ImmutableConfig;
 use Drupal\focal_point\FocalPointEffectBase;
 
 /**
- * @group Focal Point
  */
 abstract class FocalPointUnitTestCase extends UnitTestCase {
 
