Adding copy-to-clipboard functionality to your Webflow site can significantly enhance user experience, especially when dealing with shareable content, promotional codes, or technical snippets. This guide will walk you through implementing a robust, attribute-based solution that's both flexible and user-friendly.
Here are some practical applications where copy-to-clipboard functionality can enhance your Webflow site:
Our solution uses a set of carefully designed attributes that provide control over copying behavior. These attributes let you add copy buttons anywhere on your Webflow site, control what text gets copied, and customize how users see that the copy worked. Everything works directly in Webflow without needing any external tools.
The script also includes built-in accessibility features, with ARIA live regions for screen reader announcements and keyboard accessibility support.
Let's implement the copy-to-clipboard 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 need the copy-to-clipboard functionality on specific pages (like just your pricing page or documentation), you can add the script to individual page settings instead of the global project settings. Here's how:
This approach can help maintain cleaner code and faster load times on pages that don't need the copy functionality.
After adding the script, here's how to set up a copy button with all available options:
brix-copy-clipboard
brix-copy-clipboard-text
brix-copy-clipboard-successtext
brix-copy-clipboard-successclass
brix-copy-clipboard-revertms
This configuration creates a button that copies your specified text when clicked, shows a confirmation message, and can temporarily change its appearance to provide visual feedback.
Before publishing, ensure your implementation works correctly:
Implementing copy-to-clipboard functionality in Webflow using custom attributes provides a powerful, flexible solution for enhancing user interaction with your content. Whether you're sharing promotional codes, technical snippets, or contact information, this implementation offers a seamless experience while maintaining clean, professional design standards.
Need help implementing more advanced copying features or exploring other custom Webflow solutions? Our experienced 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 a copy URL button to your Webflow site. Step-by-step guide for implementing easy page sharing without plugins or code.
Complete tutorial explaining how to prevent users from specific countries from accessing your Webflow website.