UIPackage

Code Block

Vue data-display
Edit on GitHub

Read-only code preview with a header, optional filename, copy button, and `<pre>`-rendered content. Use for installation snippets, API examples, and snippets you want users to copy verbatim.

Also available for React ->

Installation

$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/code-block.json

Or with the named registry: npx shadcn-vue@latest add @uipkge/code-block

Examples

Props

Name Type / Values Default Required
code

Source code to display.

string required
language

Language label shown in the header.

string 'vue' optional
showLineNumbers

Render line numbers in the gutter.

boolean true optional
maxHeight

Maximum height of the code body before scrolling kicks in.

string '400px' optional
defaultExpanded

Render expanded on first paint. Default true.

boolean true optional
showHeader

Show the language label / copy / collapse header.

boolean true optional
class HTMLAttributes['class'] optional

Dependencies

Files (2)

  • app/components/ui/code-block/CodeBlock.vue 3.5 kB
    <script setup lang="ts">
    import { computed, ref } from 'vue'
    import { Check, ChevronDown, ChevronUp, Copy } from 'lucide-vue-next'
    import type { HTMLAttributes } from 'vue'
    import { cn } from '@/lib/utils'
    import { Button } from '@/components/ui/button'
    
    interface Props {
      /** Source code to display. */
      code: string
      /** Language label shown in the header. */
      language?: string
      /** Render line numbers in the gutter. */
      showLineNumbers?: boolean
      /** Maximum height of the code body before scrolling kicks in. */
      maxHeight?: string
      /** Render expanded on first paint. Default true. */
      defaultExpanded?: boolean
      /** Show the language label / copy / collapse header. */
      showHeader?: boolean
      class?: HTMLAttributes['class']
    }
    
    const props = withDefaults(defineProps<Props>(), {
      language: 'vue',
      showLineNumbers: true,
      maxHeight: '400px',
      defaultExpanded: true,
      showHeader: true,
    })
    
    const isExpanded = ref(props.defaultExpanded)
    const copied = ref(false)
    
    const lines = computed(() => props.code.replace(/\s+$/, '').split('\n'))
    
    async function copyToClipboard() {
      try {
        await navigator.clipboard.writeText(props.code)
        copied.value = true
        setTimeout(() => (copied.value = false), 1600)
      } catch (e) {
        console.warn('Clipboard write failed', e)
      }
    }
    </script>
    
    <template>
      <div
        data-uipkge
        data-slot="code-block"
        :class="cn('group border-border bg-muted/20 relative overflow-hidden rounded-lg border', props.class)"
      >
        <!-- Header -->
        <div v-if="showHeader" class="border-border bg-muted/40 flex items-center justify-between gap-2 border-b px-3 py-2">
          <span class="text-muted-foreground text-xs font-medium tracking-wide uppercase">
            {{ language }}
          </span>
          <div class="flex items-center gap-1">
            <Button variant="ghost" size="xs" class="h-7 gap-1.5 px-2" @click="copyToClipboard">
              <Check v-if="copied" class="text-success size-3" aria-hidden="true" />
              <Copy v-else class="size-3" aria-hidden="true" />
              <span class="text-xs">{{ copied ? 'Copied' : 'Copy' }}</span>
            </Button>
            <Button variant="ghost" size="xs" class="h-7 gap-1.5 px-2" @click="isExpanded = !isExpanded">
              <ChevronUp v-if="isExpanded" class="size-3" aria-hidden="true" />
              <ChevronDown v-else class="size-3" aria-hidden="true" />
              <span class="text-xs">{{ isExpanded ? 'Hide' : 'Show' }} code</span>
            </Button>
          </div>
        </div>
    
        <!-- Code body. We render line-by-line so the gutter and the code stay in
             sync without measuring text height. The renderer is intentionally
             plain: code is displayed as-is and the consumer can layer their own
             syntax highlighter (shiki, prism, hljs) on top by replacing this
             file or by wrapping it in their own component. -->
        <div
          v-show="isExpanded"
          class="bg-background/40 overflow-auto font-mono text-sm leading-relaxed"
          :style="{ maxHeight: props.maxHeight }"
        >
          <pre class="m-0 flex"><div
              v-if="showLineNumbers"
              aria-hidden="true"
              class="text-muted-foreground/60 sticky left-0 select-none border-r border-border/60 bg-muted/30 px-3 py-3 text-right tabular-nums"
            ><div v-for="(_, i) in lines" :key="i">{{ i + 1 }}</div></div><code
              class="block flex-1 px-4 py-3"
            ><template v-for="(line, i) in lines" :key="i"><span>{{ line || ' ' }}</span><br v-if="i < lines.length - 1"></template></code></pre>
        </div>
      </div>
    </template>
  • app/components/ui/code-block/index.ts 0.1 kB
    export { default as CodeBlock } from './CodeBlock.vue'

Raw manifest: https://uipkge.dev/r/vue/code-block.json