Using Vue.js to Create a UI Component for WordPress

· 2 min read

WordPress and Vue.js might seem like an odd pairing, but there are plenty of situations where you want a reactive UI component — a filterable list, a multi-step form, a dynamic pricing table — without rebuilding the whole site as a headless app.

Here's the pragmatic approach: use Vue from a CDN and mount it on a specific element in your page template.

The Setup

No build step needed for simple components. Add Vue via CDN in your template or via wp_enqueue_scripts:

function enqueue_vue() {
    wp_enqueue_script(
        'vue',
        'https://unpkg.com/vue@3/dist/vue.global.prod.js',
        array(),
        '3',
        true
    );
    wp_enqueue_script(
        'my-component',
        get_template_directory_uri() . '/js/service-filter.js',
        array( 'vue' ),
        '1.0.0',
        true
    );
}
add_action( 'wp_enqueue_scripts', 'enqueue_vue' );

A Simple Filterable List

Say you have a list of services pulled from a custom post type, and you want the user to be able to filter by category without a page reload.

In your PHP template, render the data as a JSON variable:

<div id="service-filter-app" data-services="<?php echo esc_attr( json_encode( $services_data ) ); ?>">
</div>

Then in service-filter.js:

const { createApp, ref, computed } = Vue;

createApp({
    setup() {
        const el = document.getElementById('service-filter-app');
        const services = ref(JSON.parse(el.dataset.services));
        const activeFilter = ref('all');

        const filtered = computed(() => {
            if (activeFilter.value === 'all') return services.value;
            return services.value.filter(s => s.category === activeFilter.value);
        });

        return { services, activeFilter, filtered };
    },
    template: `
        <div>
            <div class="filters">
                <button @click="activeFilter = 'all'" :class="{ active: activeFilter === 'all' }">All</button>
            </div>
            <ul>
                <li v-for="service in filtered" :key="service.id">
                    {{ service.title }}
                </li>
            </ul>
        </div>
    `
}).mount('#service-filter-app');

When to Use This Pattern

This CDN + inline approach works well for:

  • Single interactive components on a mostly static page
  • Client projects where you don't control the build environment
  • Adding reactivity to an existing theme without breaking anything

For more complex applications with multiple components, routing, or state management, a proper build step with Vite is worth the setup cost.

This post is a placeholder — full content coming soon.