diff --git a/css/layout-paragraphs-builder.css b/css/layout-paragraphs-builder.css
index 99cf51a70a72dfc0e85b20e13feaf9b113d11cd2..9209ddd08bd752444a919ff4287eb283be69ed08 100644
--- a/css/layout-paragraphs-builder.css
+++ b/css/layout-paragraphs-builder.css
@@ -209,6 +209,9 @@
   transform: translateX(-50%);
   transition: all .15s linear;
   z-index: 1000;
+}
+.lpb-component .lpb-btn--add,
+.lpb-region .lpb-btn--add {
   opacity: 0;
 }
 .lpb-btn--add:focus {
diff --git a/img/.DS_Store b/img/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6
Binary files /dev/null and b/img/.DS_Store differ
diff --git a/js/layout-paragraphs-builder.js b/js/layout-paragraphs-builder.js
index b3c65a2bcf5524e2a15b9972e11ec6f069756185..6c6e85b6b8df78104698227a5dcfc8455db2c266 100644
--- a/js/layout-paragraphs-builder.js
+++ b/js/layout-paragraphs-builder.js
@@ -1,5 +1,5 @@
 (($, Drupal, debounce, drupalSettings, dragula) => {
-  const idAttr = 'data-lp-builder-id';
+  const idAttr = 'data-lpb-id';
   const reorderComponents = debounce($element => {
     const id = $element.attr(idAttr);
     const order = $('.lpb-component', $element)
@@ -206,16 +206,19 @@
   Drupal.behaviors.layoutParagraphsBuilder = {
     attach: function attach(context, settings) {
       // Run only once - initialize the editor ui.
-      $('[data-lp-builder-id]', context)
-        .once('lp-builder')
+      $(`.has-components[${idAttr}]`)
+        .once('lpb-enabled')
         .each((index, element) => {
           const $element = $(element);
           const id = $element.attr(idAttr);
           const lpbSettings = settings.lpBuilder[id];
-          const dragContainers = $element
-            .find('.lpb-components, .lpb-region')
-            .get();
-          const drake = dragula(dragContainers, {
+          const drake = dragula({
+            isContainer: el => {
+              console.log(el, $element);
+              return el.classList.contains('lpb-component-list') ||
+              el.classList.contains('lpb-region');
+            },
+
             accepts(el, target, source, sibling) {
               // Returns false if any registered validator returns a value.
               // @see addMoveValidator()
@@ -270,7 +273,7 @@
       // Run every time the behavior is attached.
       if (context.classList && context.classList.contains('lpb-component')) {
         $(context)
-          .closest('[data-lp-builder-id]')
+          .closest('[data-lpb-id]')
           .each((index, element) => {
             updateMoveButtons($(element));
           });
diff --git a/layout_paragraphs.module b/layout_paragraphs.module
index 1ac0b5d08441b4bdc6ceb1b131a733a0c1b5407a..dc95b841d48a686c0664efc3dfc932dad1170090 100644
--- a/layout_paragraphs.module
+++ b/layout_paragraphs.module
@@ -54,9 +54,13 @@ function layout_paragraphs_theme() {
         'insert_button' => '',
       ],
     ],
-    'layout_paragraphs_builder_empty_layout' => [
+    'layout_paragraphs_builder_formatter' => [
       'variables' => [
-
+        'link_url' => NULL,
+        'link_text' => NULL,
+        'field_label' => NULL,
+        'is_empty' => FALSE,
+        'root_components' => [],
       ],
     ],
     'layout_paragraphs_builder_controls' => [
@@ -72,11 +76,6 @@ function layout_paragraphs_theme() {
         'types' => NULL,
       ],
     ],
-    'layout_paragraphs_builder_section_menu' => [
-      'variables' => [
-        'types' => NULL,
-      ],
-    ],
   ];
 }
 
diff --git a/layout_paragraphs.routing.yml b/layout_paragraphs.routing.yml
index 09106eda08bc7bb820fcd44d49edde27f1dbcc29..064e1eff20ae877aeb30027d9683a4dcaffbb063 100644
--- a/layout_paragraphs.routing.yml
+++ b/layout_paragraphs.routing.yml
@@ -13,6 +13,18 @@ layout_paragraphs.section_settings:
   requirements:
     _permission: 'administer site configuration'
 
+layout_paragraphs.builder.formatter:
+  path: '/layout-paragraphs-builder/{layout_paragraphs_layout}/formatter'
+  defaults:
+    _controller: '\Drupal\layout_paragraphs\Controller\LayoutParagraphsBuilderFormatterController::build'
+  options:
+    parameters:
+      layout_paragraphs_layout:
+        layout_paragraphs_layout_tempstore: TRUE
+    _admin_route: TRUE
+  requirements:
+    _permission: 'administer content'
+
 layout_paragraphs.builder.choose_component:
   path: '/layout-paragraphs-builder/{layout_paragraphs_layout}/choose-component/{sibling_uuid}/{placement}/{region}/{parent_uuid}'
   defaults:
diff --git a/src/Controller/LayoutParagraphsBuilderFormatterController.php b/src/Controller/LayoutParagraphsBuilderFormatterController.php
new file mode 100644
index 0000000000000000000000000000000000000000..99d4ba89ad9bcbd614b6a936c80077418f1c49c4
--- /dev/null
+++ b/src/Controller/LayoutParagraphsBuilderFormatterController.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\layout_paragraphs\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\AjaxHelperTrait;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\layout_paragraphs\LayoutParagraphsLayout;
+
+/**
+ * Layout paragraphs builder formatter controller.
+ */
+class LayoutParagraphsBuilderFormatterController extends ControllerBase {
+
+  use AjaxHelperTrait;
+
+  /**
+   * Builds the layout paragraphs builder form.
+   *
+   * @param \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout_paragraphs_layout
+   *   The layout paragraphs layout object.
+   *
+   * @return mixed
+   *   An ajax response or the form.
+   */
+  public function build(LayoutParagraphsLayout $layout_paragraphs_layout) {
+    $form = $this->formBuilder()->getForm(
+      '\Drupal\layout_paragraphs\Form\FrontendBuilderForm',
+      $layout_paragraphs_layout
+    );
+    if ($this->isAjax()) {
+      $response = new AjaxResponse();
+      $response->addCommand(new ReplaceCommand('[data-lpb-id="' . $layout_paragraphs_layout->id() . '"]', $form));
+      return $response;
+    }
+    return $form;
+  }
+
+}
diff --git a/src/Element/LayoutParagraphsBuilder.php b/src/Element/LayoutParagraphsBuilder.php
index da597656109cc28c42e85927757ed2f2d31340bd..b6aa3ef4b3230e6425c91786b3f1e8ffeac791b1 100644
--- a/src/Element/LayoutParagraphsBuilder.php
+++ b/src/Element/LayoutParagraphsBuilder.php
@@ -176,7 +176,7 @@ class LayoutParagraphsBuilder extends RenderElement implements ContainerFactoryP
         'lp-builder',
         'lp-builder-' . $layout->id(),
       ],
-      'data-lp-builder-id' => $layout->id(),
+      'data-lpb-id' => $layout->id(),
     ];
     $element['#attached']['library'] = ['layout_paragraphs/layout_paragraphs_builder'];
     $element['#attached']['drupalSettings']['lpBuilder'][$layout->id()] = $layout->getSettings();
@@ -194,6 +194,9 @@ class LayoutParagraphsBuilder extends RenderElement implements ContainerFactoryP
       $uuid = $component->getEntity()->uuid();
       $element['#root_components'][$uuid] =& $element['#components'][$uuid];
     }
+    if (count($element['#root_components'])) {
+      $element['#attributes']['class'][] = 'has-components';
+    }
     return $element;
   }
 
diff --git a/src/Form/FrontendBuilderForm.php b/src/Form/FrontendBuilderForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..46b027dc4a140f41d4a2abd1fac1916e506dac9e
--- /dev/null
+++ b/src/Form/FrontendBuilderForm.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\layout_paragraphs\Form;
+
+use Drupal\Core\Ajax\AjaxFormHelperTrait;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\layout_paragraphs\LayoutParagraphsLayout;
+
+/**
+ * Class LayoutParagraphsBuilderForm.
+ *
+ * Base form for layout paragraphs paragraph forms.
+ */
+class FrontendBuilderForm extends FormBase {
+
+  /**
+   * A layout paragraphs layout object.
+   *
+   * @var \Drupal\layout_paragraphs\LayoutParagraphsLayout
+   */
+  protected $layoutParagraphsLayout;
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getFormId() {
+    return 'layout_paragraphs_frontend_builder_form';
+  }
+
+  /**
+   * Builds the layout paragraphs builder form.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state object.
+   * @param \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout_paragraphs_layout
+   *   The layout paragraphs layout object.
+   */
+  public function buildForm(
+    array $form,
+    FormStateInterface $form_state,
+    LayoutParagraphsLayout $layout_paragraphs_layout = NULL) {
+    $this->layoutParagraphsLayout = $layout_paragraphs_layout;
+    $form['layout_paragraphs_builder_ui'] = [
+      '#type' => 'layout_paragraphs_builder',
+      '#layout_paragraphs_layout' => $this->layoutParagraphsLayout,
+    ];
+    $form['actions'] = [
+      '#type' => 'actions',
+      'save' => [
+        '#type' => 'submit',
+        '#value' => $this->t('Save'),
+        '#submit' => [[$this, 'saveLayout']],
+        '#ajax' => [
+          'callback' => [$this, 'closeBuilder'],
+        ],
+      ],
+      'cancel' => [
+        '#type' => 'submit',
+        '#value' => $this->t('Cancel'),
+        '#submit' => [[$this, 'closeBuilder']],
+        '#ajax' => [
+          'callback' => [$this, 'closeBuilder'],
+        ],
+      ],
+    ];
+    return $form;
+  }
+
+  /**
+   * Saves the layout to its parent entity.
+   *
+   * @param array $form
+   *   The form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state object.
+   */
+  public function saveLayout(array &$form, FormStateInterface $form_state) {
+    $entity = $this->layoutParagraphsLayout->getEntity();
+    $field_name = $this->layoutParagraphsLayout->getFieldName();
+    $entity->$field_name = $this->layoutParagraphsLayout->getParagraphsReferenceField();
+    $entity->save();
+  }
+
+  /**
+   * Closes the builder and returns the rendered layout.
+   */
+  public function closeBuilder() {
+    $rendered_layout = $this->layoutParagraphsLayout->getParagraphsReferenceField()->view();
+    return $rendered_layout;
+  }
+
+  public function refreshLayout($form, $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {}
+
+}
diff --git a/src/Form/InsertComponentForm.php b/src/Form/InsertComponentForm.php
index f3e100b363de421039e8daafa69e2bb4f7058426..815516ef9bd93e518fe780517026011e3f543126 100644
--- a/src/Form/InsertComponentForm.php
+++ b/src/Form/InsertComponentForm.php
@@ -74,7 +74,7 @@ class InsertComponentForm extends ComponentFormBase {
       $this->domSelector = '[data-region-uuid="' . $parent_uuid . '-' . $region . '"]';
     }
     else {
-      $this->domSelector = '[data-lp-builder-id="' . $this->layoutParagraphsLayout->id() . '"]';
+      $this->domSelector = '[data-lpb-id="' . $this->layoutParagraphsLayout->id() . '"]';
     }
     return $this->buildComponentForm($form, $form_state);
   }
diff --git a/src/LayoutParagraphsLayoutRefreshTrait.php b/src/LayoutParagraphsLayoutRefreshTrait.php
index 04ee1a4b05b23b2e74de59706c09c9604f4f6e12..ddbb82de81fc9a6fa8c7684ab0d8fc89898df31f 100644
--- a/src/LayoutParagraphsLayoutRefreshTrait.php
+++ b/src/LayoutParagraphsLayoutRefreshTrait.php
@@ -63,7 +63,7 @@ trait LayoutParagraphsLayoutRefreshTrait {
    */
   protected function refreshLayout(AjaxResponse $response) {
     $layout = $this->renderLayout();
-    $dom_selector = '[data-lp-builder-id="' . $this->layoutParagraphsLayout->id() . '"]';
+    $dom_selector = '[data-lpb-id="' . $this->layoutParagraphsLayout->id() . '"]';
     $response->addCommand(new ReplaceCommand($dom_selector, $layout));
     return $response;
   }
diff --git a/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php b/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php
new file mode 100644
index 0000000000000000000000000000000000000000..70dca8ed7f2d0a6b55e3daecdb1e8a3c79c6618b
--- /dev/null
+++ b/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\layout_paragraphs\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\layout_paragraphs\LayoutParagraphsLayout;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\layout_paragraphs\LayoutParagraphsLayoutTempstoreRepository;
+use Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget;
+
+/**
+ * Layout Paragraphs field formatter.
+ *
+ * @FieldFormatter(
+ *   id = "layout_paragraphs_builder",
+ *   label = @Translation("Layout Paragraphs Builder"),
+ *   description = @Translation("Renders editable paragraphs with layout."),
+ *   field_types = {
+ *     "entity_reference_revisions"
+ *   }
+ * )
+ */
+class LayoutParagraphsBuilderFormatter extends LayoutParagraphsFormatter implements ContainerFactoryPluginInterface {
+
+  /**
+   * The tempstore.
+   *
+   * @var \Drupal\layout_paragraphs\LayoutParagraphsLayoutTempstoreRepository
+   */
+  protected $tempstore;
+
+  /**
+   * {@inheritDoc}
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, LoggerChannelFactoryInterface $logger_factory, EntityDisplayRepositoryInterface $entity_display_repository, LayoutParagraphsLayoutTempstoreRepository $tempstore) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $logger_factory, $entity_display_repository);
+    $this->tempstore = $tempstore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['label'],
+      $configuration['view_mode'],
+      $configuration['third_party_settings'],
+      $container->get('logger.factory'),
+      $container->get('entity_display.repository'),
+      $container->get('layout_paragraphs.tempstore_repository')
+    );
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function view(FieldItemListInterface $items, $langcode = NULL) {
+
+    $elements = [
+      '#theme' => 'layout_paragraphs_builder_formatter',
+      '#root_components' => parent::view($items, $langcode),
+    ];
+    $entity = $items->getEntity();
+    if (!$entity->access('update')) {
+      return $elements['#root_components'];
+    }
+
+    /** @var \Drupal\Core\Entity\EntityDefintion $definition */
+    $definition = $items->getFieldDefinition();
+    $field_name = $definition->get('field_name');
+
+    $layout = new LayoutParagraphsLayout($items, $this->getSettings());
+    $this->tempstore->set($layout);
+    $layout = $this->tempstore->get($layout);
+
+    $root_components = $layout->getRootComponents();
+    $elements['#link_text'] = $this->t('Edit @field_name', ['@field_name' => $field_name]);
+    $elements['#link_url'] = Url::fromRoute('layout_paragraphs.builder.formatter', [
+      'layout_paragraphs_layout' => $layout->id(),
+    ]);
+    $elements['#field_label'] = $definition->label();
+    $elements['#type'] = 'container';
+    $elements['#is_empty'] = count($layout->getRootComponents()) == 0;
+    $elements['#attributes'] = [
+      'class' => [
+        'lpb-formatter',
+      ],
+      'data-lpb-id' => $layout->id(),
+    ];
+    $elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_builder';
+    return $elements;
+  }
+
+  /**
+   * Replicates settings from Layout Paragraphs Widget.
+   *
+   * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget
+   *
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $widget = $this->widgetInstance();
+    return $widget->settingsForm($form, $form_state);
+  }
+
+  /**
+   * Replicates settings from Layout Paragraphs Widget.
+   *
+   * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget
+   *
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $widget = $this->widgetInstance();
+    return $widget->settingsSummary();
+  }
+
+  /**
+   * Replicates settings from Layout Paragraphs Widget.
+   *
+   * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget
+   *
+   * @return array
+   *   The default settings array.
+   */
+  public static function defaultSettings() {
+    $defaults = parent::defaultSettings();
+    return LayoutParagraphsWidget::defaultSettings() + $defaults;
+  }
+
+  /**
+   * Returns a layout paragraphs field widget with correct settings applied.
+   *
+   * @return \Drupal\Core\Field\WidgetInterface
+   *   The widget instance.
+   */
+  protected function widgetInstance() {
+    $plugin_manager = \Drupal::service('plugin.manager.field.widget');
+    $widget = $plugin_manager->getInstance([
+      'field_definition' => $this->fieldDefinition,
+      'form_mode' => 'layout_paragraphs_editor',
+      'prepare' => TRUE,
+      'configuration' => [
+        'type' => 'layout_paragraphs',
+        'settings' => $this->getSettings(),
+        'third_party_settings' => [],
+      ],
+    ]);
+    return $widget;
+  }
+
+}
diff --git a/templates/layout-paragraphs-builder-formatter.html.twig b/templates/layout-paragraphs-builder-formatter.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..8eea35d9d2232269f94dbb5b3ce3022f4c93d325
--- /dev/null
+++ b/templates/layout-paragraphs-builder-formatter.html.twig
@@ -0,0 +1,17 @@
+{% if is_empty %}
+<div class="lpb-enable__empty-message__wrapper">
+  <div class="lpb-enable__empty-message">
+    <p>
+      You haven't created any {{ field_label }} yet.
+      <a class="lpb-enable-button use-ajax" href="{{link_url}}">{% trans %}Start creating {{ field_label }}.{% endtrans %}</a>
+    </p>
+  </div>
+</div>
+{% else %}
+<div class="lpb-enable__wrapper">
+  <div class="lpb-enable">
+    <div class="lpb-enable__button"><a class="lpb-enable-button use-ajax" href="{{link_url}}"><span>{{link_text}}</span></a></div>
+  </div>
+</div>
+{{ root_components }}
+{% endif %}
\ No newline at end of file
diff --git a/templates/layout-paragraphs-builder.html.twig b/templates/layout-paragraphs-builder.html.twig
index a592292dd5a36cf7a6c3cfdfed376fcbcdd42a03..5ebf84779720e043e29e0d9c9cee5b5940044500 100644
--- a/templates/layout-paragraphs-builder.html.twig
+++ b/templates/layout-paragraphs-builder.html.twig
@@ -8,7 +8,7 @@
       </div>
     </div>
   {% else %}
-  <div class="lpb-components">
+  <div class="lpb-component-list">
   {{ root_components }}
   </div>
   {% endif %}