From 3c05fc53cec799d33b59c9596a06a119138b0566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Pardou?= <jeremie@baserow.io>
Date: Fri, 22 Dec 2023 10:06:35 +0000
Subject: [PATCH] Resolve "Allow to set more styles on non Root page elements"

---
 .../builder/api/domains/serializers.py        |   6 +
 .../builder/api/elements/serializers.py       |  18 +++
 .../contrib/builder/elements/element_types.py |  67 +++++++--
 .../contrib/builder/elements/handler.py       |  12 ++
 .../contrib/builder/elements/models.py        |  58 +++++++-
 .../migrations/0035_add_more_styles.py        | 131 ++++++++++++++++++
 backend/src/baserow/contrib/builder/types.py  |   6 +
 .../builder/test_builder_application_type.py  |  39 ++++++
 web-frontend/locales/en.json                  |   3 +-
 .../components/elements/ElementPreview.vue    |   6 +-
 .../elements/components/ButtonElement.vue     |   8 +-
 .../components/FormContainerElement.vue       |   6 +-
 .../elements/components/HeadingElement.vue    |   1 +
 .../elements/components/LinkElement.vue       |   8 +-
 .../elements/components/TableElement.vue      |   6 +-
 .../forms/general/ButtonElementForm.vue       |  21 +--
 .../forms/general/ColumnElementForm.vue       |   3 +-
 .../general/FormContainerElementForm.vue      |  13 +-
 .../forms/general/HeadingElementForm.vue      |  44 +++---
 .../forms/general/ImageElementForm.vue        |   8 +-
 .../forms/general/InputTextElementForm.vue    |   8 +-
 .../forms/general/LinkElementForm.vue         |  17 ++-
 .../forms/general/ParagraphElementForm.vue    |   9 +-
 .../forms/general/TableElementForm.vue        |  14 +-
 .../forms/general/settings/FontSelector.vue   |  31 +----
 .../forms/style/DefaultStyleForm.vue          | 112 ++++++---------
 .../components/forms/style/StyleBoxForm.vue   | 106 ++++++--------
 .../builder/components/page/PageContent.vue   |   6 +-
 .../builder/components/page/PageElement.vue   |  31 +++--
 .../components/page/PageRootElement.vue       |  61 ++------
 .../components/theme/MainThemeConfigBlock.vue |  63 +++------
 web-frontend/modules/builder/elementTypes.js  |  25 ++--
 web-frontend/modules/builder/locales/en.json  |  66 +++++----
 .../modules/builder/mixins/element.js         |   7 +
 .../modules/builder/mixins/elementForm.js     |  29 ++++
 .../modules/builder/mixins/pageElement.js     |  68 ++++++++-
 .../modules/builder/mixins/styleForm.js       |  50 +++++--
 .../core/assets/scss/components/all.scss      |   1 +
 .../assets/scss/components/builder/all.scss   |   1 -
 .../scss/components/builder/element.scss      |  32 ++++-
 .../elements/link-button-element-button.scss  |   4 +-
 .../components/builder/page_root_element.scss |   5 -
 .../assets/scss/components/color_input.scss   |  14 +-
 .../scss/components/color_input_group.scss    |   8 ++
 .../modules/core/components/ColorInput.vue    |  41 ++++++
 .../core/components/ColorInputGroup.vue       |  53 +++++++
 .../modules/core/components/FormInput.vue     |   1 +
 web-frontend/modules/core/plugins/global.js   |   4 +
 web-frontend/modules/core/utils/colors.js     |   3 +
 49 files changed, 891 insertions(+), 443 deletions(-)
 create mode 100644 backend/src/baserow/contrib/builder/migrations/0035_add_more_styles.py
 create mode 100644 web-frontend/modules/builder/mixins/elementForm.js
 delete mode 100644 web-frontend/modules/core/assets/scss/components/builder/page_root_element.scss
 create mode 100644 web-frontend/modules/core/assets/scss/components/color_input_group.scss
 create mode 100644 web-frontend/modules/core/components/ColorInput.vue
 create mode 100644 web-frontend/modules/core/components/ColorInputGroup.vue

diff --git a/backend/src/baserow/contrib/builder/api/domains/serializers.py b/backend/src/baserow/contrib/builder/api/domains/serializers.py
index 460102a8e..bfde07823 100644
--- a/backend/src/baserow/contrib/builder/api/domains/serializers.py
+++ b/backend/src/baserow/contrib/builder/api/domains/serializers.py
@@ -105,6 +105,12 @@ class PublicElementSerializer(serializers.ModelSerializer):
             "style_border_bottom_color",
             "style_border_bottom_size",
             "style_padding_bottom",
+            "style_border_left_color",
+            "style_border_left_size",
+            "style_padding_left",
+            "style_border_right_color",
+            "style_border_right_size",
+            "style_padding_right",
             "style_background",
             "style_background_color",
             "style_width",
diff --git a/backend/src/baserow/contrib/builder/api/elements/serializers.py b/backend/src/baserow/contrib/builder/api/elements/serializers.py
index ffd5f0370..7daaf00f5 100644
--- a/backend/src/baserow/contrib/builder/api/elements/serializers.py
+++ b/backend/src/baserow/contrib/builder/api/elements/serializers.py
@@ -57,6 +57,12 @@ class ElementSerializer(serializers.ModelSerializer):
             "style_border_bottom_color",
             "style_border_bottom_size",
             "style_padding_bottom",
+            "style_border_left_color",
+            "style_border_left_size",
+            "style_padding_left",
+            "style_border_right_color",
+            "style_border_right_size",
+            "style_padding_right",
             "style_background",
             "style_background_color",
             "style_width",
@@ -106,6 +112,12 @@ class CreateElementSerializer(serializers.ModelSerializer):
             "style_border_bottom_color",
             "style_border_bottom_size",
             "style_padding_bottom",
+            "style_border_left_color",
+            "style_border_left_size",
+            "style_padding_left",
+            "style_border_right_color",
+            "style_border_right_size",
+            "style_padding_right",
             "style_background",
             "style_background_color",
             "style_width",
@@ -122,6 +134,12 @@ class UpdateElementSerializer(serializers.ModelSerializer):
             "style_border_bottom_color",
             "style_border_bottom_size",
             "style_padding_bottom",
+            "style_border_left_color",
+            "style_border_left_size",
+            "style_padding_left",
+            "style_border_right_color",
+            "style_border_right_size",
+            "style_padding_right",
             "style_background",
             "style_background_color",
             "style_width",
diff --git a/backend/src/baserow/contrib/builder/elements/element_types.py b/backend/src/baserow/contrib/builder/elements/element_types.py
index e01a551ac..eb5bfdfad 100644
--- a/backend/src/baserow/contrib/builder/elements/element_types.py
+++ b/backend/src/baserow/contrib/builder/elements/element_types.py
@@ -398,6 +398,7 @@ class HeadingElementType(ElementType):
 
     class SerializedDict(ElementDict):
         value: BaserowFormula
+        font_color: str
         level: int
 
     @property
@@ -417,6 +418,12 @@ class HeadingElementType(ElementType):
                 max_value=6,
                 default=1,
             ),
+            "font_color": serializers.CharField(
+                max_length=20,
+                required=False,
+                allow_blank=True,
+                help_text="Heading font color.",
+            ),
         }
 
         return overrides
@@ -500,6 +507,7 @@ class LinkElementType(ElementType):
         "target",
         "width",
         "alignment",
+        "button_color",
     ]
     allowed_fields = [
         "value",
@@ -512,6 +520,7 @@ class LinkElementType(ElementType):
         "target",
         "width",
         "alignment",
+        "button_color",
     ]
 
     class SerializedDict(ElementDict):
@@ -524,6 +533,7 @@ class LinkElementType(ElementType):
         target: str
         width: str
         alignment: str
+        button_color: str
 
     def deserialize_property(
         self, prop_name: str, value: Any, id_mapping: Dict[str, Any]
@@ -600,6 +610,12 @@ class LinkElementType(ElementType):
                 help_text=LinkElement._meta.get_field("alignment").help_text,
                 required=False,
             ),
+            "button_color": serializers.CharField(
+                max_length=20,
+                required=False,
+                default="primary",
+                help_text="Button color.",
+            ),
         }
         return overrides
 
@@ -846,13 +862,14 @@ class InputTextElementType(InputElementType):
 class ButtonElementType(ElementType):
     type = "button"
     model_class = ButtonElement
-    allowed_fields = ["value", "alignment", "width"]
-    serializer_field_names = ["value", "alignment", "width"]
+    allowed_fields = ["value", "alignment", "width", "button_color"]
+    serializer_field_names = ["value", "alignment", "width", "button_color"]
 
     class SerializedDict(ElementDict):
         value: BaserowFormula
         width: str
         alignment: str
+        button_color: str
 
     @property
     def serializer_field_overrides(self):
@@ -875,6 +892,12 @@ class ButtonElementType(ElementType):
                 help_text=ButtonElement._meta.get_field("alignment").help_text,
                 required=False,
             ),
+            "button_color": serializers.CharField(
+                max_length=20,
+                required=False,
+                default="primary",
+                help_text="Button color.",
+            ),
         }
 
         return overrides
@@ -896,6 +919,29 @@ class TableElementType(CollectionElementType):
     type = "table"
     model_class = TableElement
 
+    class SerializedDict(CollectionElementType.SerializedDict):
+        button_color: str
+
+    @property
+    def allowed_fields(self):
+        return super().allowed_fields + ["button_color"]
+
+    @property
+    def serializer_field_names(self):
+        return super().serializer_field_names + ["button_color"]
+
+    @property
+    def serializer_field_overrides(self):
+        return {
+            **super().serializer_field_overrides,
+            "button_color": serializers.CharField(
+                max_length=20,
+                required=False,
+                default="primary",
+                help_text="Button color.",
+            ),
+        }
+
     def get_pytest_params(self, pytest_data_fixture) -> Dict[str, Any]:
         return {"data_source_id": None}
 
@@ -903,11 +949,12 @@ class TableElementType(CollectionElementType):
 class FormContainerElementType(ContainerElementType):
     type = "form_container"
     model_class = FormContainerElement
