A minimal example demonstrating how to use Elena web components (@elenajs/core) inside a Next.js 15 application with TypeScript.
Elena is a lightweight (2kB) library for building Progressive Web Components that work natively in the browser. This example uses both @elenajs/core and @elenajs/components, an example component library built on top of Elena.
Examples live in src/app/page.tsx and cover:
- Variants:
default,primary, anddangerbutton styles - Interactive counter: React state (
useState) driving Elena button click handlers - Dynamic attribute binding: cycling through variants reactively via JSX expressions
- Node.js v20+
- pnpm v10+
# Install dependencies
pnpm install
# Start the development server
pnpm startOpen http://localhost:3000 in your browser.
| Command | Description |
|---|---|
pnpm start |
Start the Next.js dev server |
pnpm build |
Type-check and build for production |
pnpm preview |
Preview the production build locally |
src/
├── app/
│ ├── layout.tsx # Root layout, mounts ElementsRegistry and imports CSS
│ ├── page.tsx # Main page, all Elena examples live here
│ ├── counter.tsx # Interactive counter (client component)
│ ├── variant-cycler.tsx # Dynamic variant cycling (client component)
│ └── globals.css # Application styles
├── elements-registry.tsx # Client component that lazy-loads all Elena elements
└── elena.d.ts # TypeScript JSX declarations for Elena elements
tsconfig.json # TypeScript configuration
next.config.ts # Next.js configuration
Because Elena components access the DOM on hydration, Next.js needs to be configured in a way that supports this. To achieve this, create a "use client" component with useEffect to defer Elena hydration to the browser:
// src/elements-registry.tsx
"use client";
import { useEffect } from "react";
export default function ElementsRegistry() {
useEffect(() => {
import("@elenajs/components");
}, []);
return null;
}Mount it once in the root layout so components are available everywhere. Elena components can then be used directly in Server Components for markup and styles, with interactivity handled by client components.
Create a type declaration file to wire Elena’s types into React’s JSX namespace:
// src/elena.d.ts
import type { CustomElements } from "@elenajs/components";
type ElenaIntrinsicElements = {
[K in keyof CustomElements]: CustomElements[K] & {
onClick?: (e: MouseEvent) => void;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
children?: React.ReactNode;
};
};
declare module "react" {
namespace JSX {
interface IntrinsicElements extends ElenaIntrinsicElements {}
}
}