diff --git a/ansible/roles/horizon/defaults/main.yml b/ansible/roles/horizon/defaults/main.yml
index fed7bb1eb7a6bc176f46ff54be7b141185b90234..10592d396d8368395884c399a199b8f2892cbc20 100644
--- a/ansible/roles/horizon/defaults/main.yml
+++ b/ansible/roles/horizon/defaults/main.yml
@@ -75,6 +75,8 @@ horizon_services:
 horizon_keystone_domain_choices:
   Default: default
 
+horizon_custom_themes: []
+
 ####################
 # Database
 ####################
diff --git a/ansible/roles/horizon/tasks/config.yml b/ansible/roles/horizon/tasks/config.yml
index e614af5a2021de9ab1523ae5b11e8f86809c9ce5..f302c6b59b1707e6084726920212e39fbec25bbc 100644
--- a/ansible/roles/horizon/tasks/config.yml
+++ b/ansible/roles/horizon/tasks/config.yml
@@ -129,6 +129,22 @@
   notify:
     - Restart horizon container
 
+- name: Copying over custom themes
+  become: true
+  vars:
+    horizon: "{{ horizon_services['horizon'] }}"
+  copy:
+    src: "{{ node_custom_config }}/horizon/themes/{{ item.name }}"
+    dest: "{{ node_config_directory }}/horizon/themes/"
+    mode: 0660
+  when:
+    - horizon.enabled | bool
+    - inventory_hostname in groups[horizon.group]
+    - horizon_custom_themes | length > 0
+  with_items: "{{ horizon_custom_themes }}"
+  notify:
+    - Restart horizon container
+
 - include_tasks: copy-certs.yml
   when:
     - kolla_copy_ca_into_containers | bool or horizon_enable_tls_backend | bool
diff --git a/ansible/roles/horizon/templates/horizon.json.j2 b/ansible/roles/horizon/templates/horizon.json.j2
index bc2eb6843ec01c999f20ef61e574561671942cb6..64fe58152a40581301237de492b3c22ebfa09f01 100644
--- a/ansible/roles/horizon/templates/horizon.json.j2
+++ b/ansible/roles/horizon/templates/horizon.json.j2
@@ -41,6 +41,12 @@
             "dest": "/etc/horizon/certs/horizon-key.pem",
             "owner": "horizon",
             "perm": "0600"
+        }{% endif %}{% if horizon_custom_themes | length > 0 %},
+        {
+            "source": "{{ container_config_directory}}/themes",
+            "dest": "/etc/openstack-dashboard/themes",
+            "owner": "horizon",
+            "perm": "0600"
         }{% endif %}
     ]
 }
diff --git a/ansible/roles/horizon/templates/local_settings.j2 b/ansible/roles/horizon/templates/local_settings.j2
index ecaba31d2b143a111b6830b8867e922b8d88b92e..c46dacc6831c98c307fdab60f90d45ce400b8ebd 100644
--- a/ansible/roles/horizon/templates/local_settings.j2
+++ b/ansible/roles/horizon/templates/local_settings.j2
@@ -515,6 +515,15 @@ POLICY_FILES_PATH = '/etc/openstack-dashboard'
 #    ('default', 'Default', 'themes/default'),
 #    ('material', 'Material', 'themes/material'),
 #]
+{% if horizon_custom_themes | length > 0 %}
+AVAILABLE_THEMES = [
+     ('default', 'Default', 'themes/default'),
+     ('material', 'Material', 'themes/material'),
+{% for theme in horizon_custom_themes %}
+     ('{{ theme.name|e }}', '{{ theme.label|e }}', '/etc/openstack-dashboard/themes/{{ theme.name|e }}'),
+{% endfor %}
+]
+{% endif %}
 
 LOGGING = {
     'version': 1,
diff --git a/doc/source/reference/shared-services/horizon-guide.rst b/doc/source/reference/shared-services/horizon-guide.rst
index 89d19c72f21ad78ceeee187776ddc8357108b983..2660c21fb10dfebab718541850f468dcdf79dff6 100644
--- a/doc/source/reference/shared-services/horizon-guide.rst
+++ b/doc/source/reference/shared-services/horizon-guide.rst
@@ -27,3 +27,27 @@ a file named custom_local_settings should be created under the directory
                 ('material', 'Material', 'themes/material'),
    ]
 
+As a result material theme will be the only one available, and used by default.
+Other way of setting default theme is shown in the next section.
+
+Adding custom themes
+--------------------
+
+It is possible to add custom themes to be available for Horizon
+by using ``horizon_custom_themes`` configuration variable in ``globals.yml``.
+This entry updates AVAILABLE_THEMES adding the new theme at the list end.
+
+.. code-block:: yaml
+
+   horizon_custom_themes:
+     - name: my_custom_theme
+       label: CustomTheme
+
+Theme files have to be copied into:
+``{{ node_custom_config }}/horizon/themes/my_custom_theme``.
+The new theme can be set as default in custom_local_settings:
+
+.. code-block:: python
+
+   DEFAULT_THEME = 'my_custom_theme'
+
diff --git a/releasenotes/notes/horizon-custom-themes-41b3f349dc118e88.yaml b/releasenotes/notes/horizon-custom-themes-41b3f349dc118e88.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0486bf00d56af3c5dc9060920d083cba184cebc4
--- /dev/null
+++ b/releasenotes/notes/horizon-custom-themes-41b3f349dc118e88.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    ``horizon`` deployment now supports custom themes.