-    allowed_fields = ["submit_button_label"]
-    serializer_field_names = ["submit_button_label"]
+    allowed_fields = ["submit_button_label", "button_color"]
+    serializer_field_names = ["submit_button_label", "button_color"]
 
     class SerializedDict(ElementDict):
         submit_button_label: BaserowFormula
+        button_color: str
 
     def get_pytest_params(self, pytest_data_fixture) -> Dict[str, Any]:
         return {"submit_button_label": "'Submit'"}
@@ -916,7 +963,7 @@ class FormContainerElementType(ContainerElementType):
     def serializer_field_overrides(self):
         from baserow.core.formula.serializers import FormulaSerializerField
 
-        overrides = {
+        return {
             "submit_button_label": FormulaSerializerField(
                 help_text=FormContainerElement._meta.get_field(
                     "submit_button_label"
@@ -924,11 +971,15 @@ class FormContainerElementType(ContainerElementType):
                 required=False,
                 allow_blank=True,
                 default="",
-            )
+            ),
+            "button_color": serializers.CharField(
+                max_length=20,
+                required=False,
+                default="primary",
+                help_text="Button color.",
+            ),
         }
 
-        return overrides
-
     @property
     def child_types_allowed(self) -> List[str]:
         child_types_allowed = []
diff --git a/backend/src/baserow/contrib/builder/elements/handler.py b/backend/src/baserow/contrib/builder/elements/handler.py
index 5cedc9348..ba634317e 100644
--- a/backend/src/baserow/contrib/builder/elements/handler.py
+++ b/backend/src/baserow/contrib/builder/elements/handler.py
@@ -34,6 +34,12 @@ class ElementHandler:
         "style_border_bottom_color",
         "style_border_bottom_size",
         "style_padding_bottom",
+        "style_border_left_color",
+        "style_border_left_size",
+        "style_padding_left",
+        "style_border_right_color",
+        "style_border_right_size",
+        "style_padding_right",
         "style_background",
         "style_background_color",
         "style_width",
@@ -48,6 +54,12 @@ class ElementHandler:
         "style_border_bottom_color",
         "style_border_bottom_size",
         "style_padding_bottom",
+        "style_border_left_color",
+        "style_border_left_size",
+        "style_padding_left",
+        "style_border_right_color",
+        "style_border_right_size",
+        "style_padding_right",
         "style_background",
         "style_background_color",
         "style_width",
diff --git a/backend/src/baserow/contrib/builder/elements/models.py b/backend/src/baserow/contrib/builder/elements/models.py
index a2cfcd755..68bd30403 100644
--- a/backend/src/baserow/contrib/builder/elements/models.py
+++ b/backend/src/baserow/contrib/builder/elements/models.py
@@ -109,7 +109,7 @@ class Element(
         default=0, help_text="Pixel height of the top border."
     )
     style_padding_top = models.PositiveIntegerField(
-        default=10, help_text="Padding height of the top border."
+        default=10, help_text="Padding size of the top border."
     )
 
     style_border_bottom_color = models.CharField(
@@ -122,7 +122,33 @@ class Element(
         default=0, help_text="Pixel height of the bottom border."
     )
     style_padding_bottom = models.PositiveIntegerField(
-        default=10, help_text="Padding height of the bottom border."
+        default=10, help_text="Padding size of the bottom border."
+    )
+
+    style_border_left_color = models.CharField(
+        max_length=20,
+        default="border",
+        blank=True,
+        help_text="Left border color",
+    )
+    style_border_left_size = models.PositiveIntegerField(
+        default=0, help_text="Pixel height of the left border."
+    )
+    style_padding_left = models.PositiveIntegerField(
+        default=20, help_text="Padding size of the left border."
+    )
+
+    style_border_right_color = models.CharField(
+        max_length=20,
+        default="border",
+        blank=True,
+        help_text="Right border color",
+    )
+    style_border_right_size = models.PositiveIntegerField(
+        default=0, help_text="Pixel height of the right border."
+    )
+    style_padding_right = models.PositiveIntegerField(
+        default=20, help_text="Padding size of the right border."
     )
 
     style_background = models.CharField(
@@ -281,7 +307,7 @@ class ColumnElement(ContainerElement):
         ],
     )
     column_gap = models.IntegerField(
-        default=30,
+        default=20,
         help_text="The amount of space between the columns.",
         validators=[
             MinValueValidator(0, message="Value cannot be less than 0."),
@@ -391,6 +417,12 @@ class LinkElement(Element):
         max_length=10,
         default=HorizontalAlignments.LEFT,
     )
+    button_color = models.CharField(
+        max_length=20,
+        default="primary",
+        blank=True,
+        help_text="The color of the button",
+    )
 
 
 class ImageElement(Element):
@@ -479,6 +511,12 @@ class ButtonElement(Element):
         max_length=10,
         default=HorizontalAlignments.LEFT,
     )
+    button_color = models.CharField(
+        max_length=20,
+        default="primary",
+        blank=True,
+        help_text="The color of the button",
+    )
 
 
 class CollectionField(models.Model):
@@ -542,6 +580,13 @@ class TableElement(CollectionElement):
     A table element
     """
 
+    button_color = models.CharField(
+        max_length=20,
+        default="primary",
+        blank=True,
+        help_text="The color of the button",
+    )
+
 
 class FormContainerElement(ContainerElement):
     """
@@ -550,6 +595,13 @@ class FormContainerElement(ContainerElement):
 
     submit_button_label = FormulaField(default="")
 
+    button_color = models.CharField(
+        max_length=20,
+        default="primary",
+        blank=True,
+        help_text="The color of the button",
+    )
+
 
 class DropdownElement(Element):
     label = FormulaField(
diff --git a/backend/src/baserow/contrib/builder/migrations/0035_add_more_styles.py b/backend/src/baserow/contrib/builder/migrations/0035_add_more_styles.py
new file mode 100644
index 000000000..58d15364e
--- /dev/null
+++ b/backend/src/baserow/contrib/builder/migrations/0035_add_more_styles.py
@@ -0,0 +1,131 @@
+# Generated by Django 3.2.21 on 2023-12-19 13:35
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("builder", "0034_dropdownelement_dropdownelementoption"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="buttonelement",
+            name="button_color",
+            field=models.CharField(
+                blank=True,
+                default="primary",
+                help_text="The color of the button",
+                max_length=20,
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_border_left_color",
+            field=models.CharField(
+                blank=True,
+                default="border",
+                help_text="Left border color",
+                max_length=20,
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_border_left_size",
+            field=models.PositiveIntegerField(
+                default=0, help_text="Pixel height of the left border."
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_border_right_color",
+            field=models.CharField(
+                blank=True,
+                default="border",
+                help_text="Right border color",
+                max_length=20,
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_border_right_size",
+            field=models.PositiveIntegerField(
+                default=0, help_text="Pixel height of the right border."
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_padding_left",
+            field=models.PositiveIntegerField(
+                default=20, help_text="Padding size of the left border."
+            ),
+        ),
+        migrations.AddField(
+            model_name="element",
+            name="style_padding_right",
+            field=models.PositiveIntegerField(
+                default=20, help_text="Padding size of the right border."
+            ),
+        ),
+        migrations.AddField(
+            model_name="formcontainerelement",
+            name="button_color",
+            field=models.CharField(
+                blank=True,
+                default="primary",
+                help_text="The color of the button",
+                max_length=20,
+            ),
+        ),
+        migrations.AddField(
+            model_name="linkelement",
+            name="button_color",
+            field=models.CharField(
+                blank=True,
+                default="primary",
+                help_text="The color of the button",
+                max_length=20,
+            ),
+        ),
+        migrations.AddField(
+            model_name="tableelement",
+            name="button_color",
+            field=models.CharField(
+                blank=True,
+                default="primary",
+                help_text="The color of the button",
+                max_length=20,
+            ),
+        ),
+        migrations.AlterField(
+            model_name="columnelement",
+            name="column_gap",
+            field=models.IntegerField(
+                default=20,
+                help_text="The amount of space between the columns.",
+                validators=[
+                    django.core.validators.MinValueValidator(
+                        0, message="Value cannot be less than 0."
+                    ),
+                    django.core.validators.MaxValueValidator(
+                        2000, message="Value cannot be greater than 2000."
+                    ),
+                ],
+            ),
+        ),
+        migrations.AlterField(
+            model_name="element",
+            name="style_padding_bottom",
+            field=models.PositiveIntegerField(
+                default=10, help_text="Padding size of the bottom border."
+            ),
+        ),
+        migrations.AlterField(
+            model_name="element",
+            name="style_padding_top",
+            field=models.PositiveIntegerField(
+                default=10, help_text="Padding size of the top border."
+            ),
+        ),
+    ]
diff --git a/backend/src/baserow/contrib/builder/types.py b/backend/src/baserow/contrib/builder/types.py
index 8c1605809..0d751541f 100644
--- a/backend/src/baserow/contrib/builder/types.py
+++ b/backend/src/baserow/contrib/builder/types.py
@@ -19,6 +19,12 @@ class ElementDict(TypedDict):
     style_border_bottom_color: str
     style_border_bottom_size: int
     style_padding_bottom: int
+    style_border_left_color: str
+    style_border_left_size: int
+    style_padding_left: int
+    style_border_right_color: str
+    style_border_right_size: int
+    style_padding_right: int
     style_background: str
     style_background_color: str
     style_width: str
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 535d5f0f8..7301c7a67 100644
--- a/backend/tests/baserow/contrib/builder/test_builder_application_type.py
+++ b/backend/tests/baserow/contrib/builder/test_builder_application_type.py
@@ -128,14 +128,21 @@ def test_builder_application_export(data_fixture):
                         "order": str(element1.order),
                         "parent_element_id": None,
                         "place_in_container": None,
+                        "font_color": "default",
                         "style_background_color": "#ffffffff",
                         "style_border_bottom_color": "border",
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "value": element1.value,
                         "level": element1.level,
@@ -151,9 +158,15 @@ def test_builder_application_export(data_fixture):
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "value": element2.value,
                     },
@@ -167,9 +180,15 @@ def test_builder_application_export(data_fixture):
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "order": str(element_container.order),
                         "column_amount": 3,
@@ -186,9 +205,15 @@ def test_builder_application_export(data_fixture):
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "order": str(element_inside_container.order),
                         "value": element_inside_container.value,
@@ -241,14 +266,21 @@ def test_builder_application_export(data_fixture):
                         "order": str(element3.order),
                         "parent_element_id": None,
                         "place_in_container": None,
+                        "font_color": "default",
                         "style_background_color": "#ffffffff",
                         "style_border_bottom_color": "border",
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "value": element3.value,
                         "level": element3.level,
@@ -257,6 +289,7 @@ def test_builder_application_export(data_fixture):
                         "id": element4.id,
                         "type": "table",
                         "order": str(element4.order),
+                        "button_color": "primary",
                         "parent_element_id": None,
                         "place_in_container": None,
                         "style_background_color": "#ffffffff",
@@ -264,9 +297,15 @@ def test_builder_application_export(data_fixture):
                         "style_border_bottom_size": 0,
                         "style_border_top_color": "border",
                         "style_border_top_size": 0,
+                        "style_border_left_color": "border",
+                        "style_border_left_size": 0,
+                        "style_border_right_color": "border",
+                        "style_border_right_size": 0,
                         "style_width": "normal",
                         "style_padding_top": 10,
                         "style_padding_bottom": 10,
+                        "style_padding_left": 20,
+                        "style_padding_right": 20,
                         "style_background": "none",
                         "items_per_page": 42,
                         "data_source_id": element4.data_source.id,
diff --git a/web-frontend/locales/en.json b/web-frontend/locales/en.json
index 103a826d3..1ae7006f0 100644
--- a/web-frontend/locales/en.json
+++ b/web-frontend/locales/en.json
@@ -73,7 +73,8 @@
         "requiredField": "This field is required.",
         "integerField": "The field must be an integer.",
         "minValueField": "The field must be greater than or equal to {min}.",
-        "maxValueField": "The field must be less than or equal to {max}."
+        "maxValueField": "The field must be less than or equal to {max}.",
+        "minMaxValueField": "The field value must be between {min} and {max}."
     },
     "permission": {
         "admin": "Admin",
diff --git a/web-frontend/modules/builder/components/elements/ElementPreview.vue b/web-frontend/modules/builder/components/elements/ElementPreview.vue
index 6d59c40c1..6cb20ba4a 100644
--- a/web-frontend/modules/builder/components/elements/ElementPreview.vue
+++ b/web-frontend/modules/builder/components/elements/ElementPreview.vue
@@ -25,11 +25,11 @@
       @select-parent="actionSelectElement({ element: parentElement })"
     />
 
-    <PageRootElement
+    <PageElement
       v-if="isRootElement"
       :element="element"
       :mode="mode"
-    ></PageRootElement>
+    ></PageElement>
     <PageElement v-else :element="element" :mode="mode" />
 
     <InsertElementButton
@@ -58,7 +58,6 @@ import { PLACEMENTS } from '@baserow/modules/builder/enums'
 import AddElementModal from '@baserow/modules/builder/components/elements/AddElementModal'
 import { notifyIf } from '@baserow/modules/core/utils/error'
 import { mapActions, mapGetters } from 'vuex'
-import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
 import { checkIntermediateElements } from '@baserow/modules/core/utils/dom'
 
 export default {
@@ -68,7 +67,6 @@ export default {
     ElementMenu,
     InsertElementButton,
     PageElement,
-    PageRootElement,
   },
   inject: ['builder', 'page', 'mode'],
   props: {
diff --git a/web-frontend/modules/builder/components/elements/components/ButtonElement.vue b/web-frontend/modules/builder/components/elements/components/ButtonElement.vue
index a6626dc23..08ffc9596 100644
--- a/web-frontend/modules/builder/components/elements/components/ButtonElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/ButtonElement.vue
@@ -1,5 +1,11 @@
 <template>
-  <div class="button-element" :class="classes">
+  <div
+    class="button-element"
+    :class="classes"
+    :style="{
+      '--button-color': resolveColor(element.button_color, colorVariables),
+    }"
+  >
     <button
       class="link-button-element-button"
       :class="{
diff --git a/web-frontend/modules/builder/components/elements/components/FormContainerElement.vue b/web-frontend/modules/builder/components/elements/components/FormContainerElement.vue
index 9c9f2a875..4423d77ea 100644
--- a/web-frontend/modules/builder/components/elements/components/FormContainerElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/FormContainerElement.vue
@@ -1,5 +1,9 @@
 <template>
-  <div>
+  <div
+    :style="{
+      '--button-color': resolveColor(element.button_color, colorVariables),
+    }"
+  >
     <div v-if="mode === 'editing' && children.length === 0">
       <AddElementZone @add-element="showAddElementModal"></AddElementZone>
       <AddElementModal
diff --git a/web-frontend/modules/builder/components/elements/components/HeadingElement.vue b/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
index 2d296f8ca..0c2dfdf87 100644
--- a/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/HeadingElement.vue
@@ -5,6 +5,7 @@
     :class="{ 'element--no-value': !resolvedValue }"
     :style="{
       '--color': resolveColor(element.font_color, headingColorVariables),
+      '--font-size': `${builder.theme[`heading_${element.level}_font_size`]}px`,
     }"
   >
     {{ resolvedValue || $t('headingElement.noValue') }}
diff --git a/web-frontend/modules/builder/components/elements/components/LinkElement.vue b/web-frontend/modules/builder/components/elements/components/LinkElement.vue
index b840a4a4a..8340fc293 100644
--- a/web-frontend/modules/builder/components/elements/components/LinkElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/LinkElement.vue
@@ -1,5 +1,11 @@
 <template>
-  <div class="link-element" :class="classes">
+  <div
+    class="link-element"
+    :class="classes"
+    :style="{
+      '--button-color': resolveColor(element.button_color, colorVariables),
+    }"
+  >
     <a
       :class="{
         'link-element__link': element.variant !== 'button',
diff --git a/web-frontend/modules/builder/components/elements/components/TableElement.vue b/web-frontend/modules/builder/components/elements/components/TableElement.vue
index 6ced799c6..a0c05a71a 100644
--- a/web-frontend/modules/builder/components/elements/components/TableElement.vue
+++ b/web-frontend/modules/builder/components/elements/components/TableElement.vue
@@ -1,5 +1,9 @@
 <template>
-  <div>
+  <div
+    :style="{
+      '--button-color': resolveColor(element.button_color, colorVariables),
+    }"
+  >
     <BaserowTable :fields="element.fields" :rows="rows">
       <template #cell-content="{ field, value }">
         <component
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/ButtonElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/ButtonElementForm.vue
index e504cc31e..77fb86f0e 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/ButtonElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/ButtonElementForm.vue
@@ -12,20 +12,21 @@
     <FormElement class="control">
       <WidthSelector v-model="values.width" />
     </FormElement>
+    <ColorInputGroup
+      v-model="values.button_color"
+      :label="$t('buttonElementForm.buttonColor')"
+      :color-variables="colorVariables"
+    />
   </form>
 </template>
 
 <script>
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
-import form from '@baserow/modules/core/mixins/form'
-import {
-  DATA_PROVIDERS_ALLOWED_ELEMENTS,
-  HORIZONTAL_ALIGNMENTS,
-  WIDTHS,
-} from '@baserow/modules/builder/enums'
+import { HORIZONTAL_ALIGNMENTS, WIDTHS } from '@baserow/modules/builder/enums'
 import HorizontalAlignmentsSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/HorizontalAlignmentsSelector'
-
 import WidthSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/WidthSelector'
+import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'ButtonElementForm',
@@ -34,7 +35,7 @@ export default {
     ApplicationBuilderFormulaInputGroup,
     HorizontalAlignmentsSelector,
   },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
@@ -45,7 +46,9 @@ export default {
     }
   },
   computed: {
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
+    colorVariables() {
+      return themeToColorVariables(this.builder.theme)
+    },
   },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/ColumnElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/ColumnElementForm.vue
index db450ee15..65756d95e 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/ColumnElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/ColumnElementForm.vue
@@ -48,13 +48,14 @@ import form from '@baserow/modules/core/mixins/form'
 import { VERTICAL_ALIGNMENTS } from '@baserow/modules/builder/enums'
 import { required, integer, minValue, maxValue } from 'vuelidate/lib/validators'
 import VerticalAlignmentSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/VerticalAlignmentSelector'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'ColumnElementForm',
   components: {
     VerticalAlignmentSelector,
   },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue
index d31075fbb..353dc1cdd 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue
@@ -6,18 +6,22 @@
       :label="$t('formContainerElementForm.submitButtonLabel')"
       :placeholder="$t('formContainerElementForm.submitButtonPlaceholder')"
     />
+    <ColorInputGroup
+      v-model="values.button_color"
+      :label="$t('formContainerElementForm.buttonColor')"
+      :color-variables="colorVariables"
+    />
   </form>
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup.vue'
-import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'FormContainerElementForm',
   components: { ApplicationBuilderFormulaInputGroup },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
@@ -25,8 +29,5 @@ export default {
       },
     }
   },
