How to Copy to Clipboard In Vue

Copy-to-clipboard is a tiny UX win that makes apps feel polished—share links, copy API keys, invite codes, or code snippets. The easiest way to ship it in Vue is with VueUse’s useClipboard composable. After that, we’ll cover zero-dependency (native) patterns, a directive, SSR gotchas, and accessibility tips.
TL;DR - The Simplest Way to Implement Copy to Clipboard in Vue with VueUse
npm i @vueuse/core<script setup lang="ts">
import { ref } from 'vue'
import { useClipboard } from '@vueuse/core'
const source = ref('Hello from Vue + VueUse!')
const { copy, copied, isSupported } = useClipboard({ source })
async function onCopy() {
await copy() // copies source.value
// You can also do copy('override per call')
}
</script>
<template>
<div>
<code>{{ source }}</code>
<button @click="onCopy" :disabled="copied">
<span v-if="copied">Copied ✓</span>
<span v-else>Copy</span>
</button>
<p aria-live="polite" v-if="!isSupported">
Clipboard unsupported; using fallback.
</p>
</div>
</template>
Handy Variations of useClipboard
- Copy dynamic strings without wiring a ref:
const { copy } = useClipboard()
copy('https://example.com/invite/abc123')- Copy from a DOM element’s text:
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import { useClipboard } from '@vueuse/core'
const el = ref<HTMLElement | null>(null)
const { copy, copied } = useClipboard()
async function copyFromElement() {
await nextTick();
const text = el.value?.textContent ?? ''
if (text) copy(text)
}
</script>
<template>
<pre ref="el">npm create vue@latest</pre>
<button @click="copyFromElement">{{ copied ? 'Copied!' : 'Copy' }}</button>
</template>
- Read from the clipboard (opt-in):
import { useClipboard } from '@vueuse/core'
const { text, read } = useClipboard({ read: true })
await read()
console.log(text.value)
Copy To Clipboard In a Nuxt App
Copying to clipboard in a Nuxt app is also possible with VueUse’s useClipboard. Just make sure to call copy in event handlers or inside onMounted as the underlying navigator dependency doesn’t exist on the server.
A Tiny v-copy Directive (Powered by VueUse)
When you want a one-liner on buttons or icons everywhere it’s easy enough repurpose VueUse’s composable as a custom directive.
// /directives/copy.ts
import type { Directive } from 'vue'
import { useClipboard } from '@vueuse/core'
export const vCopy: Directive<HTMLElement, string | undefined> = {
mounted(el, binding) {
const { copy } = useClipboard()
el.addEventListener('click', () => {
const value =
binding.value ??
el.getAttribute('data-copy') ??
el.textContent ??
''
copy(value)
el.dispatchEvent(new CustomEvent('copied'))
})
},
}
Make sure to register the directive.
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { vCopy } from './directives/copy'
createApp(App).directive('copy', vCopy).mount('#app')
Then use it whenever you need it!
<button v-copy="'https://example.com/invite/abc'">Copy Invite Link</button>
<button v-copy data-copy="hard-coded">Copy Hard-Coded</button>
<button v-copy @copied="toast('Copied!')">Copy & Toast</button>Zero-Dependency Option (Native Clipboard API)
If you prefer no dependencies, the native API is concise. You can copy and paste the code below into your own projects, it includes a textarea fallback for older/quirky browsers.
// /composables/useClipboard.native.ts
import { ref } from 'vue'
export function useClipboard() {
const copied = ref(false)
const error = ref<unknown>(null)
const isSupported = typeof navigator !== 'undefined' && !!navigator.clipboard
function fallbackWriteText(text: string) {
const ta = document.createElement('textarea')
ta.value = text
ta.setAttribute('readonly', '')
ta.style.position = 'fixed'
ta.style.left = '-9999px'
document.body.appendChild(ta)
ta.select()
const ok = document.execCommand('copy')
document.body.removeChild(ta)
return ok
}
async function copy(text: string) {
error.value = null
try {
if (isSupported) await navigator.clipboard.writeText(text)
else if (!fallbackWriteText(text)) throw new Error('execCommand failed')
copied.value = true
setTimeout(() => (copied.value = false), 1200)
return true
} catch (e) {
error.value = e
return false
}
}
return { copy, copied, error, isSupported }
}
Accessibility (a11y) Tips for Copy-to-Clipboard in Vue.js
- Use clear labels (not just an icon).
- Announce status with
aria-live="polite". - Don’t steal focus; keep feedback subtle (toast/badge).
- Respect reduced motion in any “Copied!” animation.
<span class="sr-only" aria-live="polite">{{ copied ? 'Copied' : '' }}</span>Common Pitfalls with useClipboard
- Not HTTPS / not localhost: Clipboard API is secure-context only.
- No user gesture: Trigger from a click/tap.
- Safari quirks: Keep a fallback for older versions (provided by VueUse out of the box).
Final Checklist for Copy to Clipboard In Vue.js
- [ ] Start with VueUse
useClipboardfor the fastest and easiest path - [ ] Repurpose
useClipboardas a directive if you prefer - [ ] For a zero dependency solution, copy the example from this article
Finally, useClipboard is only one of VueUse’s many helpful composables. If you want to discover even more powerful helpers from this amazingly popular library, checkout our full VueUse course. In it, we go over useClipboard but also many other helpful composables like useDark, useFavicon, useInterval, and more!
Start learning Vue.js for free

Comments
Latest Vue School Articles
5 Component Design Patterns to Boost Your Vue.js Applications

Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.


