Using Vue in Markdown
Learn how to use Vue components and features directly in your Markdown files.
Vue Components in Markdown
Basic Component Usage
You can use Vue components directly in Markdown:
markdown
<script setup>
import CustomButton from './components/CustomButton.vue'
</script>
# My Page
Here's a custom button component:
<CustomButton text="Click me!" @click="handleClick" />
Regular markdown content continues here.
Inline Components
Define components inline:
markdown
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>
# Counter Example
<div class="counter">
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
<style scoped>
.counter {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
margin: 20px 0;
}
button {
background: #42b883;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
</style>
Global Components
Register components globally in your theme:
javascript
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
import CustomButton from './components/CustomButton.vue'
import InfoBox from './components/InfoBox.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('CustomButton', CustomButton)
app.component('InfoBox', InfoBox)
}
}
Then use them anywhere in Markdown:
markdown
# My Page
<InfoBox type="warning">
This is a warning message using a global component.
</InfoBox>
<CustomButton>Global Button</CustomButton>
Vue Expressions
Template Expressions
Use Vue template expressions in Markdown:
markdown
# Dynamic Content
Current year: {{ new Date().getFullYear() }}
Math calculation: {{ 2 + 3 * 4 }}
Conditional text: {{ user ? `Hello, ${user.name}!` : 'Please log in' }}
Reactive Data
Use reactive data in expressions:
markdown
<script setup>
import { ref, computed } from 'vue'
const name = ref('World')
const greeting = computed(() => `Hello, ${name.value}!`)
</script>
# Interactive Greeting
<input v-model="name" placeholder="Enter your name" />
{{ greeting }}
Vue Directives
v-if and v-show
Conditional rendering:
markdown
<script setup>
import { ref } from 'vue'
const showDetails = ref(false)
const isLoggedIn = ref(false)
</script>
# Conditional Content
<button @click="showDetails = !showDetails">
{{ showDetails ? 'Hide' : 'Show' }} Details
</button>
<div v-if="showDetails" class="details">
<p>Here are the details!</p>
</div>
<div v-show="isLoggedIn">
Welcome back, user!
</div>
<div v-if="!isLoggedIn">
Please log in to continue.
</div>
v-for
List rendering:
markdown
<script setup>
const items = [
{ id: 1, name: 'Apple', price: 1.20 },
{ id: 2, name: 'Banana', price: 0.80 },
{ id: 3, name: 'Orange', price: 1.50 }
]
const users = ['Alice', 'Bob', 'Charlie']
</script>
# Dynamic Lists
## Fruit Prices
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - ${{ item.price }}
</li>
</ul>
## User List
<ol>
<li v-for="(user, index) in users" :key="index">
{{ index + 1 }}. {{ user }}
</li>
</ol>
v-model
Two-way data binding:
markdown
<script setup>
import { ref } from 'vue'
const message = ref('Hello VitePress!')
const selected = ref('option1')
const checked = ref(false)
const multiSelect = ref([])
</script>
# Form Controls
## Text Input
<input v-model="message" />
<p>Message: {{ message }}</p>
## Select
<select v-model="selected">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<p>Selected: {{ selected }}</p>
## Checkbox
<label>
<input type="checkbox" v-model="checked" />
Check me
</label>
<p>Checked: {{ checked }}</p>
## Multiple Checkboxes
<label>
<input type="checkbox" value="vue" v-model="multiSelect" />
Vue
</label>
<label>
<input type="checkbox" value="react" v-model="multiSelect" />
React
</label>
<label>
<input type="checkbox" value="angular" v-model="multiSelect" />
Angular
</label>
<p>Selected: {{ multiSelect.join(', ') }}</p>
Event Handling
Click Events
Handle user interactions:
markdown
<script setup>
import { ref } from 'vue'
const count = ref(0)
const message = ref('')
function increment() {
count.value++
}
function showAlert() {
alert('Button clicked!')
}
function handleInput(event) {
message.value = event.target.value.toUpperCase()
}
</script>
# Event Handling
<button @click="increment">
Clicked {{ count }} times
</button>
<button @click="showAlert">
Show Alert
</button>
<input @input="handleInput" placeholder="Type something" />
<p>Uppercase: {{ message }}</p>
Custom Events
Handle custom component events:
markdown
<script setup>
import { ref } from 'vue'
import CustomInput from './components/CustomInput.vue'
const inputValue = ref('')
function handleCustomEvent(value) {
inputValue.value = value
console.log('Custom event received:', value)
}
</script>
# Custom Events
<CustomInput @custom-change="handleCustomEvent" />
<p>Value from custom component: {{ inputValue }}</p>
Lifecycle Hooks
Component Lifecycle
Use Vue lifecycle hooks:
markdown
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const time = ref(new Date().toLocaleTimeString())
let timer = null
onMounted(() => {
console.log('Component mounted')
timer = setInterval(() => {
time.value = new Date().toLocaleTimeString()
}, 1000)
})
onUnmounted(() => {
console.log('Component unmounted')
if (timer) {
clearInterval(timer)
}
})
</script>
# Live Clock
Current time: {{ time }}
Watchers
Watch for data changes:
markdown
<script setup>
import { ref, watch } from 'vue'
const searchTerm = ref('')
const searchResults = ref([])
watch(searchTerm, async (newTerm) => {
if (newTerm.length > 2) {
// Simulate API call
searchResults.value = [
`Result 1 for "${newTerm}"`,
`Result 2 for "${newTerm}"`,
`Result 3 for "${newTerm}"`
]
} else {
searchResults.value = []
}
})
</script>
# Search Example
<input v-model="searchTerm" placeholder="Search..." />
<ul v-if="searchResults.length > 0">
<li v-for="result in searchResults" :key="result">
{{ result }}
</li>
</ul>
<p v-else-if="searchTerm.length > 0 && searchTerm.length <= 2">
Type at least 3 characters to search
</p>
Computed Properties
Basic Computed
Create computed properties:
markdown
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
const reversedName = computed(() => {
return fullName.value.split('').reverse().join('')
})
</script>
# Computed Properties
<input v-model="firstName" placeholder="First name" />
<input v-model="lastName" placeholder="Last name" />
<p>Full name: {{ fullName }}</p>
<p>Reversed: {{ reversedName }}</p>
Complex Computed
More complex computed logic:
markdown
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'Apple', price: 1.20, category: 'fruit' },
{ name: 'Carrot', price: 0.80, category: 'vegetable' },
{ name: 'Banana', price: 0.90, category: 'fruit' },
{ name: 'Broccoli', price: 2.00, category: 'vegetable' }
])
const selectedCategory = ref('all')
const filteredItems = computed(() => {
if (selectedCategory.value === 'all') {
return items.value
}
return items.value.filter(item => item.category === selectedCategory.value)
})
const totalPrice = computed(() => {
return filteredItems.value.reduce((sum, item) => sum + item.price, 0).toFixed(2)
})
</script>
# Shopping List
<select v-model="selectedCategory">
<option value="all">All Categories</option>
<option value="fruit">Fruits</option>
<option value="vegetable">Vegetables</option>
</select>
<ul>
<li v-for="item in filteredItems" :key="item.name">
{{ item.name }} - ${{ item.price }} ({{ item.category }})
</li>
</ul>
<p><strong>Total: ${{ totalPrice }}</strong></p>
Styling
Scoped Styles
Add component-specific styles:
markdown
<script setup>
import { ref } from 'vue'
const isActive = ref(false)
</script>
# Styled Component
<div class="card" :class="{ active: isActive }">
<h3>Card Title</h3>
<p>This is a styled card component.</p>
<button @click="isActive = !isActive">
{{ isActive ? 'Deactivate' : 'Activate' }}
</button>
</div>
<style scoped>
.card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
transition: all 0.3s ease;
}
.card.active {
border-color: #42b883;
box-shadow: 0 4px 12px rgba(66, 184, 131, 0.15);
}
.card h3 {
margin-top: 0;
color: #2c3e50;
}
.card button {
background: #42b883;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
.card button:hover {
background: #369870;
}
</style>
CSS Modules
Use CSS modules for styling:
markdown
<script setup>
import { ref } from 'vue'
import styles from './styles.module.css'
const count = ref(0)
</script>
# CSS Modules Example
<div :class="styles.container">
<button :class="styles.button" @click="count++">
Count: {{ count }}
</button>
</div>
Advanced Patterns
Composables
Create and use composables:
markdown
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// Composable for mouse position
function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// Composable for local storage
function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
function setValue(newValue) {
value.value = newValue
localStorage.setItem(key, JSON.stringify(newValue))
}
return { value, setValue }
}
// Use composables
const { x, y } = useMouse()
const { value: savedText, setValue: setSavedText } = useLocalStorage('savedText', '')
</script>
# Composables Example
## Mouse Position
Mouse position: {{ x }}, {{ y }}
## Local Storage
<input :value="savedText" @input="setSavedText($event.target.value)" />
<p>Saved text: {{ savedText }}</p>
Provide/Inject
Share data across components:
markdown
<script setup>
import { provide, inject, ref } from 'vue'
// Provider component
const theme = ref('light')
provide('theme', theme)
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
</script>
# Theme Provider
<div :class="`theme-${theme}`">
<button @click="toggleTheme">
Switch to {{ theme === 'light' ? 'dark' : 'light' }} theme
</button>
<ChildComponent />
</div>
<style scoped>
.theme-light {
background: white;
color: black;
}
.theme-dark {
background: #1a1a1a;
color: white;
}
</style>
Best Practices
Performance
- Use
v-show
instead ofv-if
for frequently toggled elements - Implement proper key attributes for
v-for
- Use computed properties for expensive calculations
- Lazy load components when possible
Accessibility
- Add proper ARIA attributes
- Ensure keyboard navigation works
- Use semantic HTML elements
- Provide alternative text for images
Code Organization
- Keep components small and focused
- Use composables for reusable logic
- Separate concerns properly
- Add TypeScript for better development experience
SEO Considerations
- Be aware that dynamic content may not be indexed
- Use server-side rendering for critical content
- Provide fallbacks for JavaScript-disabled users
- Test with search engine crawlers