-  computed: {
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
-  },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/HeadingElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/HeadingElementForm.vue
index 15bf31fb1..c785c2e4f 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/HeadingElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/HeadingElementForm.vue
@@ -1,22 +1,17 @@
 <template>
   <form @submit.prevent @keydown.enter.prevent>
-    <FormElement class="control">
-      <label class="control__label">
-        {{ $t('headingElementForm.levelTitle') }}
-      </label>
-      <div class="control__elements">
-        <Dropdown v-model="values.level" :show-search="false">
-          <DropdownItem
-            v-for="level in levels"
-            :key="level.value"
-            :name="level.name"
-            :value="level.value"
-          >
-            {{ level.name }}
-          </DropdownItem>
-        </Dropdown>
-      </div>
-    </FormElement>
+    <FormGroup :label="$t('headingElementForm.levelTitle')">
+      <Dropdown v-model="values.level" :show-search="false">
+        <DropdownItem
+          v-for="level in levels"
+          :key="level.value"
+          :name="level.name"
+          :value="level.value"
+        >
+          {{ level.name }}
+        </DropdownItem>
+      </Dropdown>
+    </FormGroup>
     <ApplicationBuilderFormulaInputGroup
       v-model="values.value"
       :label="$t('headingElementForm.textTitle')"
