Adding URL copy functionality to your Webflow site enhances content sharing and improves user experience. Whether you're running a blog, portfolio, or e-commerce site, making it easy for visitors to share your pages can significantly boost your site's reach and engagement.
This guide will show you how to implement a simple, attribute-based solution for adding a button for copying page URLs on your Webflow site.
Here are some practical applications where URL copy functionality can enhance your Webflow site:
Let's implement the URL copy functionality step by step:
<script>
/*!
* BRIX Templates Copy to Clipboard Attribute for Webflow
* ----------------------------------------------------------------------------
* Automatically enables copy-to-clipboard functionality on elements marked with
* the appropriate attributes. This script supports two main copying modes:
*
* 1. Custom Text Copying:
* - Add [brix-copy-clipboard="true"] to enable copying
* - Add [brix-copy-clipboard-text="Your text"] to specify what to copy
*
* 2. URL Copying:
* - Add [brix-copy-clipboard="true"] to enable copying
* - Add [brix-copy-clipboard-url="true"] to copy the current page URL
*
* Optional Attributes for Both Modes:
* - [brix-copy-clipboard-successtext="Copied!"]
* Shows a temporary success message after copying
*
* - [brix-copy-clipboard-successclass="is-copied"]
* Adds a temporary class for styling feedback
*
* - [brix-copy-clipboard-revertms="2000"]
* Controls how long success state lasts (default: 1500ms)
*
* The script includes:
* - Automatic success feedback
* - Screen reader support via ARIA
* - Keyboard accessibility
* - Detailed console logging for debugging
*
* Version: 1.0.1
* Author: BRIX Templates
*
*/
(function() {
'use strict';
//--------------------------------------------------------------------------
// 1) Configuration & Constants
//--------------------------------------------------------------------------
const SCRIPT_VERSION = '1.0.1';
const DEFAULT_REVERT_MS = 1500;
const ARIA_ANNOUNCE_DEFAULT = 'Copied to clipboard';
let ariaLiveContainer = null;
//--------------------------------------------------------------------------
// 2) Initialize on DOM Ready
//--------------------------------------------------------------------------
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBrixClipboard);
} else {
initBrixClipboard();
}
//--------------------------------------------------------------------------
// 3) Main Initialization
//--------------------------------------------------------------------------
function initBrixClipboard() {
// 3.1) Find all elements with brix-copy-clipboard="true"
const triggers = document.querySelectorAll('[brix-copy-clipboard="true"]');
const total = triggers.length;
if (total === 0) return;
// 3.2) Create a hidden ARIA-live region for screenreader announcements
ariaLiveContainer = createAriaLiveRegion();
// 3.3) Console logging: Intro summary
console.log('------------------------');
console.log(
'%c[BRIX Templates Copy to Clipboard for Webflow]%c v' + SCRIPT_VERSION +
' Activated. Found %c' + total + '%c element' + (total !== 1 ? 's' : '') + ' to manage:',
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;',
'font-weight:normal;'
);
console.log('------------------------');
// 3.4) For each trigger, parse attributes & log
triggers.forEach((elem, index) => {
const info = parseAttributes(elem, index);
// (A) Per-element line #1
console.log(
'- Element #%c' + (index + 1) + '%c: %c' + info.identifier,
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;'
);
// (B) Per-element line #2 - attributes summary
console.log(
' copyingURL=%c' + info.isUrl + '%c, text="%c' + info.textAttr + '%c", successText="%c' +
info.successText + '%c", successClass="%c' + info.successClass + '%c", revertMs=%c' + info.revertMs,
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;',
'font-weight:normal;',
'font-weight:bold;',
'font-weight:normal;'
);
// (C) Warnings & conflicts (same indentation)
if (info.urlAndTextConflict) {
console.warn(
' "url" specified alongside "text=' + info.textAttr +
'". "url" takes precedence, ignoring text.'
);
}
if (info.invalidRevertMs) {
console.warn(
' Invalid revertMs="' + info.revertMsStr + '". Using default ' + DEFAULT_REVERT_MS + '.'
);
}
// (D) Attach click listener
elem.addEventListener('click', (evt) => {
evt.preventDefault();
handleCopyAction(elem, index);
});
});
// 3.5) Final line after listing triggers
console.log('------------------------');
console.log(
'%c[BRIX Templates Copy to Clipboard for Webflow]%c All copy triggers have been initialized successfully.',
'font-weight:bold;',
'font-weight:normal;'
);
console.log('------------------------');
}
//--------------------------------------------------------------------------
// 4) Parse Attributes (for logging)
//--------------------------------------------------------------------------
// This function only logs the data; the actual fallback or overrides
// still happen in handleCopyAction for the real copy process.
function parseAttributes(elem, index) {
const identifier = getElementIdentifier(elem, index);
// Gather
const rawUrl = (elem.getAttribute('brix-copy-clipboard-url') || '').toLowerCase();
const isUrl = (rawUrl === 'true');
const textAttr = elem.getAttribute('brix-copy-clipboard-text') || '';
const successText = elem.getAttribute('brix-copy-clipboard-successtext') || '';
const successClass = elem.getAttribute('brix-copy-clipboard-successclass') || '';
const revertMsStr = elem.getAttribute('brix-copy-clipboard-revertms');
let revertMsNum = parseInt(revertMsStr, 10);
let invalidRevertMs = false;
if (isNaN(revertMsNum) || revertMsNum < 0) {
invalidRevertMs = !!revertMsStr; // only warn if the user tried to set a revertMs
revertMsNum = (invalidRevertMs) ? 0 : DEFAULT_REVERT_MS;
// We'll do the real fallback in handleCopyAction, but log here for clarity.
}
// Conflicts
const urlAndTextConflict = (isUrl && textAttr.trim().length > 0);
return {
identifier,
isUrl,
textAttr,
successText,
successClass,
revertMs: revertMsNum,
revertMsStr,
invalidRevertMs,
urlAndTextConflict
};
}
//--------------------------------------------------------------------------
// 5) Build a readable identifier for the element
//--------------------------------------------------------------------------
function getElementIdentifier(elem, index) {
if (elem.id) {
return 'ID: "' + elem.id + '"';
}
else if (elem.classList && elem.classList.length > 0) {
return 'Classes: "' + Array.from(elem.classList).join(' ') + '"';
}
else {
return 'element-' + (index + 1);
}
}
//--------------------------------------------------------------------------
// 6) Handle the actual copy action (on click)
//--------------------------------------------------------------------------
function handleCopyAction(element, index) {
const originalText = element.textContent;
// Collect attributes again for actual logic/fallback
const isUrl = (element.getAttribute('brix-copy-clipboard-url') || '').toLowerCase() === 'true';
const textAttr = element.getAttribute('brix-copy-clipboard-text');
const successText = element.getAttribute('brix-copy-clipboard-successtext') || '';
const successClass = element.getAttribute('brix-copy-clipboard-successclass') || '';
let revertMs = parseInt(element.getAttribute('brix-copy-clipboard-revertms'), 10);
if (isNaN(revertMs) || revertMs < 0) {
revertMs = DEFAULT_REVERT_MS;
// We no longer console.warn here, because we already did it in initBrixClipboard logs
}
// Decide what text to copy
let textToCopy = '';
if (isUrl) {
textToCopy = window.location.href;
} else if (textAttr) {
textToCopy = textAttr;
} else {
textToCopy = originalText.trim();
}
doCopyText(textToCopy);
// Provide success feedback
const finalSuccessText = successText || ARIA_ANNOUNCE_DEFAULT;
if (successText) {
element.textContent = successText;
}
if (successClass) {
element.classList.add(successClass);
}
announceAriaLive(finalSuccessText);
// Revert after revertMs
setTimeout(() => {
element.textContent = originalText;
if (successClass) {
element.classList.remove(successClass);
}
}, revertMs);
}
//--------------------------------------------------------------------------
// 7) Copy Logic (Modern + Fallback)
//--------------------------------------------------------------------------
function doCopyText(str) {
// Attempt modern async clipboard API first
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
navigator.clipboard.writeText(str).catch((err) => {
// Already in fallback, no bracket prefix here
console.warn(' Clipboard API error:', err);
fallbackCopyText(str);
});
}
// Attempt document.execCommand('copy') if available
else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
fallbackCopyText(str);
}
else {
console.warn(' No browser copy support found. Copy may fail.');
}
}
// Fallback for older browsers
function fallbackCopyText(str) {
const temp = document.createElement('textarea');
temp.value = str;
temp.setAttribute('readonly', '');
temp.style.position = 'absolute';
temp.style.left = '-9999px';
document.body.appendChild(temp);
temp.select();
try {
document.execCommand('copy');
} catch (err) {
console.warn(' execCommand("copy") failed:', err);
}
document.body.removeChild(temp);
}
//--------------------------------------------------------------------------
// 8) Create ARIA Live Region for Screenreaders
//--------------------------------------------------------------------------
function createAriaLiveRegion() {
let region = document.getElementById('brix-copy-aria-live');
if (region) return region;
region = document.createElement('div');
region.id = 'brix-copy-aria-live';
region.setAttribute('aria-live', 'polite');
region.setAttribute('aria-atomic', 'true');
region.style.position = 'absolute';
region.style.left = '-9999px';
region.style.width = '1px';
region.style.height = '1px';
region.style.overflow = 'hidden';
document.body.appendChild(region);
return region;
}
//--------------------------------------------------------------------------
// 9) Announce to Screenreaders
//--------------------------------------------------------------------------
function announceAriaLive(message) {
if (!ariaLiveContainer) return;
ariaLiveContainer.textContent = '';
setTimeout(() => {
ariaLiveContainer.textContent = message;
}, 50);
}
})();
</script>
If you only want URL copy buttons on certain pages (like blog posts or product pages), you can add the script to individual page settings instead:
This targeted approach helps maintain site performance by loading the copy URL functionality only where needed.
After adding the script, here's how to set up a URL copy button:
brix-copy-clipboard
brix-copy-clipboard-url
brix-copy-clipboard-successtext
brix-copy-clipboard-successclass
brix-copy-clipboard-revertms
This setup creates a button that copies the current page's URL when clicked, shows a confirmation, and can provide visual feedback through temporary styling.
Before publishing, verify your implementation:
Adding URL copy functionality to your Webflow site using custom attributes provides a seamless way to enhance content sharing and improve user experience. Whether you're running a blog, portfolio, or ecommerce site, this implementation makes it effortless for visitors to share your pages while maintaining professional design standards.
Need help implementing more advanced sharing features or exploring other custom Webflow solutions? Our top-notch Webflow agency specializes in creating tailored functionality that enhances your site's user experience while maintaining performance and reliability. Feel free to reach out to discuss how we can help optimize your Webflow project.
Learn how to add animated number counters to your Webflow site. This step-by-step guide explains how to do it in 5 minutes or less.
Learn how to add copy-to-clipboard button to your Webflow site in 5 minutes or less.
Complete tutorial explaining how to prevent users from specific countries from accessing your Webflow website.