React integration
Use Plain Elements custom elements in React with typed events and client-only registration.
Client-only registration
Section titled “Client-only registration”Top-level imports call customElements.define. Import the library only in client code:
"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.
JSX markup
Section titled “JSX markup”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> );}Listening to events
Section titled “Listening to events”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> );}Imperative control
Section titled “Imperative control”Call methods on the host ref:
hostRef.current?.open(); // Dialog, Popover, Tooltip, CollapsiblehostRef.current?.select("security"); // Tabsconsole.log(hostRef.current?.isOpen); // open/close componentsTypeScript
Section titled “TypeScript”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.