
Slidev Components
- 132 installs
- 33 repo stars
- Updated January 30, 2026
- yoanbernabeu/slidev-skills
Teaches how to embed Vue components into Slidev markdown presentations, including built-in components and custom interactive elements.
About
slidev-components teaches developers how to use Vue components within Slidev presentations, covering the full built-in component library and the process of building custom interactive elements. Components embed directly into Markdown slide files, enabling dynamic content without complex tooling. Reach for it when you want to add interactive features, reusable elements, or dynamic content to a Slidev presentation and need guidance on the component system.
- Covers all Slidev built-in components
- Guides creating custom Vue components for presentations
- Components embed directly into Markdown slides
- Enables interactive and dynamic presentation content
- 109 installs from a 32-star repository
Slidev Components by the numbers
- 132 all-time installs (skills.sh)
- +6 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #987 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yoanbernabeu/slidev-skills --skill slidev-componentsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 132 |
|---|---|
| repo stars | ★ 33 |
| Last updated | January 30, 2026 |
| Repository | yoanbernabeu/slidev-skills ↗ |
What it does
Teaches how to embed Vue components into Slidev markdown presentations, including built-in components and custom interactive elements.
Who is it for?
Best when you're building interactive technical presentations with Slidev
Skip if: Static slide decks without interactivity
What you get
- Vue component code
- interactive slide content
Files
Slidev Components
This skill covers using Vue components in Slidev, including all built-in components and how to create custom interactive elements for your presentations.
When to Use This Skill
- Adding interactive elements to slides
- Using built-in Slidev components
- Creating custom Vue components
- Building reusable presentation elements
- Adding dynamic content
Using Components
Components can be used directly in Markdown:
# My Slide
<MyComponent />
<Counter :start="5" />Built-in Components
Arrow
Draws an arrow between points.
<Arrow x1="10" y1="20" x2="100" y2="200" />
<Arrow
x1="10"
y1="20"
x2="100"
y2="200"
color="#f00"
width="3"
/>Props:
x1,y1: Start coordinatesx2,y2: End coordinatescolor: Arrow colorwidth: Line width
VDragArrow
Draggable arrow (useful for presentations).
<VDragArrow />AutoFitText
Automatically adjusts font size to fit container.
<AutoFitText :max="200" :min="50" modelValue="My Text" />Props:
max: Maximum font sizemin: Minimum font sizemodelValue: Text content
LightOrDark
Renders different content based on theme.
<LightOrDark>
<template #light>
<img src="/logo-dark.png" />
</template>
<template #dark>
<img src="/logo-light.png" />
</template>
</LightOrDark>Link
Navigation link to other slides.
<Link to="42">Go to slide 42</Link>
<Link to="/intro">Go to intro</Link>SlideCurrentNo / SlidesTotal
Display slide numbers.
Slide <SlideCurrentNo /> of <SlidesTotal />Toc (Table of Contents)
Generates a table of contents.
<Toc />
<Toc maxDepth="2" />
<Toc mode="onlyCurrentTree" />Props:
maxDepth: Maximum heading depthmode: Display mode (all,onlyCurrentTree,onlySiblings)
Transform
Applies CSS transforms.
<Transform :scale="1.5">
<div>Scaled content</div>
</Transform>
<Transform :scale="0.8" :rotate="10">
Rotated and scaled
</Transform>Props:
scale: Scale factorrotate: Rotation in degrees
Tweet
Embeds a tweet.
<Tweet id="1234567890" />
<Tweet id="1234567890" scale="0.8" />Youtube
Embeds a YouTube video.
<Youtube id="dQw4w9WgXcQ" />
<Youtube id="dQw4w9WgXcQ" width="560" height="315" />Props:
id: YouTube video IDwidth,height: Dimensions
SlidevVideo
Embeds a video file.
<SlidevVideo v-click autoplay controls>
<source src="/video.mp4" type="video/mp4" />
</SlidevVideo>Props:
autoplay: Auto-play on slide entercontrols: Show video controlsloop: Loop video
RenderWhen
Conditional rendering based on context.
<RenderWhen context="slide">
Only visible in slide view
</RenderWhen>
<RenderWhen context="presenter">
Only visible in presenter view
</RenderWhen>Context options: slide, presenter, previewNext, print
VDrag
Makes elements draggable.
<VDrag>
<div class="p-4 bg-blue-500 text-white">
Drag me!
</div>
</VDrag>
<VDrag :initialX="100" :initialY="50">
Positioned draggable
</VDrag>Animation Components
VClick
Reveals on click.
<v-click>
Revealed on first click
</v-click>
<v-click at="2">
Revealed on second click
</v-click>VClicks
Reveals children sequentially.
<v-clicks>
- First item
- Second item
- Third item
</v-clicks>Props:
depth: Depth for nested listsevery: Items per click
VAfter
Reveals with the previous element.
<v-click>First</v-click>
<v-after>Appears with first</v-after>VSwitch
Switches between content based on clicks.
<v-switch>
<template #1>Step 1 content</template>
<template #2>Step 2 content</template>
<template #3>Step 3 content</template>
</v-switch>Creating Custom Components
Basic Component
Create components/Counter.vue:
<script setup>
import { ref } from 'vue'
const props = defineProps({
start: {
type: Number,
default: 0
}
})
const count = ref(props.start)
</script>
<template>
<div class="counter">
<button @click="count--">-</button>
<span class="count">{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<style scoped>
.counter {
display: flex;
align-items: center;
gap: 1rem;
}
button {
padding: 0.5rem 1rem;
font-size: 1.5rem;
cursor: pointer;
}
.count {
font-size: 2rem;
min-width: 3rem;
text-align: center;
}
</style>Usage:
# Interactive Counter
<Counter :start="10" />Component with Slots
<!-- components/Card.vue -->
<script setup>
defineProps({
title: String,
color: {
type: String,
default: 'blue'
}
})
</script>
<template>
<div :class="`card card-${color}`">
<h3 v-if="title">{{ title }}</h3>
<slot />
</div>
</template>
<style scoped>
.card {
padding: 1.5rem;
border-radius: 0.5rem;
margin: 1rem 0;
}
.card-blue { background: #3b82f6; color: white; }
.card-green { background: #22c55e; color: white; }
.card-red { background: #ef4444; color: white; }
</style>Usage:
<Card title="Important" color="red">
This is a red card with important content.
</Card>Component with Slidev Context
<!-- components/ProgressBar.vue -->
<script setup>
import { computed } from 'vue'
import { useNav } from '@slidev/client'
const { currentSlideNo, total } = useNav()
const progress = computed(() =>
(currentSlideNo.value / total.value) * 100
)
</script>
<template>
<div class="progress-bar">
<div
class="progress"
:style="{ width: `${progress}%` }"
/>
</div>
</template>
<style scoped>
.progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 4px;
background: #e5e7eb;
}
.progress {
height: 100%;
background: #3b82f6;
transition: width 0.3s;
}
</style>Code Demo Component
<!-- components/CodeDemo.vue -->
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
code: String,
language: {
type: String,
default: 'javascript'
}
})
const output = ref('')
const error = ref('')
const run = () => {
try {
output.value = eval(props.code)
error.value = ''
} catch (e) {
error.value = e.message
output.value = ''
}
}
</script>
<template>
<div class="code-demo">
<pre><code>{{ code }}</code></pre>
<button @click="run">Run</button>
<div v-if="output" class="output">{{ output }}</div>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>Composables
useNav
Access navigation state:
<script setup>
import { useNav } from '@slidev/client'
const {
currentSlideNo, // Current slide number
total, // Total slides
next, // Go to next
prev, // Go to previous
go // Go to specific slide
} = useNav()
</script>useSlideContext
Access slide context:
<script setup>
import { useSlideContext } from '@slidev/client'
const {
$slidev, // Global context
$clicks, // Current click count
$page // Current page number
} = useSlideContext()
</script>Global Components
global-top.vue
Appears above all slides:
<!-- global-top.vue -->
<template>
<div class="absolute top-4 right-4">
<img src="/logo.png" class="h-8" />
</div>
</template>global-bottom.vue
Appears below all slides:
<!-- global-bottom.vue -->
<template>
<footer class="absolute bottom-4 left-4 text-sm opacity-50">
© 2025 My Company
</footer>
</template>Component Patterns
Progress Indicator
<div class="fixed bottom-4 right-4 text-sm">
<SlideCurrentNo /> / <SlidesTotal />
</div>Social Links
<!-- components/SocialLinks.vue -->
<template>
<div class="flex gap-4">
<a href="https://twitter.com/..." target="_blank">
<carbon-logo-twitter class="text-2xl" />
</a>
<a href="https://github.com/..." target="_blank">
<carbon-logo-github class="text-2xl" />
</a>
</div>
</template>QR Code
<!-- components/QRCode.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import QRCodeLib from 'qrcode'
const props = defineProps({
url: String,
size: { type: Number, default: 200 }
})
const qrDataUrl = ref('')
onMounted(async () => {
qrDataUrl.value = await QRCodeLib.toDataURL(props.url, {
width: props.size
})
})
</script>
<template>
<img :src="qrDataUrl" :width="size" :height="size" />
</template>Best Practices
1. Keep Components Simple: Focus on single responsibilities 2. Use Props: Make components configurable 3. Style Scoped: Avoid global style pollution 4. Document Usage: Add comments showing how to use 5. Test Interactivity: Verify components work in presenter mode
Output Format
When creating components, provide:
COMPONENT: [name]
PURPOSE: [what it does]
FILE: components/[Name].vue
---
<script setup>
[script content]
</script>
<template>
[template content]
</template>
<style scoped>
[styles]
</style>
---
USAGE IN SLIDES:<[Name] prop="value" />
PROPS:
- [propName]: [type] - [description]