Code Block
<pre><code> + copy button
Styled code display with language badge and one-click copy. Supports tabbed multi-language blocks. VitePress handles syntax highlighting automatically via Shiki — these patterns are for rendering code in your Nuxt app.
Overview
Preview
bash
npm install @headlessui/vue
npm install @nuxtjs/tailwindcssnpm install @headlessui/vue
npm install @nuxtjs/tailwindcssSingle language block
html
<div class="overflow-hidden rounded-xl border border-zinc-800 bg-zinc-900">
<!-- Header bar -->
<div class="flex items-center justify-between border-b border-zinc-800 px-4 py-2.5">
<span class="text-[10px] font-semibold uppercase tracking-widest text-zinc-500">bash</span>
<button @click="copy"
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-all"
:class="copied ? 'text-emerald-400' : 'text-zinc-600 hover:bg-zinc-800 hover:text-zinc-300'"
>
<!-- copy / check icon -->
{{ copied ? 'Copied!' : 'Copy' }}
</button>
</div>
<!-- Code -->
<pre class="overflow-x-auto px-4 py-4 text-[13px] leading-relaxed font-mono text-zinc-300"><code>{{ code }}</code></pre>
</div>Copy to clipboard (Vue)
vue
<script setup>
import { ref } from 'vue'
const props = defineProps({ code: String })
const copied = ref(false)
async function copy() {
try {
await navigator.clipboard.writeText(props.code)
} catch {
// fallback for older browsers
const el = document.createElement('textarea')
el.value = props.code
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
}
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
}
</script>Tabbed multi-language block
vue
<script setup>
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = [
{ lang: 'npm', code: 'npm install @headlessui/vue' },
{ lang: 'pnpm', code: 'pnpm add @headlessui/vue' },
{ lang: 'yarn', code: 'yarn add @headlessui/vue' },
]
</script>
<template>
<div class="overflow-hidden rounded-xl border border-zinc-800 bg-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-800 pr-3">
<!-- Tab headers -->
<div class="flex">
<button
v-for="(tab, i) in tabs"
:key="tab.lang"
@click="activeTab = i"
class="px-4 py-2.5 text-[11px] font-semibold uppercase tracking-widest transition-colors"
:class="activeTab === i
? 'border-b-2 border-[#D40C37] text-zinc-200'
: 'text-zinc-600 hover:text-zinc-400'"
>{{ tab.lang }}</button>
</div>
<!-- Copy button -->
<button @click="copy(tabs[activeTab].code)" ...>Copy</button>
</div>
<pre class="overflow-x-auto px-4 py-4 text-[13px] leading-relaxed font-mono text-zinc-300"><code>{{ tabs[activeTab].code }}</code></pre>
</div>
</template>Shiki in Nuxt (server-side, recommended)
For production, use Shiki for proper tokenised syntax highlighting:
bash
npm install shikivue
<!-- server component or composable -->
<script setup>
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({
themes: ['vesper'], // dark theme
langs: ['vue', 'ts', 'bash', 'html', 'css'],
})
const html = highlighter.codeToHtml(code, {
lang: 'vue',
theme: 'vesper',
})
</script>
<template>
<div class="rounded-xl border border-zinc-800 bg-zinc-900 overflow-hidden">
<!-- Shiki outputs its own <pre><code> with inline colour tokens -->
<div v-html="html" class="[&_pre]:overflow-x-auto [&_pre]:p-4 [&_pre]:text-[13px]"></div>
</div>
</template>Accessibility notes
<pre><code>is the semantic element for preformatted code — don't replace it with<div>- Add
aria-label="Copy code"on the copy button since it may contain only an icon - After a successful copy, announce with
role="status"or by changing the button text (which is already done viacopiedstate) - Long lines should scroll horizontally — set
overflow-x: autoand never break line wrapping on code blocks