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.
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>
const { copy } = useClipboard()
copy('https://example.com/invite/abc123')
<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>
import { useClipboard } from '@vueuse/core'
const { text, read } = useClipboard({ read: true })
await read()
console.log(text.value)
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.
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>
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 }
}
aria-live="polite"
.<span class="sr-only" aria-live="polite">{{ copied ? 'Copied' : '' }}</span>
useClipboard
for the fastest and easiest pathuseClipboard
as a directive if you preferFinally, 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!
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.