Skip to content

React integration

Use Plain Elements custom elements in React with typed events and client-only registration.

Top-level imports call customElements.define. Import the library only in client code:

ClientComponent.tsx
"use client";
import "plain-elements/dialog";
import "plain-elements/collapsible";

In Next.js App Router, keep Plain Elements imports inside "use client" components or dynamic imports with { ssr: false }. Do not import the library in Server Components.

Use the custom element tags directly. Data attributes map to JSX prop names in camelCase or as strings:

export function SettingsCollapsible() {
return (
<pe-collapsible>
<button type="button" data-collapsible-trigger>
Show details
</button>
<div data-collapsible-panel>Additional settings content.</div>
</pe-collapsible>
);
}

Custom events bubble and are composed. Listen on document or on the host ref:

"use client";
import { useEffect, useRef } from "react";
import "plain-elements/tabs";
export function AccountTabs() {
const hostRef = useRef<HTMLElement>(null);
useEffect(() => {
const host = hostRef.current;
if (!host) {
return;
}
const onChange = (event: CustomEvent) => {
// Typed as CustomEvent<TabsEventDetail> when plain-elements types are loaded.
console.log(event.detail.value);
};
host.addEventListener("pe-tabs:change", onChange);
return () => host.removeEventListener("pe-tabs:change", onChange);
}, []);
return (
<pe-tabs ref={hostRef}>
<div data-tabs-list aria-label="Account">
<button type="button" data-tabs-trigger="profile">
Profile
</button>
<button type="button" data-tabs-trigger="security">
Security
</button>
</div>
<section data-tabs-content="profile">Profile panel</section>
<section data-tabs-content="security">Security panel</section>
</pe-tabs>
);
}

Call methods on the host ref:

hostRef.current?.open(); // Dialog, Popover, Tooltip, Collapsible
hostRef.current?.select("security"); // Tabs
console.log(hostRef.current?.isOpen); // open/close components

Import event detail types when you need explicit annotations:

import type { TabsEventDetail } from "plain-elements/tabs";

Subpath imports register only the components you use. See Styling for CSS guidance.