diff --git a/aleksis/core/static/js/ical_urls.js b/aleksis/core/static/js/copy_button.js
similarity index 100%
rename from aleksis/core/static/js/ical_urls.js
rename to aleksis/core/static/js/copy_button.js
diff --git a/aleksis/core/templates/core/ical/ical_list.html b/aleksis/core/templates/core/ical/ical_list.html
index 0bd56e3fdf8cd1a03ead22549016dd54b78ea939..2388feb2daa9c4b31abb80bc830055e2d2d3efc6 100644
--- a/aleksis/core/templates/core/ical/ical_list.html
+++ b/aleksis/core/templates/core/ical/ical_list.html
@@ -1,5 +1,5 @@
 {% extends 'core/base.html' %}
-{% load i18n msg_box static %}
+{% load i18n msg_box static html_helpers %}
 
 {% block page_title %}{% trans "ICal Feeds" %}{% endblock page_title %}
 {% block browser_title %}{% trans "ICal Feeds" %}{% endblock browser_title %}
@@ -25,17 +25,14 @@
              class="secondary-content btn-flat btn-small primary-color-text btn-smaller-padding waves-effect waves-primary">
             <i class="material-icons iconify" data-icon="mdi:pencil-outline"></i>
           </a>
-          <button type="button" data-target="input-{{ forloop.counter0 }}"
-                  class="secondary-content btn-flat btn-small secondary-color-text btn-smaller-padding copy-button waves-effect waves-secondary">
-            <i class="material-icons iconify copy-icon-copy" data-icon="mdi:content-copy"></i>
-            <i class="material-icons iconify copy-icon-success" style="display: none" data-icon="mdi:check"></i>
-          </button>
+          {% generate_random_id "ical-copy-" as id %}
+          {% include "core/partials/copy_button.html" with classes="secondary-content btn-flat btn-small secondary-color-text btn-smaller-padding" target=id %}
         </span>
         <p class="ical-description">{{ object.ical_feed_object.title }} – {{ object.ical_feed_object.description }}</p>
         <input type="url" readonly value="https://{{ request.site }}{{ object.get_absolute_url }}"
-               id="input-{{ forloop.counter0 }}">
+               id="{{ id }}">
       </div>
     {% endfor %}
   </div>
-  <script src="{% static "js/ical_urls.js" %}" type="text/javascript"></script>
+  <script src="{% static "js/copy_button.js" %}" type="text/javascript"></script>
 {% endblock content %}
diff --git a/aleksis/core/templates/core/partials/copy_button.html b/aleksis/core/templates/core/partials/copy_button.html
new file mode 100644
index 0000000000000000000000000000000000000000..2ca0168333bc7215a580b05316c4556db436bb12
--- /dev/null
+++ b/aleksis/core/templates/core/partials/copy_button.html
@@ -0,0 +1,5 @@
+<button type="button" data-target="{{ target }}"
+        class="{{ classes }} copy-button waves-effect waves-secondary">
+  <i class="material-icons iconify copy-icon-copy" data-icon="mdi:content-copy"></i>
+  <i class="material-icons iconify copy-icon-success" style="display: none" data-icon="mdi:check"></i>
+</button>
diff --git a/aleksis/core/templatetags/html_helpers.py b/aleksis/core/templatetags/html_helpers.py
index 5f78b1b195f9030cc97d96a0488c14845d450741..34066192527930c2a4096f49deb3d247f883c31e 100644
--- a/aleksis/core/templatetags/html_helpers.py
+++ b/aleksis/core/templatetags/html_helpers.py
@@ -1,3 +1,6 @@
+import random
+import string
+
 from django import template
 
 from bs4 import BeautifulSoup
@@ -22,3 +25,18 @@ def add_class_to_el(value: str, arg: str) -> str:
         el["class"] = el.get("class", []) + [cls]
 
     return str(soup)
+
+
+@register.simple_tag
+def generate_random_id(prefix: str, length: int = 10) -> str:
+    """Generate a random ID for templates.
+
+    :Example:
+
+    .. code-block::
+
+        {% generate_random_id "prefix-" %}
+    """
+    return prefix + "".join(
+        random.choice(string.ascii_lowercase) for i in range(length)  # noqa: S311
+    )