slidev-components
Leverage Vue components in Slidev slides. Use this skill to add interactivity with built-in components or create custom ones.
How do I install this agent skill?
npx skills add https://github.com/yoanbernabeu/slidev-skills --skill slidev-componentsIs this agent skill safe to install?
- Gen Agent Trust Hubwarn
The skill provides documentation and examples for Slidev components, but includes a 'CodeDemo' example that uses the dangerous 'eval()' function. This pattern allows for arbitrary code execution if the component is provided with untrusted input. Additionally, the skill demonstrates fetching content from external services and using third-party libraries.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerwarn
1/1 file flagged
- ZeroLeakspass
2 findings · Score: 80/100
What does this agent skill do?
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
- Keep Components Simple: Focus on single responsibilities
- Use Props: Make components configurable
- Style Scoped: Avoid global style pollution
- Document Usage: Add comments showing how to use
- 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:
```markdown
<[Name] prop="value" />
PROPS:
- [propName]: [type] - [description]
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/yoanbernabeu/slidev-skills/slidev-components">View slidev-components on skillZs</a>