From cem
Generates React integration code for custom elements, adapting to React 18 (workarounds via @lit/react or refs) vs React 19 native support.
npx claudepluginhub bennypowers/cem --plugin cemThis skill uses the workspace's default tool permissions.
Generate framework-specific guidance for using custom elements in React, adapting to the project's React version.
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 React, adapting to the project's React version.
Search the project for React version:
# Check package.json for react version
grep -A1 '"react"' package.json
Determine if the project uses:
Also check for:
@lit/react or similar wrapper libraries are already in useRead the target element(s):
cem://element/{tagName}
cem://element/{tagName}/attributes
cem://element/{tagName}/events
Understand the element's API to know which features need framework-specific handling:
children and named slot patterns in JSXReact 19 supports custom elements natively. Properties are set correctly and custom events are handled.
// Direct usage — no wrappers needed
function MyComponent() {
return (
<my-element
variant="primary"
complexData={someObject}
onMyEvent={(e) => handleEvent(e)}
>
<span slot="icon">★</span>
Button text
</my-element>
);
}
Key React 19 behaviors:
on + PascalCase event name maps to custom events (e.g., onChange -> change)className is set as the class attributeReact 18 treats custom elements like unknown HTML elements — all props are set as attributes (stringified), and custom events are not supported via JSX.
Option A: Use @lit/react wrappers (recommended)
import { createComponent } from '@lit/react';
import { MyElement } from 'my-element-library';
export const MyElementReact = createComponent({
tagName: 'my-element',
elementClass: MyElement,
react: React,
events: {
onMyEvent: 'my-event',
onChange: 'change',
},
});
// Usage
function MyComponent() {
return (
<MyElementReact
variant="primary"
complexData={someObject}
onMyEvent={(e) => handleEvent(e)}
>
Button text
</MyElementReact>
);
}
Option B: Use refs for properties and events
function MyComponent() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
// Set complex properties via ref
(el as any).complexData = someObject;
// Add event listeners
const handler = (e: Event) => handleEvent(e);
el.addEventListener('my-event', handler);
return () => el.removeEventListener('my-event', handler);
}, [someObject]);
return (
<my-element ref={ref} variant="primary">
Button text
</my-element>
);
}
Option C: Use the CEM export react wrappers
If the project uses CEM's export command, check for pre-generated React wrappers:
cem export react
This generates typed React wrapper components from the manifest.
For both React versions, add JSX type declarations so TypeScript knows about custom element attributes:
// custom-elements.d.ts
import type { MyElement } from 'my-element-library';
declare global {
namespace JSX {
interface IntrinsicElements {
'my-element': React.DetailedHTMLProps<
React.HTMLAttributes<MyElement> & {
variant?: 'primary' | 'secondary';
disabled?: boolean;
// For React 18: only string attributes here
// For React 19: include property types too
},
MyElement
>;
}
}
}
For React 19, the declaration can include complex property types. For React 18, only include attributes (strings/booleans) since complex types won't pass through correctly without a wrapper.
Map named slots to JSX:
// HTML slots -> JSX
<my-element>
{/* Default slot */}
<span>Default content</span>
{/* Named slots */}
<span slot="header">Header content</span>
<span slot="footer">Footer content</span>
</my-element>
Present the integration code with:
| Feature | React 18 | React 19+ |
|---|---|---|
| String attributes | Passed as attributes | Passed as attributes |
| Boolean attributes | "" or undefined | Correct HTML behavior |
| Complex properties | Stringified (broken) | Set as properties |
| Custom events | Not supported in JSX | onEventName works |
className | Set as attribute | Set as class |
ref | Works | Works |
| SSR | Attributes only | Attributes only |
@lit/react wrappers for React 18: They handle properties and events correctly with minimal boilerplatedangerouslySetInnerHTML: Custom elements use slots, not innerHTML