From cem
Generates guidance for integrating custom elements into Vue apps, including compiler config for Vue 3 (Vite, Nuxt, Vue CLI), Vue 2 ignoredElements, and usage examples with attributes, events, slots.
npx claudepluginhub bennypowers/cem --plugin cemThis skill uses the workspace's default tool permissions.
Generate framework-specific guidance for using custom elements in Vue, including required compiler configuration.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Automates semantic versioning and release workflow for Claude Code plugins: bumps versions in package.json, marketplace.json, plugin.json; verifies builds; creates git tags, GitHub releases, changelogs.
Generate framework-specific guidance for using custom elements in Vue, including required compiler configuration.
Search the project for Vue version and tooling:
package.json for vue version (2.x vs 3.x)vite.config), Vue CLI (vue.config), Nuxt (nuxt.config), or webpackRead the target element(s):
cem://element/{tagName}
cem://element/{tagName}/attributes
cem://element/{tagName}/events
cem://element/{tagName}/slots
Understand which features need framework-specific handling:
v-on / @ syntax works with custom events// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// Tell Vue to treat tags with a dash as custom elements
isCustomElement: (tag) => tag.includes('-'),
},
},
}),
],
});
If the project only uses elements from specific libraries, be more specific:
isCustomElement: (tag) => tag.startsWith('my-') || tag.startsWith('prefix-'),
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => ({
...options,
compilerOptions: {
isCustomElement: (tag) => tag.includes('-'),
},
}));
},
};
// nuxt.config.ts
export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('-'),
},
},
});
Vue 2 uses Vue.config.ignoredElements:
// main.js
Vue.config.ignoredElements = [
'my-element',
/^prefix-/, // regex for all elements with a prefix
];
Note: Vue 2 reached end-of-life on December 31, 2023. Consider migrating to Vue 3.
<template>
<my-element
variant="primary"
:disabled="isDisabled"
@my-event="handleEvent"
>
Default slot content
</my-element>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import 'my-element-library';
const isDisabled = ref(false);
function handleEvent(e: CustomEvent) {
console.log(e.detail);
}
</script>
<template>
<!-- Attribute binding (string coercion) -->
<my-element variant="primary" />
<!-- Property binding (passes JS values directly) -->
<my-element :complex-data="myObject" />
<!-- or explicitly with .prop modifier -->
<my-element :complex-data.prop="myObject" />
<!-- Boolean attribute -->
<my-element :disabled="true" />
<!-- or just present for true -->
<my-element disabled />
</template>
Vue 3 passes :bound values as properties if the element has a matching property, and as attributes otherwise. Use the .prop modifier to force property binding.
In Vue 2, use the .prop modifier explicitly:
<my-element :complex-data.prop="myObject" />
Vue's @ / v-on works with custom element events:
<template>
<!-- kebab-case event names work directly -->
<my-element @my-event="handleMyEvent" />
<!-- Access event detail -->
<my-element @change="(e) => value = e.detail.value" />
</template>
Note: Vue converts @myEvent to listen for my-event (camelCase to kebab-case). If the custom element fires a camelCase event name, use the explicit form:
<my-element v-on:myEvent="handler" />
Web component slots and Vue slots coexist but work differently:
Important: Do not use Vue's v-slot / #slotname syntax for web component slots. Use the native HTML slot="name" attribute instead.
<template>
<my-element>
<!-- Default slot -->
<span>Content goes to the default slot</span>
<!-- Named slots — use the native HTML slot attribute -->
<span slot="header">This goes to the web component's header slot</span>
<span slot="footer">This goes to the web component's footer slot</span>
</my-element>
</template>
Custom elements don't support v-model out of the box. Wire it up manually:
<template>
<my-input
:value="inputValue"
@input="inputValue = $event.detail.value"
/>
</template>
Or create a composable for reusable v-model binding:
function useCustomElementModel(eventName = 'input', detailKey = 'value') {
return {
modelValue: (props: any) => props.modelValue,
listener: (emit: any) => (e: CustomEvent) => emit('update:modelValue', e.detail[detailKey]),
};
}
Add type declarations for custom elements in Vue templates:
// custom-elements.d.ts
declare module 'vue' {
interface GlobalComponents {
'my-element': import('my-element-library').MyElement;
}
}
For more detailed type checking, use the element's type with Vue's HTMLAttributes:
declare module 'vue' {
interface GlobalComponents {
'my-element': DefineComponent<{
variant?: 'primary' | 'secondary';
disabled?: boolean;
}>;
}
}
Present the integration with:
slot attribute (not Vue v-slot)isCustomElement config: Without it, Vue warns about unresolved componentsslot attribute, not v-slot: This is the most common mistake when using web components in Vue:prop binding for non-strings: Vue's property binding passes values correctly to custom elements