Skip to content

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 of v-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

VitePress Development Guide