A minimal example demonstrating how to use Elena web components (@elenajs/core) inside a React 19 application, built with Vite and 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.
All examples live in src/App.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:5173 in your browser.
| Command | Description |
|---|---|
pnpm start |
Start the Vite dev server |
pnpm build |
Type-check and build for production |
pnpm preview |
Preview the production build locally |
src/
├── main.tsx # React entry point (mounts <App />)
├── App.tsx # Main component, all Elena examples live here
├── App.css # Application styles
├── elena.d.ts # TypeScript JSX declarations for Elena elements
└── vite-env.d.ts # Vite environment types
index.html # HTML shell
vite.config.ts # Vite configuration
tsconfig.json # TypeScript configuration
Import the component and its styles, then use it as a regular JSX tag:
import "@elenajs/components/dist/button.js"; // registers <elena-button>
import "@elenajs/components/dist/button.css"; // button styles
function App() {
return (
<>
<elena-button variant="primary" onClick={() => console.log("clicked!")}>
Click me
</elena-button>
</>
);
}Elena exports a CustomElements type map from @elenajs/components that describes every element’s attributes. To get full IntelliSense and type-checking for Elena elements in React, you need to wire this into React’s JSX type system.
- Create a type declaration file (e.g.
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/jsx-runtime" {
namespace JSX {
interface IntrinsicElements extends ElenaIntrinsicElements {}
}
}
declare module "react/jsx-dev-runtime" {
namespace JSX {
interface IntrinsicElements extends ElenaIntrinsicElements {}
}
}- Make sure
tsconfig.jsonincludes the file, thesrcdirectory must be in theincludearray:
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "bundler"
},
"include": ["src"]
}- Restart the TypeScript server in your editor (in VSCode:
Cmd+Shift+P→ "TypeScript: Restart TS Server").
CustomElementsis a type map exported by@elenajs/componentsthat maps tag names (e.g."elena-button") to their prop types (e.g.ButtonProps & BaseProps & BaseEvents).- The
ElenaIntrinsicElementstype extends each entry with React event handlers (onClick,onFocus,onBlur) andchildren, since these aren’t part of the web component attribute definitions. - The
declare moduleblocks augment React’s JSX namespace so that TypeScript recognizes<elena-button>,<elena-stack>, etc. as valid JSX elements with typed props. - Both
react/jsx-runtime(production) andreact/jsx-dev-runtime(development) are augmented because Vite’s"jsx": "react-jsx"transform uses different runtimes depending on the mode.