@@ -32,22 +27,24 @@
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
-import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
 import headingElement from '@baserow/modules/builder/mixins/headingElement'
 import FontSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/FontSelector'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'HeaderElementForm',
-  components: { FontSelector, ApplicationBuilderFormulaInputGroup },
-  mixins: [form, headingElement],
-  inject: ['builder'],
+  components: {
+    FontSelector,
+    ApplicationBuilderFormulaInputGroup,
+  },
+  mixins: [elementForm, headingElement],
   data() {
     return {
       values: {
         value: '',
         level: 1,
+        font_color: '',
       },
       levels: [...Array(6).keys()].map((level) => ({
         name: this.$t('headingElementForm.headingName', { level: level + 1 }),
@@ -55,8 +52,5 @@ export default {
       })),
     }
   },
-  computed: {
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
-  },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/ImageElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/ImageElementForm.vue
index aac54bf20..74431e689 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/ImageElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/ImageElementForm.vue
@@ -46,7 +46,6 @@
     <FormElement
       v-if="values.image_source_type === IMAGE_SOURCE_TYPES.URL"
       class="control"
-      :error="fieldHasErrors('image_url')"
     >
       <div class="control__elements">
         <div class="control__description">
@@ -54,7 +53,6 @@
         </div>
         <ApplicationBuilderFormulaInputGroup
           v-model="values.image_url"
-          :class="{ 'input--error': fieldHasErrors('image_url') }"
           :placeholder="$t('elementForms.urlInputPlaceholder')"
           :data-providers-allowed="DATA_PROVIDERS_ALLOWED_ELEMENTS"
         />
@@ -82,9 +80,7 @@
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
 import {
-  DATA_PROVIDERS_ALLOWED_ELEMENTS,
   HORIZONTAL_ALIGNMENTS,
   IMAGE_SOURCE_TYPES,
 } from '@baserow/modules/builder/enums'
@@ -93,6 +89,7 @@ import UserFilesModal from '@baserow/modules/core/components/files/UserFilesModa
 import { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
 import HorizontalAlignmentSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/HorizontalAlignmentsSelector'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup.vue'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'ImageElementForm',
@@ -101,7 +98,7 @@ export default {
     HorizontalAlignmentSelector,
     UserFilesModal,
   },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
@@ -120,7 +117,6 @@ export default {
     IMAGE_FILE_TYPES() {
       return IMAGE_FILE_TYPES
     },
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
   },
   methods: {
     openFileUploadModal() {
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/InputTextElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/InputTextElementForm.vue
index 603bc026c..fa7a168e4 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/InputTextElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/InputTextElementForm.vue
@@ -32,12 +32,12 @@
 <script>
 import form from '@baserow/modules/core/mixins/form'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup.vue'
-import { DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS } from '@baserow/modules/builder/enums'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'InputTextElementForm',
   components: { ApplicationBuilderFormulaInputGroup },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
@@ -48,10 +48,6 @@ export default {
       },
     }
   },
-  computed: {
-    DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS: () =>
-      DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS,
-  },
   methods: {
     emitChange(newValues) {
       if (this.isFormValid()) {
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/LinkElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/LinkElementForm.vue
index cc938f803..6c2ede55b 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/LinkElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/LinkElementForm.vue
@@ -113,21 +113,22 @@
     <FormElement v-if="values.variant === 'button'" class="control">
       <WidthSelector v-model="values.width" />
     </FormElement>
+    <ColorInputGroup
+      v-model="values.button_color"
+      :label="$t('linkElementForm.buttonColor')"
+      :color-variables="colorVariables"
+    />
   </form>
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
 import { LinkElementType } from '@baserow/modules/builder/elementTypes'
 import HorizontalAlignmentSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/HorizontalAlignmentsSelector'
-import {
-  DATA_PROVIDERS_ALLOWED_ELEMENTS,
-  HORIZONTAL_ALIGNMENTS,
-  WIDTHS,
-} from '@baserow/modules/builder/enums'
+import { HORIZONTAL_ALIGNMENTS, WIDTHS } from '@baserow/modules/builder/enums'
 import WidthSelector from '@baserow/modules/builder/components/elements/components/forms/general/settings/WidthSelector'
 import { PageParameterDataProviderType } from '@baserow/modules/builder/dataProviderTypes'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'LinkElementForm',
@@ -136,8 +137,7 @@ export default {
     ApplicationBuilderFormulaInputGroup,
     HorizontalAlignmentSelector,
   },
-  mixins: [form],
-  inject: ['builder', 'page'],
+  mixins: [elementForm],
   data() {
     let navigateTo = ''
     if (this.defaultValues.navigation_type === 'page') {
@@ -164,7 +164,6 @@ export default {
     }
   },
   computed: {
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
     DATA_PROVIDERS_ALLOWED_PAGE_PARAMETERS() {
       const PROVIDERS_TO_REMOVE = [
         new PageParameterDataProviderType().getType(),
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/ParagraphElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/ParagraphElementForm.vue
index 60271f412..31a3ef1d7 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/ParagraphElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/ParagraphElementForm.vue
@@ -10,15 +10,13 @@
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
-
-import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'ParagraphElementForm',
   components: { ApplicationBuilderFormulaInputGroup },
-  mixins: [form],
+  mixins: [elementForm],
   data() {
     return {
       values: {
@@ -26,8 +24,5 @@ export default {
       },
     }
   },
-  computed: {
-    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
-  },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/TableElementForm.vue b/web-frontend/modules/builder/components/elements/components/forms/general/TableElementForm.vue
index 0e04dc9db..4914ea114 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/TableElementForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/TableElementForm.vue
@@ -133,12 +133,15 @@
       </template>
       <p v-else>{{ $t('tableElementForm.selectSourceFirst') }}</p>
     </FormElement>
+    <ColorInputGroup
+      v-model="values.button_color"
+      :label="$t('tableElementForm.buttonColor')"
+      :color-variables="colorVariables"
+    />
   </form>
 </template>
 
 <script>
-import form from '@baserow/modules/core/mixins/form'
-import { DATA_PROVIDERS_ALLOWED_ELEMENTS } from '@baserow/modules/builder/enums'
 import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup'
 import {
   getNextAvailableNameInSequence,
@@ -152,12 +155,12 @@ import {
   maxValue,
 } from 'vuelidate/lib/validators'
 import { mapGetters } from 'vuex'
+import elementForm from '@baserow/modules/builder/mixins/elementForm'
 
 export default {
   name: 'TableElementForm',
   components: { ApplicationBuilderFormulaInputGroup },
-  mixins: [form],
-  inject: ['page'],
+  mixins: [elementForm],
   data() {
     return {
       allowedValues: ['data_source_id', 'fields', 'items_per_page'],
@@ -206,9 +209,6 @@ export default {
     collectionTypes() {
       return this.$registry.getAll('collectionField')
     },
-    DATA_PROVIDERS_ALLOWED_ELEMENTS() {
-      return DATA_PROVIDERS_ALLOWED_ELEMENTS
-    },
     ...mapGetters({
       element: 'element/getSelected',
     }),
diff --git a/web-frontend/modules/builder/components/elements/components/forms/general/settings/FontSelector.vue b/web-frontend/modules/builder/components/elements/components/forms/general/settings/FontSelector.vue
index 17fb1612b..d3abe283a 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/general/settings/FontSelector.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/general/settings/FontSelector.vue
@@ -1,36 +1,19 @@
 <template>
-  <FormElement class="control">
-    <label class="control__label">
-      {{ $t('fontSidePanelForm.label') }}
-    </label>
-    <div class="control__elements">
-      <div class="color-input">
-        <ColorPickerContext
-          ref="colorPicker"
-          v-model="values.font_color"
-          :variables="colorVariables"
-        ></ColorPickerContext>
-        <a
-          ref="fontColor"
-          class="color-input__preview"
-          :style="{
-            'background-color': resolveColor(values.font_color, colorVariables),
-          }"
-          @click="$refs.colorPicker.toggle($refs.fontColor)"
-        ></a>
-      </div>
-    </div>
-  </FormElement>
+  <ColorInputGroup
+    v-model="values.font_color"
+    :label="$t('fontSidePanelForm.label')"
+    :color-variables="colorVariables"
+  />
 </template>
 
 <script>
-import ColorPickerContext from '@baserow/modules/core/components/ColorPickerContext'
+import ColorInputGroup from '@baserow/modules/core/components/ColorInputGroup'
 import form from '@baserow/modules/core/mixins/form'
 import { resolveColor } from '@baserow/modules/core/utils/colors'
 
 export default {
   name: 'FontSelector',
-  components: { ColorPickerContext },
+  components: { ColorInputGroup },
   mixins: [form],
   props: {
     colorVariables: {
diff --git a/web-frontend/modules/builder/components/elements/components/forms/style/DefaultStyleForm.vue b/web-frontend/modules/builder/components/elements/components/forms/style/DefaultStyleForm.vue
index c35c5c44f..2237f5e92 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/style/DefaultStyleForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/style/DefaultStyleForm.vue
@@ -1,74 +1,49 @@
 <template>
   <form @submit.prevent>
     <StyleBoxForm
-      v-model="boxStyles.top"
-      :label="$t('defaultStyleForm.boxTop')"
-      :padding-is-allowed="isStyleAllowed('style_padding_top')"
-      :border-color-is-allowed="isStyleAllowed('style_border_top_color')"
-      :border-size-is-allowed="isStyleAllowed('style_border_top_size')"
+      v-for="{ name, label } in borders"
+      :key="name"
+      v-model="boxStyles[name]"
+      :label="label"
+      :padding-is-allowed="isStyleAllowed(`style_padding_${name}`)"
+      :border-is-allowed="isStyleAllowed(`style_border_${name}`)"
     />
-    <StyleBoxForm
-      v-model="boxStyles.bottom"
-      :label="$t('defaultStyleForm.boxBottom')"
-      :padding-is-allowed="isStyleAllowed('style_padding_bottom')"
-      :border-color-is-allowed="isStyleAllowed('style_border_bottom_color')"
-      :border-size-is-allowed="isStyleAllowed('style_border_bottom_size')"
-    />
-    <FormElement v-if="isStyleAllowed('style_background')" class="control">
-      <label class="control__label">{{
-        $t('defaultStyleForm.backgroundLabel')
-      }}</label>
-      <div class="control__elements">
-        <Dropdown v-model="values.style_background">
-          <DropdownItem
-            v-for="type in Object.values(BACKGROUND_TYPES)"
-            :key="type.value"
-            :name="$t(type.name)"
-            :value="type.value"
-          ></DropdownItem>
-        </Dropdown>
-        <div
-          v-if="
-            values.style_background === BACKGROUND_TYPES.COLOR.value &&
-            isStyleAllowed('style_background_color')
-          "
-          class="color-input margin-top-2"
-        >
-          <ColorPickerContext
-            ref="colorPicker"
-            v-model="values.style_background_color"
-            :variables="colorVariables"
-          ></ColorPickerContext>
-          <a
-            ref="backgroundColor"
-            class="color-input__preview margin-right-2"
-            :style="{
-              'background-color': resolveColor(
-                values.style_background_color,
-                colorVariables
-              ),
-            }"
-            @click="$refs.colorPicker.toggle($refs.backgroundColor)"
-          ></a>
-          {{ $t('defaultStyleForm.backgroundColor') }}
-        </div>
-      </div>
-    </FormElement>
-    <FormElement v-if="isStyleAllowed('style_width')" class="control">
-      <label class="control__label">{{
-        $t('defaultStyleForm.widthLabel')
-      }}</label>
-      <div class="control__elements">
-        <Dropdown v-model="values.style_width">
-          <DropdownItem
-            v-for="type in Object.values(WIDTH_TYPES)"
-            :key="type.value"
-            :name="$t(type.name)"
-            :value="type.value"
-          ></DropdownItem>
-        </Dropdown>
-      </div>
-    </FormElement>
+    <FormGroup
+      v-if="isStyleAllowed('style_background')"
+      :label="$t('defaultStyleForm.backgroundLabel')"
+    >
+      <Dropdown v-model="values.style_background">
+        <DropdownItem
+          v-for="type in Object.values(BACKGROUND_TYPES)"
+          :key="type.value"
+          :name="$t(type.name)"
+          :value="type.value"
+        />
+      </Dropdown>
+      <ColorInputGroup
+        v-if="
+          values.style_background === BACKGROUND_TYPES.COLOR.value &&
+          isStyleAllowed('style_background_color')
+        "
+        v-model="values.style_background_color"
+        label-after
+        class="margin-top-2"
+        :label="$t('defaultStyleForm.backgroundColor')"
+        :color-variables="colorVariables"
+      />
+    </FormGroup>
+    <FormGroup
+      v-if="isStyleAllowed('style_width')"
+      :label="$t('defaultStyleForm.widthLabel')"
+    >
+      <Dropdown v-model="values.style_width">
+        <DropdownItem
+          v-for="type in Object.values(WIDTH_TYPES)"
+          :key="type.value"
+          :name="$t(type.name)"
+          :value="type.value"
+        ></DropdownItem> </Dropdown
+    ></FormGroup>
   </form>
 </template>
 
@@ -76,10 +51,9 @@
 import StyleBoxForm from '@baserow/modules/builder/components/elements/components/forms/style/StyleBoxForm'
 import styleForm from '@baserow/modules/builder/mixins/styleForm'
 import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
-import ColorPickerContext from '@baserow/modules/core/components/ColorPickerContext'
 
 export default {
-  components: { StyleBoxForm, ColorPickerContext },
+  components: { StyleBoxForm },
   mixins: [styleForm],
   computed: {
     BACKGROUND_TYPES: () => BACKGROUND_TYPES,
diff --git a/web-frontend/modules/builder/components/elements/components/forms/style/StyleBoxForm.vue b/web-frontend/modules/builder/components/elements/components/forms/style/StyleBoxForm.vue
index af2cd06e9..1bcaf179f 100644
--- a/web-frontend/modules/builder/components/elements/components/forms/style/StyleBoxForm.vue
+++ b/web-frontend/modules/builder/components/elements/components/forms/style/StyleBoxForm.vue
@@ -1,79 +1,60 @@
 <template>
   <form @submit.prevent>
-    <FormElement class="control">
-      <label class="control__label">{{ label }}</label>
-      <div class="control__elements">
-        <div
-          v-if="borderSizeIsAllowed || paddingIsAllowed"
-          class="row margin-bottom-2"
-          style="--gap: 6px"
-        >
-          <div v-if="borderSizeIsAllowed" class="col col-3">
-            <div class="margin-bottom-1">
-              {{ $t('styleBoxForm.borderLabel') }}
-            </div>
-            <input
-              v-model="values.border_size"
-              type="number"
-              class="input"
-              :class="{
-                'input--error': error,
-              }"
-              @blur="$v.values.border_size.$touch()"
-            />
+    <FormGroup :label="label" :error="error">
+      <div
+        v-if="borderIsAllowed || paddingIsAllowed"
+        class="row margin-bottom-2"
+        style="--gap: 6px"
+      >
+        <div v-if="borderIsAllowed" class="col col-3">
+          <div class="margin-bottom-1">
+            {{ $t('styleBoxForm.borderLabel') }}
           </div>
-          <div v-if="paddingIsAllowed" class="col col-3">
-            <div class="margin-bottom-1">
-              {{ $t('styleBoxForm.paddingLabel') }}
-            </div>
-            <input
-              v-model="values.padding"
-              type="number"
-              class="input"
-              :class="{
-                'input--error': error,
-              }"
-              @blur="$v.values.padding.$touch()"
-            />
-          </div>
-        </div>
-        <div v-if="borderColorIsAllowed" class="color-input">
-          <ColorPickerContext
-            ref="colorPicker"
-            v-model="values.border_color"
-            :variables="colorVariables"
-          ></ColorPickerContext>
-          <a
-            ref="borderColor"
-            class="color-input__preview margin-right-2"
-            :style="{
-              'background-color': resolveColor(
-                values.border_color,
-                colorVariables
-              ),
+          <input
+            v-model="values.border_size"
+            type="number"
+            class="input"
+            :class="{
+              'input--error': error,
             }"
-            @click="$refs.colorPicker.toggle($refs.borderColor)"
-          ></a>
-          {{ $t('styleBoxForm.borderLabel') }}
+            @blur="$v.values.border_size.$touch()"
+          />
         </div>
-        <div v-if="error" class="error">
-          {{ error }}
+        <div v-if="paddingIsAllowed" class="col col-3">
+          <div class="margin-bottom-1">
+            {{ $t('styleBoxForm.paddingLabel') }}
+          </div>
+          <input
+            v-model="values.padding"
+            type="number"
+            class="input"
+            :class="{
+              'input--error': error,
+            }"
+            @blur="$v.values.padding.$touch()"
+          />
         </div>
       </div>
-    </FormElement>
+      <ColorInputGroup
+        v-if="borderIsAllowed"
+        v-model="values.border_color"
+        label-after
+        class="margin-top-2"
+        :label="$t('styleBoxForm.borderLabel')"
+        :color-variables="colorVariables"
+      />
+    </FormGroup>
   </form>
 </template>
 
 <script>
 import { required, integer, between } from 'vuelidate/lib/validators'
 import form from '@baserow/modules/core/mixins/form'
-import ColorPickerContext from '@baserow/modules/core/components/ColorPickerContext.vue'
 import { resolveColor } from '@baserow/modules/core/utils/colors'
 import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
 
 export default {
   name: 'StyleBoxForm',
-  components: { ColorPickerContext },
   mixins: [form],
   inject: ['builder'],
   props: {
@@ -90,12 +71,7 @@ export default {
       required: false,
       default: () => false,
     },
-    borderColorIsAllowed: {
-      type: Boolean,
-      required: false,
-      default: () => false,
-    },
-    borderSizeIsAllowed: {
+    borderIsAllowed: {
       type: Boolean,
       required: false,
       default: () => false,
@@ -120,7 +96,7 @@ export default {
      */
     error() {
       if (this.$v.values.padding.$error || this.$v.values.border_size.$error) {
-        return this.$t('error.minMaxLength', { min: 0, max: 200 })
+        return this.$t('error.minMaxValueField', { min: 0, max: 200 })
       } else {
         return ''
       }
diff --git a/web-frontend/modules/builder/components/page/PageContent.vue b/web-frontend/modules/builder/components/page/PageContent.vue
index 0f0281fd2..36da4f235 100644
--- a/web-frontend/modules/builder/components/page/PageContent.vue
+++ b/web-frontend/modules/builder/components/page/PageContent.vue
@@ -1,6 +1,6 @@
 <template>
   <div>
-    <PageRootElement
+    <PageElement
       v-for="element in elements"
       :key="element.id"
       :element="element"
@@ -10,10 +10,10 @@
 </template>
 
 <script>
-import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
+import PageElement from '@baserow/modules/builder/components/page/PageElement'
 
 export default {
-  components: { PageRootElement },
+  components: { PageElement },
   inject: ['builder', 'mode'],
   props: {
     page: {
diff --git a/web-frontend/modules/builder/components/page/PageElement.vue b/web-frontend/modules/builder/components/page/PageElement.vue
index 3b422470a..7500f1e68 100644
--- a/web-frontend/modules/builder/components/page/PageElement.vue
+++ b/web-frontend/modules/builder/components/page/PageElement.vue
@@ -1,19 +1,32 @@
 <template>
-  <div class="element__wrapper" :style="wrapperStyles">
-    <component
-      :is="component"
-      :element="element"
-      :children="children"
-      class="element"
-    />
+  <div
+    class="element__wrapper"
+    :class="{
+      'element__wrapper--full-width':
+        element.style_width === WIDTH_TYPES.FULL.value,
+      'element__wrapper--medium-width':
+        element.style_width === WIDTH_TYPES.MEDIUM.value,
+      'element__wrapper--small-width':
+        element.style_width === WIDTH_TYPES.SMALL.value,
+    }"
+    :style="wrapperStyles"
+  >
+    <div class="element__inner-wrapper" :style="innerWrapperStyles">
+      <component
+        :is="component"
+        :element="element"
+        :children="children"
+        class="element"
+      />
+    </div>
   </div>
 </template>
 
 <script>
-import PageElement from '@baserow/modules/builder/mixins/pageElement'
+import pageElement from '@baserow/modules/builder/mixins/pageElement'
 
 export default {
   name: 'PageElement',
-  mixins: [PageElement],
+  mixins: [pageElement],
 }
 </script>
diff --git a/web-frontend/modules/builder/components/page/PageRootElement.vue b/web-frontend/modules/builder/components/page/PageRootElement.vue
index 912abd4d2..e7a76f5af 100644
--- a/web-frontend/modules/builder/components/page/PageRootElement.vue
+++ b/web-frontend/modules/builder/components/page/PageRootElement.vue
@@ -1,61 +1,30 @@
 <template>
   <div
-    class="page-root-element__inner"
-    :style="{
-      '--background-color':
-        element.style_background === BACKGROUND_TYPES.COLOR.value
-          ? resolveColor(element.style_background_color, colorVariables)
-          : 'transparent',
-      '--border-top': border(
-        element.style_border_top_size,
-        element.style_border_top_color
-      ),
-      '--border-bottom': border(
-        element.style_border_bottom_size,
-        element.style_border_bottom_color
-      ),
+    class="element__wrapper"
+    :class="{
+      'element__wrapper--full-width':
+        element.style_width === WIDTH_TYPES.FULL.value,
+      'element__wrapper--medium-width':
+        element.style_width === WIDTH_TYPES.MEDIUM.value,
+      'element__wrapper--small-width':
+        element.style_width === WIDTH_TYPES.SMALL.value,
     }"
+    :style="wrapperStyles"
   >
-    <div
-      class="element__wrapper element__wrapper--normal-width"
-      :class="{
-        'element__wrapper--full-width':
-          element.style_width === WIDTH_TYPES.FULL.value,
-        'element__wrapper--medium-width':
-          element.style_width === WIDTH_TYPES.MEDIUM.value,
-        'element__wrapper--small-width':
-          element.style_width === WIDTH_TYPES.SMALL.value,
-      }"
-      :style="wrapperStyles"
-    >
-      <component
-        :is="component"
-        :element="element"
-        :children="children"
-        class="element"
-      />
-    </div>
+    <component
+      :is="component"
+      :element="element"
+      :children="children"
+      class="element"
+    />
   </div>
 </template>
 
 <script>
 import pageElement from '@baserow/modules/builder/mixins/pageElement'
-import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
 
 export default {
   name: 'PageRootElement',
   mixins: [pageElement],
-  computed: {
-    BACKGROUND_TYPES: () => BACKGROUND_TYPES,
-    WIDTH_TYPES: () => WIDTH_TYPES,
-  },
-  methods: {
-    border(size, color) {
-      return `solid ${size || 0}px ${this.resolveColor(
-        color,
-        this.colorVariables
-      )}`
-    },
-  },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/theme/MainThemeConfigBlock.vue b/web-frontend/modules/builder/components/theme/MainThemeConfigBlock.vue
index a3545667a..d0c7e1e36 100644
--- a/web-frontend/modules/builder/components/theme/MainThemeConfigBlock.vue
+++ b/web-frontend/modules/builder/components/theme/MainThemeConfigBlock.vue
@@ -22,38 +22,18 @@
           ></i>
         </a>
         <div v-show="!isClosed('colors')">
-          <div class="control">
-            <div class="control__elements">
-              <div class="color-input">
-                <a
-                  ref="primaryColor"
-                  class="color-input__preview"
-                  :style="{ 'background-color': builder.theme.primary_color }"
-                  @click="openColorPicker($refs.primaryColor, 'primary_color')"
-                ></a>
-                <div class="color-input__label">
-                  {{ $t('mainThemeConfigBlock.primaryColor') }}
-                </div>
-              </div>
-            </div>
-          </div>
-          <div class="control">
-            <div class="control__elements">
-              <div class="color-input">
-                <a
-                  ref="secondaryColor"
-                  class="color-input__preview"
-                  :style="{ 'background-color': builder.theme.secondary_color }"
-                  @click="
-                    openColorPicker($refs.secondaryColor, 'secondary_color')
-                  "
-                ></a>
-                <div class="color-input__label">
-                  {{ $t('mainThemeConfigBlock.secondaryColor') }}
-                </div>
-              </div>
-            </div>
-          </div>
+          <ColorInputGroup
+            :value="builder.theme.primary_color"
+            label-after
+            :label="$t('mainThemeConfigBlock.primaryColor')"
+            @input="setPropertyInStore('primary_color', $event)"
+          />
+          <ColorInputGroup
+            :value="builder.theme.secondary_color"
+            label-after
+            :label="$t('mainThemeConfigBlock.secondaryColor')"
+            @input="setPropertyInStore('secondary_color', $event)"
+          />
         </div>
       </div>
     </div>
@@ -88,21 +68,10 @@
               {{ $t('mainThemeConfigBlock.headingLabel', { i }) }}
             </div>
             <div class="control__elements control__elements--flex">
-              <div class="color-input">
-                <a
-                  ref="headingColor"
-                  class="color-input__preview"
-                  :style="{
-                    'background-color': builder.theme[`heading_${i}_color`],
-                  }"
-                  @click="
-                    openColorPicker(
-                      $refs.headingColor[i - 1],
-                      `heading_${i}_color`
-                    )
-                  "
-                ></a>
-              </div>
+              <ColorInput
+                :value="builder.theme[`heading_${i}_color`]"
+                @input="setPropertyInStore(`heading_${i}_color`, $event)"
+              />
               <div class="input__with-icon">
                 <input
                   type="number"
diff --git a/web-frontend/modules/builder/elementTypes.js b/web-frontend/modules/builder/elementTypes.js
index bad9ea40d..fd7996940 100644
--- a/web-frontend/modules/builder/elementTypes.js
+++ b/web-frontend/modules/builder/elementTypes.js
@@ -62,13 +62,14 @@ export class ElementType extends Registerable {
 
   get stylesAll() {
     return [
-      'style_border_top_color',
-      'style_border_top_size',
       'style_padding_top',
-      'style_border_bottom_color',
-      'style_border_bottom_size',
-      'style_padding_bottom',
       'style_padding_bottom',
+      'style_padding_left',
+      'style_padding_right',
+      'style_border_top',
+      'style_border_bottom',
+      'style_border_left',
+      'style_border_right',
       'style_background',
       'style_background_color',
       'style_width',
@@ -218,15 +219,7 @@ export class ColumnElementType extends ContainerElementType {
   }
 
   get childStylesForbidden() {
-    return [
-      'style_border_top_color',
-      'style_border_top_size',
-      'style_border_bottom_color',
-      'style_border_bottom_size',
-      'style_background',
-      'style_background_color',
-      'style_width',
-    ]
+    return ['style_width']
   }
 
   get defaultPlaceInContainer() {
@@ -667,4 +660,8 @@ export class FormContainerElementType extends ContainerElementType {
   get events() {
     return [SubmitEvent]
   }
+
+  get childStylesForbidden() {
+    return ['style_width']
+  }
 }
diff --git a/web-frontend/modules/builder/locales/en.json b/web-frontend/modules/builder/locales/en.json
index 33eaa0b25..96d7f825a 100644
--- a/web-frontend/modules/builder/locales/en.json
+++ b/web-frontend/modules/builder/locales/en.json
@@ -214,23 +214,24 @@
         "noValue": "Unnamed..."
     },
     "linkElementForm": {
-        "text": "Text",
-        "textPlaceholder": "Enter text...",
-        "navigateTo": "Navigate to",
-        "navigateToNotSet": "No destination",
-        "navigateToCustom": "Custom URL",
-        "url": "Destination URL",
-        "urlPlaceholder": "Enter an URL...",
-        "variant": "Variant",
-        "variantLink": "Link",
-        "variantButton": "Button",
-        "target": "Open in...",
-        "targetSelf": "Same tab",
-        "targetNewTab": "New tab",
-        "paramPlaceholder": "Enter a value...",
-        "paramsInErrorDescription": "The saved parameters don't match the page parameters. The page has probably been deleted or updated.",
-        "paramsInErrorButton": "Update parameters",
-        "pageParameterTypeError": "Invalid type"
+      "text": "Text",
+      "textPlaceholder": "Enter text...",
+      "navigateTo": "Navigate to",
+      "navigateToNotSet": "No destination",
+      "navigateToCustom": "Custom URL",
+      "url": "Destination URL",
+      "urlPlaceholder": "Enter an URL...",
+      "variant": "Variant",
+      "variantLink": "Link",
+      "variantButton": "Button",
+      "target": "Open in...",
+      "targetSelf": "Same tab",
+      "targetNewTab": "New tab",
+      "paramPlaceholder": "Enter a value...",
+      "paramsInErrorDescription": "The saved parameters don't match the page parameters. The page has probably been deleted or updated.",
+      "paramsInErrorButton": "Update parameters",
+      "pageParameterTypeError": "Invalid type",
+      "buttonColor": "Button color"
     },
     "widthSelector": {
         "width": "Width",
@@ -302,16 +303,17 @@
         "noDataSourceMessage": "Data sources can be used to fetch data from internal or external sources and display it on the page."
     },
     "defaultStyleForm": {
-        "boxTop": "Top",
-        "boxBottom": "Bottom",
-        "backgroundLabel": "Background",
-        "backgroundColor": "Background color",
-        "widthLabel": "Width"
+      "boxTop": "Top",
+      "boxBottom": "Bottom",
+      "boxLeft": "Left",
+      "boxRight": "Right",
+      "backgroundLabel": "Background",
+      "backgroundColor": "Background color",
+      "widthLabel": "Width"
     },
     "styleBoxForm": {
         "borderLabel": "Border",
-        "paddingLabel": "Padding",
-        "error": "The value must be an integer between 0 and 200."
+        "paddingLabel": "Padding"
     },
     "mainThemeConfigBlock": {
       "colorsLabel": "Colors",
@@ -322,8 +324,9 @@
       "headingValue": "Heading <h{i}>"
     },
     "buttonElementForm": {
-        "valueLabel": "Text",
-        "valuePlaceholder": "Enter text..."
+      "valueLabel": "Text",
+      "valuePlaceholder": "Enter text...",
+      "buttonColor": "Button color"
     },
     "buttonElement": {
         "noValue": "Unnamed..."
@@ -348,7 +351,8 @@
       "fieldDefaultName": "Column",
       "fieldType": "Type",
       "itemsPerPagePlaceholder": "Enter value...",
-      "selectSourceFirst": "Choose a list data source to begin configuring your fields."
+      "selectSourceFirst": "Choose a list data source to begin configuring your fields.",
+      "buttonColor": "Button color"
     },
     "tableElement": {
       "empty": "No items have been found.",
@@ -389,7 +393,8 @@
       "fieldValueLabel": "Url",
       "fieldValuePlaceholder": "Enter value...",
       "fieldLinkNameLabel": "Link text",
-      "fieldLinkNamePlaceholder": "Enter value..."
+      "fieldLinkNamePlaceholder": "Enter value...",
+      "buttonColor": "Button color"
     },
     "linkField": {
       "details": "Details"
@@ -405,8 +410,9 @@
       "integrationFieldLabel": "Integration"
     },
     "formContainerElementForm": {
-        "submitButtonLabel": "Submit button",
-        "submitButtonPlaceholder": "Enter value..."
+      "submitButtonLabel": "Submit button",
+      "submitButtonPlaceholder": "Enter value...",
+      "buttonColor": "Button color"
     },
     "dropdownOptionSelector": {
         "label": "Options",
diff --git a/web-frontend/modules/builder/mixins/element.js b/web-frontend/modules/builder/mixins/element.js
index bbd457318..c28414cc0 100644
--- a/web-frontend/modules/builder/mixins/element.js
+++ b/web-frontend/modules/builder/mixins/element.js
@@ -1,6 +1,8 @@
 import RuntimeFormulaContext from '@baserow/modules/core/runtimeFormulaContext'
 import { resolveFormula } from '@baserow/modules/core/formula'
 import { ClickEvent, SubmitEvent } from '@baserow/modules/builder/eventTypes'
+import { resolveColor } from '@baserow/modules/core/utils/colors'
+import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
 
 export default {
   inject: ['builder', 'page', 'mode'],
@@ -53,6 +55,9 @@ export default {
         },
       }
     },
+    colorVariables() {
+      return themeToColorVariables(this.builder.theme)
+    },
   },
   methods: {
     resolveFormula(formula) {
@@ -93,5 +98,7 @@ export default {
     fireSubmitEvent() {
       this.fireEvent(SubmitEvent)
     },
+
+    resolveColor,
   },
 }
diff --git a/web-frontend/modules/builder/mixins/elementForm.js b/web-frontend/modules/builder/mixins/elementForm.js
new file mode 100644
index 000000000..686581ffe
--- /dev/null
+++ b/web-frontend/modules/builder/mixins/elementForm.js
@@ -0,0 +1,29 @@
+import { resolveColor } from '@baserow/modules/core/utils/colors'
+import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
+import form from '@baserow/modules/core/mixins/form'
+import {
+  DATA_PROVIDERS_ALLOWED_ELEMENTS,
+  DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS,
+} from '@baserow/modules/builder/enums'
+
+export default {
+  inject: ['builder', 'page', 'mode'],
+  mixins: [form],
+  data() {
+    return {
+      workflowActionsInProgress: false,
+    }
+  },
+  computed: {
+    colorVariables() {
+      return themeToColorVariables(this.builder.theme)
+    },
+
+    DATA_PROVIDERS_ALLOWED_ELEMENTS: () => DATA_PROVIDERS_ALLOWED_ELEMENTS,
+    DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS: () =>
+      DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS,
+  },
+  methods: {
+    resolveColor,
+  },
+}
diff --git a/web-frontend/modules/builder/mixins/pageElement.js b/web-frontend/modules/builder/mixins/pageElement.js
index e73e07423..29eb47a8a 100644
--- a/web-frontend/modules/builder/mixins/pageElement.js
+++ b/web-frontend/modules/builder/mixins/pageElement.js
@@ -2,6 +2,8 @@ import _ from 'lodash'
 import { resolveColor } from '@baserow/modules/core/utils/colors'
 import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
 
+import { BACKGROUND_TYPES, WIDTH_TYPES } from '@baserow/modules/builder/enums'
+
 export default {
   inject: ['builder', 'page'],
   props: {
@@ -16,6 +18,8 @@ export default {
     },
   },
   computed: {
+    BACKGROUND_TYPES: () => BACKGROUND_TYPES,
+    WIDTH_TYPES: () => WIDTH_TYPES,
     colorVariables() {
       return themeToColorVariables(this.builder.theme)
     },
@@ -46,13 +50,61 @@ export default {
             parentElementType.childStylesForbidden
           )
     },
+
     /**
      * Computes an object containing all the style properties that must be set on
      * the element wrapper.
      */
     wrapperStyles() {
-      const stylesAllowed = this.allowedStyles
+      const styles = {
+        style_background_color: {
+          '--background-color':
+            this.element.style_background === BACKGROUND_TYPES.COLOR.value
+              ? this.resolveColor(
+                  this.element.style_background_color,
+                  this.colorVariables
+                )
+              : 'transparent',
+        },
+        style_border_top: {
+          '--border-top': this.border(
+            this.element.style_border_top_size,
+            this.element.style_border_top_color
+          ),
+        },
+        style_border_bottom: {
+          '--border-bottom': this.border(
+            this.element.style_border_bottom_size,
+            this.element.style_border_bottom_color
+          ),
+        },
+        style_border_left: {
+          '--border-left': this.border(
+            this.element.style_border_left_size,
+            this.element.style_border_left_color
+          ),
+        },
+        style_border_right: {
+          '--border-right': this.border(
+            this.element.style_border_right_size,
+            this.element.style_border_right_color
+          ),
+        },
+      }
 
+      return Object.keys(styles).reduce((acc, key) => {
+        if (this.allowedStyles.includes(key)) {
+          acc = { ...acc, ...styles[key] }
+        }
+        return acc
+      }, {})
+    },
+
+    /**
+     * Computes an object containing all the style properties that must be set on
+     * the element inner wrapper.
+     */
+    innerWrapperStyles() {
       const styles = {
         style_padding_top: {
           '--padding-top': `${this.element.style_padding_top || 0}px`,
@@ -60,10 +112,16 @@ export default {
         style_padding_bottom: {
           '--padding-bottom': `${this.element.style_padding_bottom || 0}px`,
         },
+        style_padding_left: {
+          '--padding-left': `${this.element.style_padding_left || 0}px`,
+        },
+        style_padding_right: {
+          '--padding-right': `${this.element.style_padding_right || 0}px`,
+        },
       }
 
       return Object.keys(styles).reduce((acc, key) => {
-        if (stylesAllowed.includes(key)) {
+        if (this.allowedStyles.includes(key)) {
           acc = { ...acc, ...styles[key] }
         }
         return acc
@@ -72,5 +130,11 @@ export default {
   },
   methods: {
     resolveColor,
+    border(size, color) {
+      return `solid ${size || 0}px ${this.resolveColor(
+        color,
+        this.colorVariables
+      )}`
+    },
   },
 }
diff --git a/web-frontend/modules/builder/mixins/styleForm.js b/web-frontend/modules/builder/mixins/styleForm.js
index e8311cbfc..947c8c060 100644
--- a/web-frontend/modules/builder/mixins/styleForm.js
+++ b/web-frontend/modules/builder/mixins/styleForm.js
@@ -1,8 +1,11 @@
 import _ from 'lodash'
 import form from '@baserow/modules/core/mixins/form'
-import { resolveColor } from '@baserow/modules/core/utils/colors'
 import { themeToColorVariables } from '@baserow/modules/builder/utils/theme'
 
+const borderNames = ['top', 'bottom', 'left', 'right']
+
+const borderStyleNames = borderNames.map((pos) => `style_border_${pos}`)
+
 export default {
   inject: ['builder'],
   props: {
@@ -23,7 +26,7 @@ export default {
       allowedValues,
       values: this.getValuesFromElement(allowedValues),
       boxStyles: Object.fromEntries(
-        ['top', 'bottom'].map((pos) => [pos, this.getBoxStyleValue(pos)])
+        borderNames.map((pos) => [pos, this.getBoxStyleValue(pos)])
       ),
     }
   },
@@ -31,6 +34,17 @@ export default {
     colorVariables() {
       return themeToColorVariables(this.builder.theme)
     },
+    allowedStyles() {
+      return this.getAllowedStyles()
+    },
+    borders() {
+      return [
+        { name: 'top', label: this.$t('defaultStyleForm.boxTop') },
+        { name: 'bottom', label: this.$t('defaultStyleForm.boxBottom') },
+        { name: 'left', label: this.$t('defaultStyleForm.boxLeft') },
+        { name: 'right', label: this.$t('defaultStyleForm.boxRight') },
+      ]
+    },
   },
   watch: {
     boxStyles: {
@@ -43,9 +57,8 @@ export default {
     },
   },
   methods: {
-    resolveColor,
     isStyleAllowed(style) {
-      return this.allowedValues.includes(style)
+      return this.allowedStyles.includes(style)
     },
     getBoxStyleValue(pos) {
       return {
@@ -61,20 +74,33 @@ export default {
         this.values[`style_border_${pos}_size`] = newValue.border_size
       }
     },
-    getAllowedValues() {
+    getAllowedStyles() {
       const elementType = this.$registry.get('element', this.element.type)
       const parentElementType = this.parentElement
         ? this.$registry.get('element', this.parentElement?.type)
-        : null
+        : []
 
-      if (!parentElementType) {
-        return elementType.styles
+      let styles = elementType.styles
+
+      if (parentElementType) {
+        styles = _.difference(
+          elementType.styles,
+          parentElementType.childStylesForbidden
+        )
       }
 
-      return _.difference(
-        elementType.styles,
-        parentElementType.childStylesForbidden
-      )
+      return styles
+    },
+    getAllowedValues() {
+      // Rewrite border style names
+      return this.getAllowedStyles()
+        .map((style) => {
+          if (borderStyleNames.includes(style)) {
+            return [`${style}_color`, `${style}_size`]
+          }
+          return style
+        })
+        .flat()
     },
     getValuesFromElement(allowedValues) {
       return allowedValues.reduce((obj, value) => {
diff --git a/web-frontend/modules/core/assets/scss/components/all.scss b/web-frontend/modules/core/assets/scss/components/all.scss
index 89fb31d52..5f403cd48 100644
--- a/web-frontend/modules/core/assets/scss/components/all.scss
+++ b/web-frontend/modules/core/assets/scss/components/all.scss
@@ -136,6 +136,7 @@
 @import 'notification_panel';
 @import 'color_picker';
 @import 'color_picker_context';
+@import 'color_input_group';
 @import 'formula_input_field';
 @import 'get_formula_component';
 @import 'theme_settings';
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 213f98538..95a50d2ed 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/all.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/all.scss
@@ -9,7 +9,6 @@
 @import 'side_panels';
 @import 'empty_side_panel_state';
 @import 'page_editor';
-@import 'page_root_element';
 @import 'page_settings_path_params_form_element';
 @import 'domain_card';
 @import 'dns_status';
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 b59d74972..788b5a366 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/element.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/element.scss
@@ -1,13 +1,31 @@
 .element__wrapper {
-  padding: var(--padding-top, 0) 0 var(--padding-bottom, 0) 0;
+  background-color: var(--background-color, $black);
+  border-top: var(--border-top, none);
+  border-bottom: var(--border-bottom, none);
+  border-left: var(--border-left, none);
+  border-right: var(--border-right, none);
+  margin: 0 auto;
+  max-width: $builder-page-max-width;
 
-  &--normal-width {
-    margin: 0 auto;
-    padding-left: 20px;
-    padding-right: 20px;
-    max-width: $builder-page-max-width;
+  &--full-width {
+    max-width: 100%;
   }
 
+  &--medium-width {
+    max-width: 960px;
+  }
+
+  &--small-width {
+    max-width: 680px;
+  }
+}
+
+.element__inner-wrapper {
+  padding: var(--padding-top, 0) var(--padding-right, 0)
+    var(--padding-bottom, 0) var(--padding-left, 0);
+  margin: 0 auto;
+  max-width: $builder-page-max-width;
+
   &--full-width {
     max-width: 100%;
   }
@@ -22,7 +40,7 @@
 }
 
 .element {
-  // this is a placeholder, the class will be added to every element component.
+  // Placeholder for element styles
 }
 
 .element--no-value {
diff --git a/web-frontend/modules/core/assets/scss/components/builder/elements/link-button-element-button.scss b/web-frontend/modules/core/assets/scss/components/builder/elements/link-button-element-button.scss
index 49e3bdf81..badf7fd8b 100644
--- a/web-frontend/modules/core/assets/scss/components/builder/elements/link-button-element-button.scss
+++ b/web-frontend/modules/core/assets/scss/components/builder/elements/link-button-element-button.scss
@@ -3,7 +3,7 @@
   cursor: pointer;
   display: inline-block;
   color: $white;
-  background-color: $black;
+  background-color: var(--button-color, $black);
   line-height: 28px;
   padding: 0 12px;
   border: none;
@@ -13,7 +13,7 @@
   @include rounded($rounded);
 
   &:hover {
-    background-color: lighten($black, 10%);
+    filter: brightness(1.3);
     text-decoration: none;
   }
 
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
deleted file mode 100644
index 4b2da865b..000000000
--- a/web-frontend/modules/core/assets/scss/components/builder/page_root_element.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.page-root-element__inner {
-  background-color: var(--background-color, $black);
-  border-top: var(--border-top, none);
-  border-bottom: var(--border-bottom, none);
-}
diff --git a/web-frontend/modules/core/assets/scss/components/color_input.scss b/web-frontend/modules/core/assets/scss/components/color_input.scss
index 2b4f63226..0d18af43e 100644
--- a/web-frontend/modules/core/assets/scss/components/color_input.scss
+++ b/web-frontend/modules/core/assets/scss/components/color_input.scss
@@ -1,19 +1,11 @@
-.color-input {
-  display: flex;
-  align-items: center;
-}
-
 .color-input__preview {
+  display: block;
   width: 40px;
   height: 20px;
   border-radius: 2px;
-  box-shadow: 0 0 1px 0 rgba($black, 0.16);
+  box-shadow: 1px 1px 1px 1px rgba($black, 0.2);
 
   &:hover {
-    box-shadow: 0 0 2px 0 rgba($black, 0.16);
+    box-shadow: 1px 1px 1px 0 rgba($black, 0.2);
   }
 }
-
-.color-input__label {
-  margin-left: 10px;
-}
diff --git a/web-frontend/modules/core/assets/scss/components/color_input_group.scss b/web-frontend/modules/core/assets/scss/components/color_input_group.scss
new file mode 100644
index 000000000..6814b11e9
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/color_input_group.scss
@@ -0,0 +1,8 @@
+.color-input-group--label-after {
+  display: flex;
+  align-items: center;
+}
+
+.color-input-group__label-after {
+  margin-left: 16px;
+}
diff --git a/web-frontend/modules/core/components/ColorInput.vue b/web-frontend/modules/core/components/ColorInput.vue
new file mode 100644
index 000000000..7a27d3e04
--- /dev/null
+++ b/web-frontend/modules/core/components/ColorInput.vue
@@ -0,0 +1,41 @@
+<template>
+  <div>
+    <ColorPickerContext
+      ref="colorPicker"
+      :value="value"
+      :variables="colorVariables"
+      @input="$emit('input', $event)"
+    />
+    <a
+      ref="opener"
+      class="color-input__preview"
+      :style="{
+        'background-color': resolveColor(value, colorVariables),
+      }"
+      @click="$refs.colorPicker.toggle($refs.opener)"
+    />
+  </div>
+</template>
+
+<script>
+import ColorPickerContext from '@baserow/modules/core/components/ColorPickerContext'
+import { resolveColor } from '@baserow/modules/core/utils/colors'
+
+export default {
+  name: 'ColorInput',
+  components: { ColorPickerContext },
+  props: {
+    value: {
+      type: String,
+      required: false,
+      default: 'primary',
+    },
+    colorVariables: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+  },
+  methods: { resolveColor },
+}
+</script>
diff --git a/web-frontend/modules/core/components/ColorInputGroup.vue b/web-frontend/modules/core/components/ColorInputGroup.vue
new file mode 100644
index 000000000..af521abec
--- /dev/null
+++ b/web-frontend/modules/core/components/ColorInputGroup.vue
@@ -0,0 +1,53 @@
+<template>
+  <FormGroup v-if="!labelAfter" :label="label">
+    <ColorInput
+      :value="value"
+      :color-variables="colorVariables"
+      @input="$emit('input', $event)"
+    />
+  </FormGroup>
+  <div v-else class="control">
+    <div class="control__elements">
+      <div class="color-input-group--label-after">
+        <ColorInput
+          :value="value"
+          :color-variables="colorVariables"
+          @input="$emit('input', $event)"
+        />
+        <div class="color-input-group__label-after">
+          {{ label }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import ColorInput from '@baserow/modules/core/components/ColorInput'
+
+export default {
+  name: 'ColorInputGroup',
+  components: { ColorInput },
+  props: {
+    value: {
+      type: String,
+      required: false,
+      default: 'primary',
+    },
+    label: {
+      type: String,
+      required: true,
+    },
+    labelAfter: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    colorVariables: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+  },
+}
+</script>
diff --git a/web-frontend/modules/core/components/FormInput.vue b/web-frontend/modules/core/components/FormInput.vue
index 846684d87..3b4e0db41 100644
--- a/web-frontend/modules/core/components/FormInput.vue
+++ b/web-frontend/modules/core/components/FormInput.vue
@@ -33,6 +33,7 @@
           <input
             ref="base_url"
             class="form-input__input"
+            :class="{ 'remove-number-input-controls': true }"
             :value="fromValue(value)"
             :disabled="disabled"
             :type="type"
diff --git a/web-frontend/modules/core/plugins/global.js b/web-frontend/modules/core/plugins/global.js
index 5fe87af28..cf98cc67a 100644
--- a/web-frontend/modules/core/plugins/global.js
+++ b/web-frontend/modules/core/plugins/global.js
@@ -45,6 +45,8 @@ import Badge from '@baserow/modules/core/components/Badge'
 import Expandable from '@baserow/modules/core/components/Expandable.vue'
 import RadioButton from '@baserow/modules/core/components/RadioButton'
 import Thumbnail from '@baserow/modules/core/components/Thumbnail'
+import ColorInput from '@baserow/modules/core/components/ColorInput'
+import ColorInputGroup from '@baserow/modules/core/components/ColorInputGroup'
 
 function setupVue(Vue) {
   Vue.component('Context', Context)
@@ -79,6 +81,8 @@ function setupVue(Vue) {
   Vue.component('CallToAction', CallToAction)
   Vue.component('FormGroup', FormGroup)
   Vue.component('FormRow', FormRow)
+  Vue.component('ColorInput', ColorInput)
+  Vue.component('ColorInputGroup', ColorInputGroup)
 
   Vue.filter('lowercase', lowercase)
   Vue.filter('uppercase', uppercase)
diff --git a/web-frontend/modules/core/utils/colors.js b/web-frontend/modules/core/utils/colors.js
index 34fa3c593..f8814e0ef 100644
--- a/web-frontend/modules/core/utils/colors.js
+++ b/web-frontend/modules/core/utils/colors.js
@@ -260,6 +260,9 @@ export const conversionsMap = {
 }
 
 export function isColorVariable(value) {
+  if (!value) {
+    return false
+  }
   return value.substring(0, 1) !== '#'
 }