diff --git a/backend/src/baserow/contrib/builder/api/domains/serializers.py b/backend/src/baserow/contrib/builder/api/domains/serializers.py
index a17094a99..f1d92414c 100644
--- a/backend/src/baserow/contrib/builder/api/domains/serializers.py
+++ b/backend/src/baserow/contrib/builder/api/domains/serializers.py
@@ -49,7 +49,7 @@ class PublicElementSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Element
-        fields = ("id", "type")
+        fields = ("id", "type", "style_padding_top", "style_padding_bottom")
         extra_kwargs = {
             "id": {"read_only": True},
             "type": {"read_only": True},
diff --git a/backend/src/baserow/contrib/builder/api/elements/serializers.py b/backend/src/baserow/contrib/builder/api/elements/serializers.py
index 84591a3c1..c4e2d5178 100644
--- a/backend/src/baserow/contrib/builder/api/elements/serializers.py
+++ b/backend/src/baserow/contrib/builder/api/elements/serializers.py
@@ -22,7 +22,14 @@ class ElementSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Element
-        fields = ("id", "page_id", "type", "order")
+        fields = (
+            "id",
+            "page_id",
+            "type",
+            "order",
+            "style_padding_top",
+            "style_padding_bottom",
+        )
         extra_kwargs = {
             "id": {"read_only": True},
             "page_id": {"read_only": True},
@@ -50,13 +57,13 @@ class CreateElementSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Element
-        fields = ("before_id", "type")
+        fields = ("before_id", "type", "style_padding_top", "style_padding_bottom")
 
 
 class UpdateElementSerializer(serializers.ModelSerializer):
     class Meta:
         model = Element
-        fields = []
+        fields = ("style_padding_top", "style_padding_bottom")
 
 
 class MoveElementSerializer(serializers.Serializer):
diff --git a/backend/src/baserow/contrib/builder/elements/element_types.py b/backend/src/baserow/contrib/builder/elements/element_types.py
index af4317800..86a5751df 100644
--- a/backend/src/baserow/contrib/builder/elements/element_types.py
+++ b/backend/src/baserow/contrib/builder/elements/element_types.py
@@ -1,4 +1,3 @@
-from abc import ABC
 from typing import Dict, Optional
 
 from rest_framework import serializers
@@ -21,17 +20,70 @@ from baserow.contrib.builder.types import ElementDict
 from baserow.core.user_files.models import UserFile
 
 
-class BaseTextElementType(ElementType, ABC):
+class HeadingElementType(ElementType):
     """
-    Base class for text elements.
+    A simple heading element that can be used to display a title.
     """
 
+    type = "heading"
+    model_class = HeadingElement
+    serializer_field_names = ["value", "level"]
+    allowed_fields = ["value", "level"]
+
+    class SerializedDict(ElementDict):
+        value: Expression
+        level: int
+
+    @property
+    def serializer_field_overrides(self):
+        from baserow.core.expression.serializers import ExpressionSerializer
+
+        overrides = {
+            "value": ExpressionSerializer(
+                help_text="The value of the element. Must be an expression.",
+                required=False,
+                allow_blank=True,
+                default="",
+            ),
+            "level": serializers.IntegerField(
+                help_text="The level of the heading from 1 to 6.",
+                min_value=1,
+                max_value=6,
+                default=1,
+            ),
+        }
+
+        return overrides
+
+    def get_sample_params(self):
+        return {
+            "value": "Corporis perspiciatis",
+            "level": 2,
+        }
+
+
+class ParagraphElementType(ElementType):
+    """
+    A simple paragraph element that can be used to display a paragraph of text.
+    """
+
+    type = "paragraph"
+    model_class = ParagraphElement
     serializer_field_names = ["value"]
     allowed_fields = ["value"]
 
     class SerializedDict(ElementDict):
         value: Expression
 
+    def get_sample_params(self):
+        return {
+            "value": "Suscipit maxime eos ea vel commodi dolore. "
+            "Eum dicta sit rerum animi. Sint sapiente eum cupiditate nobis vel. "
+            "Maxime qui nam consequatur. "
+            "Asperiores corporis perspiciatis nam harum veritatis. "
+            "Impedit qui maxime aut illo quod ea molestias."
+        }
+
     @property
     def serializer_field_overrides(self):
         from baserow.core.expression.serializers import ExpressionSerializer
@@ -46,65 +98,7 @@ class BaseTextElementType(ElementType, ABC):
         }
 
 
-class HeadingElementType(BaseTextElementType):
-    """
-    A simple heading element that can be used to display a title.
-    """
-
-    type = "heading"
-    model_class = HeadingElement
-
-    class SerializedDict(ElementDict):
-        value: Expression
-        level: int
-
-    @property
-    def serializer_field_names(self):
-        return super().serializer_field_names + ["level"]
-
-    @property
-    def allowed_fields(self):
-        return super().allowed_fields + ["level"]
-
-    @property
-    def serializer_field_overrides(self):
-        overrides = {
-            "level": serializers.IntegerField(
-                help_text="The level of the heading from 1 to 6.",
-                min_value=1,
-                max_value=6,
-                default=1,
-            )
-        }
-        overrides.update(super().serializer_field_overrides)
-        return overrides
-
-    def get_sample_params(self):
-        return {
-            "value": "Corporis perspiciatis",
-            "level": 2,
-        }
-
-
-class ParagraphElementType(BaseTextElementType):
-    """
-    A simple paragraph element that can be used to display a paragraph of text.
-    """
-
-    type = "paragraph"
-    model_class = ParagraphElement
-
-    def get_sample_params(self):
-        return {
-            "value": "Suscipit maxime eos ea vel commodi dolore. "
-            "Eum dicta sit rerum animi. Sint sapiente eum cupiditate nobis vel. "
-            "Maxime qui nam consequatur. "
-            "Asperiores corporis perspiciatis nam harum veritatis. "
-            "Impedit qui maxime aut illo quod ea molestias."
-        }
-
-
-class LinkElementType(BaseTextElementType):
+class LinkElementType(ElementType):
     """
     A simple paragraph element that can be used to display a paragraph of text.
     """
@@ -112,38 +106,34 @@ class LinkElementType(BaseTextElementType):
     type = "link"
     model_class = LinkElement
     PATH_PARAM_TYPE_TO_PYTHON_TYPE_MAP = {"text": str, "numeric": int}
+    serializer_field_names = [
+        "value",
+        "navigation_type",
+        "navigate_to_page_id",
+        "navigate_to_url",
+        "page_parameters",
+        "variant",
+        "target",
+        "width",
+        "alignment",
+    ]
+    allowed_fields = [
+        "value",
+        "navigation_type",
+        "navigate_to_page_id",
+        "navigate_to_url",
+        "page_parameters",
+        "variant",
+        "target",
+        "width",
+        "alignment",
+    ]
 
     class SerializedDict(ElementDict):
         value: Expression
         destination: Expression
         open_new_tab: bool
 
-    @property
-    def serializer_field_names(self):
-        return super().serializer_field_names + [
-            "navigation_type",
-            "navigate_to_page_id",
-            "navigate_to_url",
-            "page_parameters",
-            "variant",
-            "target",
-            "width",
-            "alignment",
-        ]
-
-    @property
-    def allowed_fields(self):
-        return super().allowed_fields + [
-            "navigation_type",
-            "navigate_to_page_id",
-            "navigate_to_url",
-            "page_parameters",
-            "variant",
-            "target",
-            "width",
-            "alignment",
-        ]
-
     def import_serialized(self, page, serialized_values, id_mapping):
         serialized_copy = serialized_values.copy()
         if serialized_copy["navigate_to_page_id"]:
@@ -160,6 +150,12 @@ class LinkElementType(BaseTextElementType):
         from baserow.core.expression.serializers import ExpressionSerializer
 
         overrides = {
+            "value": ExpressionSerializer(
+                help_text="The value of the element. Must be an expression.",
+                required=False,
+                allow_blank=True,
+                default="",
+            ),
             "navigation_type": serializers.ChoiceField(
                 choices=LinkElement.NAVIGATION_TYPES.choices,
                 help_text=LinkElement._meta.get_field("navigation_type").help_text,
@@ -203,11 +199,11 @@ class LinkElementType(BaseTextElementType):
                 required=False,
             ),
         }
-        overrides.update(super().serializer_field_overrides)
         return overrides
 
     def get_sample_params(self):
         return {
+            "value": "test",
             "navigation_type": "custom",
             "navigate_to_page_id": None,
             "navigate_to_url": "http://example.com",
diff --git a/backend/src/baserow/contrib/builder/elements/handler.py b/backend/src/baserow/contrib/builder/elements/handler.py
index a676575f8..f14fb1433 100644
--- a/backend/src/baserow/contrib/builder/elements/handler.py
+++ b/backend/src/baserow/contrib/builder/elements/handler.py
@@ -110,7 +110,7 @@ class ElementHandler:
         else:
             order = Element.get_last_order(page)
 
-        shared_allowed_fields = ["type"]
+        shared_allowed_fields = ["type", "style_padding_top", "style_padding_bottom"]
         allowed_values = extract_allowed(
             kwargs, shared_allowed_fields + element_type.allowed_fields
         )
@@ -145,7 +145,7 @@ class ElementHandler:
 
         element_type = element_type_registry.get_by_model(element)
 
-        shared_allowed_fields = []
+        shared_allowed_fields = ["style_padding_top", "style_padding_bottom"]
         allowed_updates = extract_allowed(
             kwargs, shared_allowed_fields + element_type.allowed_fields
         )
diff --git a/backend/src/baserow/contrib/builder/elements/models.py b/backend/src/baserow/contrib/builder/elements/models.py
index a976e615b..d9b84e354 100644
--- a/backend/src/baserow/contrib/builder/elements/models.py
+++ b/backend/src/baserow/contrib/builder/elements/models.py
@@ -51,6 +51,9 @@ class Element(
         on_delete=models.SET(get_default_element_content_type),
     )
 
+    style_padding_top = models.PositiveIntegerField(default=10)
+    style_padding_bottom = models.PositiveIntegerField(default=10)
+
     class Meta:
         ordering = ("order", "id")
 
@@ -86,18 +89,7 @@ class Element(
         return cls.get_unique_orders_before_item(before, queryset)[0]
 
 
-class BaseTextElement(Element):
-    """
-    Base class for text elements.
-    """
-
-    value = ExpressionField(default="")
-
-    class Meta:
-        abstract = True
-
-
-class HeadingElement(BaseTextElement):
+class HeadingElement(Element):
     """
     A Heading element to display a title.
     """
@@ -109,18 +101,21 @@ class HeadingElement(BaseTextElement):
         H4 = 4
         H5 = 5
 
+    value = ExpressionField(default="")
     level = models.IntegerField(
         choices=HeadingLevel.choices, default=1, help_text="The level of the heading"
     )
 
 
-class ParagraphElement(BaseTextElement):
+class ParagraphElement(Element):
     """
     A simple paragraph.
     """
 
+    value = ExpressionField(default="")
 
-class LinkElement(BaseTextElement):
+
+class LinkElement(Element):
     """
     A simple link.
     """
@@ -141,6 +136,7 @@ class LinkElement(BaseTextElement):
         AUTO = "auto"
         FULL = "full"
 
+    value = ExpressionField(default="")
     navigation_type = models.CharField(
         choices=NAVIGATION_TYPES.choices,
         help_text="The navigation type.",
diff --git a/backend/src/baserow/contrib/builder/elements/registries.py b/backend/src/baserow/contrib/builder/elements/registries.py
index 9dfa70dac..2ea6f0da9 100644
--- a/backend/src/baserow/contrib/builder/elements/registries.py
+++ b/backend/src/baserow/contrib/builder/elements/registries.py
@@ -58,7 +58,12 @@ class ElementType(
         other_properties = {key: getattr(element, key) for key in self.allowed_fields}
 
         serialized = self.SerializedDict(
-            id=element.id, type=self.type, order=element.order, **other_properties
+            id=element.id,
+            type=self.type,
+            order=element.order,
+            style_padding_top=element.style_padding_top,
+            style_padding_bottom=element.style_padding_bottom,
+            **other_properties
         )
 
         return serialized
diff --git a/backend/src/baserow/contrib/builder/migrations/0014_initial_styling.py b/backend/src/baserow/contrib/builder/migrations/0014_initial_styling.py
new file mode 100644
index 000000000..b54c9fbab
--- /dev/null
+++ b/backend/src/baserow/contrib/builder/migrations/0014_initial_styling.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.18 on 2023-06-07 20:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("builder", "0013_datasource"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="element",
+            name="style_padding_bottom",
+            field=models.PositiveIntegerField(default=10),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_padding_top",
+            field=models.PositiveIntegerField(default=10),
+        ),
+    ]
diff --git a/backend/tests/baserow/contrib/builder/test_builder_application_type.py b/backend/tests/baserow/contrib/builder/test_builder_application_type.py
index 875d6d4c1..9cfe64602 100644
--- a/backend/tests/baserow/contrib/builder/test_builder_application_type.py
+++ b/backend/tests/baserow/contrib/builder/test_builder_application_type.py
@@ -49,12 +49,16 @@ def test_builder_application_export(data_fixture):
                         "id": element1.id,
                         "type": "heading",
                         "order": element1.order,
+                        "style_padding_top": 10,
+                        "style_padding_bottom": 10,
                         "value": element1.value,
                         "level": element1.level,
                     },
                     {
                         "id": element2.id,
                         "type": "paragraph",
+                        "style_padding_top": 10,
+                        "style_padding_bottom": 10,
                         "order": element2.order,
                         "value": element2.value,
                     },
@@ -70,6 +74,8 @@ def test_builder_application_export(data_fixture):
                     {
                         "id": element3.id,
                         "type": "heading",
+                        "style_padding_top": 10,
+                        "style_padding_bottom": 10,
                         "order": element3.order,
                         "value": element3.value,
                         "level": element3.level,
diff --git a/web-frontend/modules/builder/components/elements/ElementMenu.vue b/web-frontend/modules/builder/components/elements/ElementMenu.vue
index 8046cddde..e186bfe0b 100644
--- a/web-frontend/modules/builder/components/elements/ElementMenu.vue
+++ b/web-frontend/modules/builder/components/elements/ElementMenu.vue
@@ -1,38 +1,44 @@
 <template>
-  <div class="element__menu">
+  <div class="element-preview__menu">
     <div
       v-if="isDuplicating"
-      class="loading element__menu-duplicate-loading"
+      class="loading element-preview__menu-duplicate-loading"
     ></div>
-    <a v-else class="element__menu-item" @click="$emit('duplicate')">
+    <a v-else class="element-preview__menu-item" @click="$emit('duplicate')">
       <i class="fas fa-copy"></i>
-      <span class="element__menu-item-description">
+      <span class="element-preview__menu-item-description">
         {{ $t('action.duplicate') }}
       </span>
     </a>
     <a
-      class="element__menu-item"
+      class="element-preview__menu-item"
       :class="{ disabled: moveUpDisabled }"
       @click="!moveUpDisabled && $emit('move', PLACEMENTS.BEFORE)"
     >
       <i class="fas fa-arrow-up"></i>
-      <span v-if="!moveUpDisabled" class="element__menu-item-description">
+      <span
+        v-if="!moveUpDisabled"
+        class="element-preview__menu-item-description"
+      >
         {{ $t('elementMenu.moveUp') }}
       </span>
     </a>
     <a
-      class="element__menu-item"
+      class="element-preview__menu-item"
       :class="{ disabled: moveDownDisabled }"
       @click="!moveDownDisabled && $emit('move', PLACEMENTS.AFTER)"
     >
       <i class="fas fa-arrow-down"></i>
-      <span v-if="!moveDownDisabled" class="element__menu-item-description">
+      <span
+        v-if="!moveDownDisabled"
+        class="element-preview__menu-item-description"
+      >
         {{ $t('elementMenu.moveDown') }}
       </span>
     </a>
-    <a class="element__menu-item" @click="$emit('delete')">
+    <a class="element-preview__menu-item" @click="$emit('delete')">
       <i class="fas fa-trash"></i>
-      <span class="element__menu-item-description">
+      <span class="element-preview__menu-item-description">
         {{ $t('action.delete') }}
       </span>
     </a>
diff --git a/web-frontend/modules/builder/components/elements/ElementPreview.vue b/web-frontend/modules/builder/components/elements/ElementPreview.vue
index 5cedf6240..04908c5ab 100644
--- a/web-frontend/modules/builder/components/elements/ElementPreview.vue
+++ b/web-frontend/modules/builder/components/elements/ElementPreview.vue
@@ -1,12 +1,15 @@
 <template>
   <div
-    class="element"
-    :class="{ 'element--active': active, 'element--in-error': inError }"
+    class="element-preview"
+    :class="{
+      'element-preview--active': active,
+      'element-preview--in-error': inError,
+    }"
     @click="$emit('selected')"
   >
     <InsertElementButton
       v-if="active"
-      class="element__insert--top"
+      class="element-preview__insert--top"
       @click="$emit('insert', PLACEMENTS.BEFORE)"
     />
     <ElementMenu
@@ -18,15 +21,14 @@
       @move="$emit('move', $event)"
       @duplicate="$emit('duplicate')"
     />
-    <component
-      :is="elementType.editComponent"
-      class="element__component"
+    <PageRootElement
       :element="element"
       :builder="builder"
-    />
+      :mode="'editing'"
+    ></PageRootElement>
     <InsertElementButton
       v-if="active"
-      class="element__insert--bottom"
+      class="element-preview__insert--bottom"
       @click="$emit('insert', PLACEMENTS.AFTER)"
     />
   </div>
@@ -35,10 +37,12 @@
 <script>
 import ElementMenu from '@baserow/modules/builder/components/elements/ElementMenu'
 import InsertElementButton from '@baserow/modules/builder/components/elements/InsertElementButton'
+import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
 import { PLACEMENTS } from '@baserow/modules/builder/enums'
+
 export default {
   name: 'ElementPreview',
-  components: { ElementMenu, InsertElementButton },
+  components: { ElementMenu, InsertElementButton, PageRootElement },
   inject: ['builder'],
   props: {
     element: {
diff --git a/web-frontend/modules/builder/components/elements/InsertElementButton.vue b/web-frontend/modules/builder/components/elements/InsertElementButton.vue
index 82afeb59b..1f1084b4c 100644
--- a/web-frontend/modules/builder/components/elements/InsertElementButton.vue
+++ b/web-frontend/modules/builder/components/elements/InsertElementButton.vue
@@ -1,5 +1,5 @@
 <template>
-  <a class="element__insert" @click="$emit('click')">
+  <a class="element-preview__insert" @click="$emit('click')">
     <i class="fas fa-plus"></i>
   </a>
 </template>
diff --git a/web-frontend/modules/builder/components/elements/components/HeadingElement.vue b/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
index 1848b191f..90d024b8a 100644
--- a/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
@@ -9,11 +9,11 @@
 </template>
 
 <script>
-import textElement from '@baserow/modules/builder/mixins/elements/textElement'
+import element from '@baserow/modules/builder/mixins/element'
 
 export default {
   name: 'HeadingElement',
-  mixins: [textElement],
+  mixins: [element],
   props: {
     /**
      * @type {Object}
diff --git a/web-frontend/modules/builder/components/elements/components/ImageElement.vue b/web-frontend/modules/builder/components/elements/components/ImageElement.vue
index 0afbf4b86..c5910bc8f 100644
--- a/web-frontend/modules/builder/components/elements/components/ImageElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/ImageElement.vue
@@ -9,10 +9,12 @@
 </template>
 
 <script>
+import element from '@baserow/modules/builder/mixins/element'
 import { IMAGE_SOURCE_TYPES } from '@baserow/modules/builder/enums'
 
 export default {
   name: 'ImageElement',
+  mixins: [element],
   props: {
     /**
      * @type {Object}
@@ -36,6 +38,7 @@ export default {
     classes() {
       return {
         [`element--alignment-${this.element.alignment}`]: true,
+        'element--no-value': !this.imageSource && !this.element.alt_text,
       }
     },
   },
diff --git a/web-frontend/modules/builder/components/elements/components/LinkElement.vue b/web-frontend/modules/builder/components/elements/components/LinkElement.vue
index 31751f1e4..97d4dc340 100644
--- a/web-frontend/modules/builder/components/elements/components/LinkElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/LinkElement.vue
@@ -1,21 +1,15 @@
 <template>
   <div class="link-element" :class="classes">
-    <Button
-      v-if="element.variant === 'button'"
-      tag="a"
-      v-bind="extraAttr"
-      :target="element.target"
-      :full-width="element.width === 'full'"
-      @click="onClick($event)"
-    >
-      {{ element.value || $t('linkElement.noValue') }}
-    </Button>
     <a
-      v-else
-      class="link-element__link"
+      :class="{
+        'link-element__link': element.variant !== 'button',
+        'link-element__button': element.variant === 'button',
+        'link-element__button--full-width':
+          element.variant === 'button' && element.width === 'full',
+      }"
       v-bind="extraAttr"
       :target="`_${element.target}`"
-      @click="onClick($event)"
+      @click="onClick"
     >
       {{ element.value || $t('linkElement.noValue') }}
     </a>
@@ -23,7 +17,7 @@
 </template>
 
 <script>
-import textElement from '@baserow/modules/builder/mixins/elements/textElement'
+import element from '@baserow/modules/builder/mixins/element'
 import { LinkElementType } from '@baserow/modules/builder/elementTypes'
 
 /**
@@ -40,18 +34,7 @@ import { LinkElementType } from '@baserow/modules/builder/elementTypes'
 
 export default {
   name: 'LinkElement',
-  mixins: [textElement],
-  props: {
-    /**
-     * @type {LinkElement}
-     */
-    element: {
-      type: Object,
-      required: true,
-    },
-    builder: { type: Object, required: true },
-    mode: { type: String, required: true },
-  },
+  mixins: [element],
   computed: {
     classes() {
       return {
@@ -100,6 +83,11 @@ export default {
   },
   methods: {
     onClick(event) {
+      if (this.mode === 'editing') {
+        event.preventDefault()
+        return
+      }
+
       if (!this.url) {
         event.preventDefault()
       } else if (
diff --git a/web-frontend/modules/builder/components/elements/components/LinkElementEdit.vue b/web-frontend/modules/builder/components/elements/components/LinkElementEdit.vue
deleted file mode 100644
index def47eb11..000000000
--- a/web-frontend/modules/builder/components/elements/components/LinkElementEdit.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-  <div class="link-element" :class="classes">
-    <Button
-      v-if="element.variant === 'button'"
-      tag="a"
-      v-bind="extraAttr"
-      :target="element.target"
-      :full-width="element.width === 'full'"
-      @click.prevent
-    >
-      {{ element.value || $t('linkElement.noValue') }}
-    </Button>
-    <a
-      v-else
-      class="link-element__link"
-      v-bind="extraAttr"
-      :target="`_${element.target}`"
-      @click.prevent
-    >
-      {{ element.value || $t('linkElement.noValue') }}
-    </a>
-  </div>
-</template>
-
-<script>
-import textElement from '@baserow/modules/builder/mixins/elements/textElement'
-import { LinkElementType } from '@baserow/modules/builder/elementTypes'
-
-export default {
-  name: 'LinkElementEdit',
-  mixins: [textElement],
-  props: {
-    /**
-     * @type {LinkElement}
-     */
-    element: {
-      type: Object,
-      required: true,
-    },
-    builder: { type: Object, required: true },
-  },
-  computed: {
-    classes() {
-      return {
-        [`element--alignment-${this.element.alignment}`]: true,
-        'element--no-value': !this.element.value,
-      }
-    },
-    extraAttr() {
-      const attr = {}
-      if (this.url) {
-        attr.href = this.url
-      }
-      return attr
-    },
-    url() {
-      try {
-        return LinkElementType.getUrlFromElement(this.element, this.builder)
-      } catch (e) {
-        return ''
-      }
-    },
-  },
-}
-</script>
diff --git a/web-frontend/modules/builder/components/elements/components/ParagraphElement.vue b/web-frontend/modules/builder/components/elements/components/ParagraphElement.vue
index 007d38165..ff5e1f39f 100644
--- a/web-frontend/modules/builder/components/elements/components/ParagraphElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/ParagraphElement.vue
@@ -14,7 +14,7 @@
 </template>
 
 <script>
-import textElement from '@baserow/modules/builder/mixins/elements/textElement'
+import element from '@baserow/modules/builder/mixins/element'
 import { generateHash } from '@baserow/modules/core/utils/hashing'
 
 /**
@@ -25,7 +25,7 @@ import { generateHash } from '@baserow/modules/core/utils/hashing'
 
 export default {
   name: 'ParagraphElement',
-  mixins: [textElement],
+  mixins: [element],
   props: {
     /**
      * @type {Object}
diff --git a/web-frontend/modules/builder/components/page/PageContent.vue b/web-frontend/modules/builder/components/page/PageContent.vue
index 0404ed53b..0dff27888 100644
--- a/web-frontend/modules/builder/components/page/PageContent.vue
+++ b/web-frontend/modules/builder/components/page/PageContent.vue
@@ -1,11 +1,9 @@
 <template>
   <div>
-    <component
-      :is="getType(element).component"
+    <PageRootElement
       v-for="element in elements"
       :key="element.id"
       :element="element"
-      class="element__component"
       :builder="builder"
       :mode="mode"
     />
@@ -13,7 +11,10 @@
 </template>
 
 <script>
+import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
+
 export default {
+  components: { PageRootElement },
   inject: ['builder', 'mode'],
   props: {
     page: {
@@ -33,10 +34,5 @@ export default {
       required: true,
     },
   },
-  methods: {
-    getType(element) {
-      return this.$registry.get('element', element.type)
-    },
-  },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/page/PageRootElement.vue b/web-frontend/modules/builder/components/page/PageRootElement.vue
new file mode 100644
index 000000000..6498e323c
--- /dev/null
+++ b/web-frontend/modules/builder/components/page/PageRootElement.vue
@@ -0,0 +1,53 @@
+<template functional>
+  <!--
+  This element is supposed to be wrapping the root elements on a page. They allow
+  setting a width, background, borders, and more, but this only makes sense if they're
+  added to the root of the page. Child elements in for example a containing element must
+  not be wrapped by this component.
+  -->
+  <div
+    class="page-root-element"
+    :style="{
+      'padding-top': `${props.element.style_padding_top || 0}px`,
+      'padding-bottom': `${props.element.style_padding_bottom || 0}px`,
+    }"
+  >
+    <div class="page-root-element__inner">
+      <component
+        :is="$options.methods.getComponent(parent, props.element, props.mode)"
+        :element="props.element"
+        :builder="props.builder"
+        :mode="props.mode"
+        class="element"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PageRootElement',
+  props: {
+    element: {
+      type: Object,
+      required: true,
+    },
+    builder: {
+      type: Object,
+      required: true,
+    },
+    mode: {
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+  methods: {
+    getComponent(parent, element, mode) {
+      const elementType = parent.$registry.get('element', element.type)
+      const componentName = mode === 'editing' ? 'editComponent' : 'component'
+      return elementType[componentName]
+    },
+  },
+}
+</script>
diff --git a/web-frontend/modules/builder/components/page/sidePanels/GeneralSidePanel.vue b/web-frontend/modules/builder/components/page/sidePanels/GeneralSidePanel.vue
index bb474b746..7fef179ff 100644
--- a/web-frontend/modules/builder/components/page/sidePanels/GeneralSidePanel.vue
+++ b/web-frontend/modules/builder/components/page/sidePanels/GeneralSidePanel.vue
@@ -11,50 +11,10 @@
 </template>
 
 <script>
-import { mapActions, mapGetters } from 'vuex'
-import { notifyIf } from '@baserow/modules/core/utils/error'
-import { clone } from '@baserow/modules/core/utils/object'
-import _ from 'lodash'
+import elementSidePanel from '@baserow/modules/builder/mixins/elementSidePanel'
 
 export default {
   name: 'GeneralSidePanel',
-  inject: ['builder'],
-  computed: {
-    ...mapGetters({
-      element: 'element/getSelected',
-    }),
-
-    elementType() {
-      if (this.element) {
-        return this.$registry.get('element', this.element.type)
-      }
-      return null
-    },
-
-    defaultValues() {
-      return this.element
-    },
-  },
-  methods: {
-    ...mapActions({
-      actionDebouncedUpdateSelectedElement: 'element/debouncedUpdateSelected',
-    }),
-    async onChange(newValues) {
-      const oldValues = this.element
-      if (!_.isEqual(newValues, oldValues)) {
-        try {
-          await this.actionDebouncedUpdateSelectedElement({
-            // Here we clone the values to prevent
-            // "modification oustide of the store" error
-            values: clone(newValues),
-          })
-        } catch (error) {
-          // Restore the previous saved values from the store
-          this.$refs.elementForm.reset()
-          notifyIf(error)
-        }
-      }
-    },
-  },
+  mixins: [elementSidePanel],
 }
 </script>
diff --git a/web-frontend/modules/builder/components/page/sidePanels/StyleBoxForm.vue b/web-frontend/modules/builder/components/page/sidePanels/StyleBoxForm.vue
new file mode 100644
index 000000000..2fbb62cac
--- /dev/null
+++ b/web-frontend/modules/builder/components/page/sidePanels/StyleBoxForm.vue
@@ -0,0 +1,65 @@
+<template>
+  <form @submit.prevent>
+    <FormElement class="control">
+      <label class="control__label">{{ label }}</label>
+      <div class="control__elements">
+        <input
+          v-model="values[paddingName]"
+          type="number"
+          class="input"
+          :class="{ 'input--error': $v.values[paddingName].$error }"
+          @blur="$v.values[paddingName].$touch()"
+        />
+        <div v-if="$v.values[paddingName].$error" class="error">
+          {{ $t('styleBoxForm.paddingError') }}
+        </div>
+      </div>
+    </FormElement>
+  </form>
+</template>
+
+<script>
+import { required, integer, between } from 'vuelidate/lib/validators'
+import form from '@baserow/modules/core/mixins/form'
+
+export default {
+  name: 'StyleBoxForm',
+  mixins: [form],
+  props: {
+    label: {
+      type: String,
+      required: true,
+    },
+    paddingName: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      allowedValues: [this.paddingName],
+      values: {
+        [this.paddingName]: 0,
+      },
+    }
+  },
+  validations() {
+    return {
+      values: {
+        [this.paddingName]: {
+          required,
+          integer,
+          between: between(0, 200),
+        },
+      },
+    }
+  },
+  methods: {
+    emitChange(newValues) {
+      if (this.isFormValid()) {
+        this.$emit('values-changed', newValues)
+      }
+    },
+  },
+}
+</script>
diff --git a/web-frontend/modules/builder/components/page/sidePanels/StyleSidePanel.vue b/web-frontend/modules/builder/components/page/sidePanels/StyleSidePanel.vue
index 317043198..317b0d614 100644
--- a/web-frontend/modules/builder/components/page/sidePanels/StyleSidePanel.vue
+++ b/web-frontend/modules/builder/components/page/sidePanels/StyleSidePanel.vue
@@ -1,12 +1,27 @@
 <template>
-  <div>Style panel</div>
+  <div :key="element.id">
+    <StyleBoxForm
+      :label="$t('styleSidePanel.paddingTop')"
+      padding-name="style_padding_top"
+      :default-values="defaultValues"
+      @values-changed="onChange($event)"
+    ></StyleBoxForm>
+    <StyleBoxForm
+      :label="$t('styleSidePanel.paddingBottom')"
+      padding-name="style_padding_bottom"
+      :default-values="defaultValues"
+      @values-changed="onChange($event)"
+    ></StyleBoxForm>
+  </div>
 </template>
 
 <script>
+import elementSidePanel from '@baserow/modules/builder/mixins/elementSidePanel'
+import StyleBoxForm from '@baserow/modules/builder/components/page/sidePanels/StyleBoxForm.vue'
+
 export default {
   name: 'StyleSidePanel',
-  data() {
-    return {}
-  },
+  components: { StyleBoxForm },
+  mixins: [elementSidePanel],
 }
 </script>
diff --git a/web-frontend/modules/builder/elementTypes.js b/web-frontend/modules/builder/elementTypes.js
index a628bb891..d493ac9f4 100644
--- a/web-frontend/modules/builder/elementTypes.js
+++ b/web-frontend/modules/builder/elementTypes.js
@@ -2,7 +2,6 @@ import { Registerable } from '@baserow/modules/core/registry'
 import ParagraphElement from '@baserow/modules/builder/components/elements/components/ParagraphElement'
 import HeadingElement from '@baserow/modules/builder/components/elements/components/HeadingElement'
 import LinkElement from '@baserow/modules/builder/components/elements/components/LinkElement'
-import LinkElementEdit from '@baserow/modules/builder/components/elements/components/LinkElementEdit'
 import ParagraphElementForm from '@baserow/modules/builder/components/elements/components/forms/ParagraphElementForm'
 import HeadingElementForm from '@baserow/modules/builder/components/elements/components/forms/HeadingElementForm'
 import LinkElementForm from '@baserow/modules/builder/components/elements/components/forms/LinkElementForm'
@@ -131,10 +130,6 @@ export class LinkElementType extends ElementType {
     return LinkElement
   }
 
-  get editComponent() {
-    return LinkElementEdit
-  }
-
   get formComponent() {
     return LinkElementForm
   }
diff --git a/web-frontend/modules/builder/mixins/element.js b/web-frontend/modules/builder/mixins/element.js
new file mode 100644
index 000000000..617244c0e
--- /dev/null
+++ b/web-frontend/modules/builder/mixins/element.js
@@ -0,0 +1,20 @@
+export default {
+  props: {
+    element: {
+      type: Object,
+      required: true,
+    },
+    builder: {
+      type: Object,
+      required: true,
+    },
+    mode: {
+      // editing = being editing by the page editor
+      // preview = previewing the application
+      // public = publicly published application
+      type: String,
+      required: false,
+      default: '',
+    },
+  },
+}
diff --git a/web-frontend/modules/builder/mixins/elementSidePanel.js b/web-frontend/modules/builder/mixins/elementSidePanel.js
new file mode 100644
index 000000000..0bea524f8
--- /dev/null
+++ b/web-frontend/modules/builder/mixins/elementSidePanel.js
@@ -0,0 +1,46 @@
+import { mapActions, mapGetters } from 'vuex'
+import _ from 'lodash'
+
+import { clone } from '@baserow/modules/core/utils/object'
+import { notifyIf } from '@baserow/modules/core/utils/error'
+
+export default {
+  inject: ['builder'],
+  computed: {
+    ...mapGetters({
+      element: 'element/getSelected',
+    }),
+
+    elementType() {
+      if (this.element) {
+        return this.$registry.get('element', this.element.type)
+      }
+      return null
+    },
+
+    defaultValues() {
+      return this.element
+    },
+  },
+  methods: {
+    ...mapActions({
+      actionDebouncedUpdateSelectedElement: 'element/debouncedUpdateSelected',
+    }),
+    async onChange(newValues) {
+      const oldValues = this.element
+      if (!_.isEqual(newValues, oldValues)) {
+        try {
+          await this.actionDebouncedUpdateSelectedElement({
+            // Here we clone the values to prevent
+            // "modification oustide of the store" error
+            values: clone(newValues),
+          })
+        } catch (error) {
+          // Restore the previous saved values from the store
+          this.$refs.elementForm.reset()
+          notifyIf(error)
+        }
+      }
+    },
+  },
+}
diff --git a/web-frontend/modules/builder/mixins/elements/textElement.js b/web-frontend/modules/builder/mixins/elements/textElement.js
deleted file mode 100644
index e1786ee30..000000000
--- a/web-frontend/modules/builder/mixins/elements/textElement.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default {
-  props: {
-    value: {
-      type: String,
-      required: false,
-      default: 'Some temp default value',
-    },
-  },
-}
diff --git a/web-frontend/modules/builder/pages/publicPage.vue b/web-frontend/modules/builder/pages/publicPage.vue
index be73f89a9..d4f14a43e 100644
--- a/web-frontend/modules/builder/pages/publicPage.vue
+++ b/web-frontend/modules/builder/pages/publicPage.vue
@@ -72,5 +72,14 @@ export default {
       mode,
     }
   },
+  head() {
+    return {
+      titleTemplate: '',
+      title: this.page.name,
+      bodyAttrs: {
+        class: 'public-page',
+      },
+    }
+  },
 }
 </script>
diff --git a/web-frontend/modules/core/assets/scss/components/builder/all.scss b/web-frontend/modules/core/assets/scss/components/builder/all.scss
index c6732d7ae..2e3f6faf6 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/all.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/all.scss
@@ -5,9 +5,11 @@
 @import 'add_element_modal';
 @import 'page_preview';
 @import 'element';
+@import 'element_preview';
 @import 'side_panels';
 @import 'empty_side_panel_state';
-@import 'page';
+@import 'page_editor';
+@import 'page_root_element';
 @import 'page_settings_path_params_form_element';
 @import 'domain_card';
 @import 'dns_status';
@@ -15,5 +17,6 @@
 @import 'integration_settings';
 @import 'last_published_domain_date';
 @import 'publish_action_modal';
+@import 'public_page';
 @import 'data_source_context';
 @import 'data_source_form';
diff --git a/web-frontend/modules/core/assets/scss/components/builder/element.scss b/web-frontend/modules/core/assets/scss/components/builder/element.scss
index 2cf78c340..c34329ede 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/element.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/element.scss
@@ -1,181 +1,19 @@
-.element__menu {
-  display: flex;
-  flex-wrap: nowrap;
-  border: solid 1px $color-neutral-400;
-  border-radius: 3px;
-  z-index: 2;
-
-  @include absolute(5px, 5px, auto, auto);
-}
-
-.element__insert {
-  @include center-text(26px, 10px);
-
-  display: block;
-  border-radius: 100%;
-  border: solid 1px $color-neutral-300;
-  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
-  color: $color-primary-900;
-  background-color: $white;
-
-  &:hover {
-    background-color: $color-neutral-50;
-    box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.32);
-  }
-
-  &--top,
-  &--bottom {
-    @include absolute(-13px, auto, auto, 50%);
-
-    margin-left: -12px;
-    z-index: 2;
-  }
-
-  &--bottom {
-    top: auto;
-    bottom: -12px;
-  }
-}
-
 .element {
-  position: relative;
-
-  .element__insert {
-    display: none;
-  }
-
-  .element__menu {
-    display: none;
-  }
-
-  &:hover {
-    .element__insert {
-      display: block;
-    }
-
-    .element__menu {
-      display: flex;
-    }
-  }
-
-  &:not(.element--active) {
-    cursor: pointer;
-  }
-
-  &.element--active {
-    cursor: inherit;
-
-    &::before {
-      @include absolute(0, 0, 0, 0);
-
-      content: '';
-      border: solid 1px $color-primary-500;
-      pointer-events: none;
-    }
-
-    .element__insert {
-      display: block;
-    }
-  }
-}
-
-.element__menu-item-description {
-  @include absolute(-25px, -2px, auto, auto);
-
-  display: none;
-  background-color: $color-neutral-900;
-  font-size: 11px;
-  color: $white;
-  line-height: 20px;
-  padding: 0 4px;
-  border-radius: 3px;
-  white-space: nowrap;
-}
-
-.element__menu-item {
-  @include center-text(24px, 9px);
-
-  position: relative;
-  background-color: $white;
-  color: $color-primary-900;
-
-  &:hover {
-    background-color: $color-neutral-100;
-
-    .element__menu-item-description {
-      display: block;
-    }
-  }
-
-  &:first-child {
-    border-top-left-radius: 3px;
-    border-bottom-left-radius: 3px;
-  }
-
-  &:last-child {
-    border-top-right-radius: 3px;
-    border-bottom-right-radius: 3px;
-  }
-
-  &.disabled {
-    cursor: inherit;
-    color: $color-neutral-300;
-
-    &:hover {
-      background-color: $white;
-    }
-  }
-}
-
-.element__component {
-  margin: 0;
-}
-
-.element__menu-duplicate-loading {
-  margin: 5px;
+  // this is a placeholder, the class will be added to every element component.
 }
 
 .element--no-value {
   opacity: 0.3;
 }
 
-.element--in-error::after {
-  @extend .fas;
-
-  @include fa-icon;
-  @include absolute(0, 0, auto, auto);
-
-  content: fa-content($fa-var-exclamation-circle);
-  pointer-events: none;
-  width: 20px;
-  height: 20px;
-  line-height: 20px;
-  font-size: 20px;
-  margin-right: 5px;
-  margin-top: 5px;
-  color: $color-error-300;
-}
-
 .element--alignment-left {
   justify-content: start;
-
-  .button--full-width {
-    text-align: left;
-  }
 }
 
 .element--alignment-center {
   justify-content: center;
-
-  .button--full-width {
-    text-align: center;
-  }
 }
 
 .element--alignment-right {
   justify-content: end;
-
-  .button--full-width {
-    text-align: right;
-  }
 }
diff --git a/web-frontend/modules/core/assets/scss/components/builder/element_preview.scss b/web-frontend/modules/core/assets/scss/components/builder/element_preview.scss
new file mode 100644
index 000000000..e3f6d8b31
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/builder/element_preview.scss
@@ -0,0 +1,149 @@
+.element-preview__menu {
+  display: flex;
+  flex-wrap: nowrap;
+  border: solid 1px $color-neutral-400;
+  border-radius: 3px;
+  z-index: 2;
+
+  @include absolute(5px, 5px, auto, auto);
+}
+
+.element-preview__insert {
+  @include center-text(26px, 10px);
+
+  display: block;
+  border-radius: 100%;
+  border: solid 1px $color-neutral-300;
+  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
+  color: $color-primary-900;
+  background-color: $white;
+
+  &:hover {
+    background-color: $color-neutral-50;
+    box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.32);
+  }
+
+  &--top,
+  &--bottom {
+    @include absolute(-13px, auto, auto, 50%);
+
+    margin-left: -12px;
+    z-index: 2;
+  }
+
+  &--bottom {
+    top: auto;
+    bottom: -12px;
+  }
+}
+
+.element-preview {
+  position: relative;
+
+  .element-preview__insert {
+    display: none;
+  }
+
+  .element-preview__menu {
+    display: none;
+  }
+
+  &:hover {
+    .element-preview__insert {
+      display: block;
+    }
+
+    .element-preview__menu {
+      display: flex;
+    }
+  }
+
+  &:not(.element-preview--active) {
+    cursor: pointer;
+  }
+
+  &.element-preview--active {
+    cursor: inherit;
+
+    &::before {
+      @include absolute(0, 0, 0, 0);
+
+      content: '';
+      border: solid 1px $color-primary-500;
+      pointer-events: none;
+    }
+
+    .element-preview__insert {
+      display: block;
+    }
+  }
+}
+
+.element-preview__menu-item-description {
+  @include absolute(-25px, -2px, auto, auto);
+
+  display: none;
+  background-color: $color-neutral-900;
+  font-size: 11px;
+  color: $white;
+  line-height: 20px;
+  padding: 0 4px;
+  border-radius: 3px;
+  white-space: nowrap;
+}
+
+.element-preview__menu-item {
+  @include center-text(24px, 9px);
+
+  position: relative;
+  background-color: $white;
+  color: $color-primary-900;
+
+  &:hover {
+    background-color: $color-neutral-100;
+
+    .element-preview__menu-item-description {
+      display: block;
+    }
+  }
+
+  &:first-child {
+    border-top-left-radius: 3px;
+    border-bottom-left-radius: 3px;
+  }
+
+  &:last-child {
+    border-top-right-radius: 3px;
+    border-bottom-right-radius: 3px;
+  }
+
+  &.disabled {
+    cursor: inherit;
+    color: $color-neutral-300;
+
+    &:hover {
+      background-color: $white;
+    }
+  }
+}
+
+.element-preview__menu-duplicate-loading {
+  margin: 5px;
+}
+
+.element-preview--in-error::after {
+  @extend .fas;
+
+  @include fa-icon;
+  @include absolute(0, 0, auto, auto);
+
+  content: fa-content($fa-var-exclamation-circle);
+  pointer-events: none;
+  width: 20px;
+  height: 20px;
+  line-height: 20px;
+  font-size: 20px;
+  margin-right: 5px;
+  margin-top: 5px;
+  color: $color-error-300;
+}
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/all.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/all.scss
index ed7eefbd6..37769fc4d 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/all.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/all.scss
@@ -1,2 +1,2 @@
-@import 'paragraphElementForm';
-@import 'linkElementForm';
+@import 'paragraph_element_form';
+@import 'link_element_form';
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/linkElementForm.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/link_element_form.scss
similarity index 63%
rename from web-frontend/modules/core/assets/scss/components/builder/elements/forms/linkElementForm.scss
rename to web-frontend/modules/core/assets/scss/components/builder/elements/forms/link_element_form.scss
index 0d21a82ec..9f5ba9c0a 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/linkElementForm.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/link_element_form.scss
@@ -10,3 +10,11 @@
   color: $color-neutral-500;
   margin-left: 5px;
 }
+
+.link-element-form__params-error {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  text-align: center;
+  align-items: center;
+}
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraphElementForm.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraphElementForm.scss
deleted file mode 100644
index e366adf8b..000000000
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraphElementForm.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.paragraph-element-form__value {
-  resize: vertical;
-  color: $color-primary-900;
-}
-
-.link-element-form__params-error {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-  text-align: center;
-  align-items: center;
-}
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraph_element_form.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraph_element_form.scss
new file mode 100644
index 000000000..79f38b144
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/forms/paragraph_element_form.scss
@@ -0,0 +1,4 @@
+.paragraph-element-form__value {
+  resize: vertical;
+  color: $color-primary-900;
+}
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/heading_element.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/heading_element.scss
index 3389a8a09..4a2343d96 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/heading_element.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/heading_element.scss
@@ -1,29 +1,29 @@
+.heading-element {
+  margin: 0;
+  color: $black;
+}
+
 h1.heading-element {
-  font-size: 24px;
-  padding: 32px 82px;
+  font-size: 30px;
 }
 
 h2.heading-element {
-  font-size: 20px;
-  padding: 28px 82px;
+  font-size: 26px;
 }
 
 h3.heading-element {
-  font-size: 18px;
-  padding: 24px 82px;
+  font-size: 22px;
 }
 
 h4.heading-element {
-  font-size: 16px;
-  padding: 20px 82px;
+  font-size: 18px;
 }
 
 h5.heading-element {
-  font-size: 15px;
-  padding: 18px 82px;
+  font-size: 14px;
 }
 
 h6.heading-element {
   font-size: 14px;
-  padding: 16px 82px;
+  font-style: italic;
 }
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/link_element.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/link_element.scss
index b7a582ae0..3956852f9 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/link_element.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/link_element.scss
@@ -1,13 +1,45 @@
 .link-element {
   display: flex;
-  padding: 5px 82px;
 }
 
 .link-element__link {
   font-size: 14px;
-  font-weight: 700;
-  padding: 0;
-  height: 32px;
-  line-height: 32px;
-  border: 1px solid transparent;
+  color: $black;
+  text-decoration: underline;
+}
+
+.link-element__button {
+  font-size: 14px;
+  cursor: pointer;
+  display: inline-block;
+  color: $white;
+  background-color: $black;
+  line-height: 28px;
+  padding: 0 12px;
+  border-radius: 3px;
+  border: none;
+  white-space: nowrap;
+  text-align: left;
+  text-decoration: none;
+
+  &:hover {
+    background-color: lighten($black, 10%);
+    text-decoration: none;
+  }
+
+  &:focus {
+    background-color: lighten($black, 20%);
+  }
+
+  &--full-width {
+    width: 100%;
+  }
+
+  .element--alignment-center & {
+    text-align: center;
+  }
+
+  .element--alignment-right & {
+    text-align: right;
+  }
 }
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/paragraph_element.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/paragraph_element.scss
index 600604eef..b095e5f1c 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/paragraph_element.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/paragraph_element.scss
@@ -1,5 +1,5 @@
 .paragraph-element {
   font-size: 14px;
-  padding: 2px 82px;
   margin: 0;
+  color: $black;
 }
diff --git a/web-frontend/modules/core/assets/scss/components/builder/page.scss b/web-frontend/modules/core/assets/scss/components/builder/page_editor.scss
similarity index 100%
rename from web-frontend/modules/core/assets/scss/components/builder/page.scss
rename to web-frontend/modules/core/assets/scss/components/builder/page_editor.scss
diff --git a/web-frontend/modules/core/assets/scss/components/builder/page_root_element.scss b/web-frontend/modules/core/assets/scss/components/builder/page_root_element.scss
new file mode 100644
index 000000000..4d0f2774c
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/builder/page_root_element.scss
@@ -0,0 +1,5 @@
+.page-root-element__inner {
+  padding: 0 20px;
+  margin: 0 auto;
+  max-width: $builder-page-max-width;
+}
diff --git a/web-frontend/modules/core/assets/scss/components/builder/public_page.scss b/web-frontend/modules/core/assets/scss/components/builder/public_page.scss
new file mode 100644
index 000000000..138c92b1b
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/builder/public_page.scss
@@ -0,0 +1,3 @@
+.public-page {
+  background-color: $white;
+}
diff --git a/web-frontend/modules/core/assets/scss/variables.scss b/web-frontend/modules/core/assets/scss/variables.scss
index 95b242418..04adbcaa4 100644
--- a/web-frontend/modules/core/assets/scss/variables.scss
+++ b/web-frontend/modules/core/assets/scss/variables.scss
@@ -102,3 +102,5 @@ $file-field-modal-body-nav-width: 120px !default;
 $file-field-modal-foot-height: 108px !default;
 
 $dashboard-breakpoint: 1100px;
+
+$builder-page-max-width: 1280px;
diff --git a/web-frontend/modules/core/locales/en.json b/web-frontend/modules/core/locales/en.json
index 4b151cf7c..6db557c4c 100644
--- a/web-frontend/modules/core/locales/en.json
+++ b/web-frontend/modules/core/locales/en.json
@@ -524,5 +524,12 @@
     },
     "dropdown": {
         "empty": "No items available"
+    },
+    "styleSidePanel": {
+        "paddingTop": "Padding top",
+        "paddingBottom": "Padding bottom"
+    },
+    "styleBoxForm": {
+        "paddingError": "The value must be an integer between 0 and 200."
     }
 }