npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fionoble/preact-custom-elements

v1.0.0

Published

Framework for building Web Components with Preact

Downloads

9

Readme

Preact Custom Elements

A lightweight framework for building Web Components with Preact. Write your components in Preact and automatically convert them into standards-compliant Custom Elements.

Features

  • Write components using familiar Preact syntax
  • Automatic attribute-to-props mapping
  • Support for camelCase props and kebab-case attributes
  • Custom event dispatching
  • Shadow DOM support (configurable)
  • TypeScript support
  • Minimal overhead

Installation

pnpm add preact-custom-elements preact

Quick Start

1. Create a Preact Component

import { register } from 'preact-custom-elements';
import { useState } from 'preact/hooks';

interface CounterProps {
  initialCount?: number;
  step?: number;
  onChange?: (count: number) => void;
}

function Counter({ initialCount = 0, step = 1, onChange }: CounterProps) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    const newCount = count + step;
    setCount(newCount);
    onChange?.(newCount);
  };

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// Register as a custom element
register(Counter, 'my-counter', {
  attributes: ['initial-count', 'step'],
  shadow: true,
  events: ['change']
});

2. Use in HTML

<my-counter initial-count="10" step="5"></my-counter>

<script>
  const counter = document.querySelector('my-counter');

  // Listen to custom events
  counter.addEventListener('change', (e) => {
    console.log('Count changed:', e.detail);
  });

  // Update properties dynamically
  counter.initialCount = 20;
</script>

API

register(Component, tagName, options)

Registers a Preact component as a Custom Element.

Parameters

  • Component (ComponentType<P>): Your Preact component
  • tagName (string): The custom element tag name (must contain a hyphen)
  • options (WebComponentOptions): Configuration options

Options

interface WebComponentOptions {
  // Attributes that will be observed and converted to props
  attributes?: string[];

  // Attributes to observe for changes (defaults to attributes)
  observedAttributes?: string[];

  // Enable Shadow DOM (true, false, or ShadowRootInit)
  shadow?: boolean | ShadowRootInit;

  // Event names to dispatch as CustomEvents
  events?: string[];
}

Features Explained

Attribute Mapping

Attributes are automatically converted from kebab-case to camelCase:

<my-component initial-value="10" user-name="John"></my-component>

Maps to:

<MyComponent initialValue={10} userName="John" />

Type Conversion

Attribute values are automatically parsed:

  • "true" / "false" → boolean
  • "123" → number
  • '{"key": "value"}' → object (JSON.parse)
  • Other strings → string

Shadow DOM

Control Shadow DOM behavior:

// Enable with default settings
register(Component, 'my-element', { shadow: true });

// Disable Shadow DOM
register(Component, 'my-element', { shadow: false });

// Custom Shadow DOM config
register(Component, 'my-element', {
  shadow: { mode: 'closed', delegatesFocus: true }
});

Custom Events

Components can dispatch custom events:

function MyButton({ onClick }: { onClick?: (value: string) => void }) {
  return (
    <button onClick={() => onClick?.('clicked!')}>
      Click me
    </button>
  );
}

register(MyButton, 'my-button', {
  events: ['click']
});
<my-button></my-button>

<script>
  document.querySelector('my-button')
    .addEventListener('click', (e) => {
      console.log(e.detail); // 'clicked!'
    });
</script>

Slots

The framework supports both default and named slots when Shadow DOM is enabled. Slot content is automatically extracted from the host element and passed to your component via the slots prop.

Using Slots in Components

import { Slot } from 'preact-custom-elements';

interface CardProps {
  slots?: {
    default?: any[];
    named: Record<string, any[]>;
  };
}

function Card({ slots }: CardProps) {
  const header = slots?.named?.header;
  const footer = slots?.named?.footer;

  return (
    <div class="card">
      {header && <div class="header">{header}</div>}

      <div class="content">
        {slots?.default || <Slot />}
      </div>

      {footer && <div class="footer">{footer}</div>}
    </div>
  );
}

register(Card, 'my-card', { shadow: true });

Using Slots in HTML

<my-card>
  <h2 slot="header">Card Title</h2>

  <p>This goes in the default slot.</p>
  <p>Multiple elements are supported!</p>

  <div slot="footer">Footer content</div>
</my-card>

Key Features:

  • Automatic Extraction: Slot content is automatically extracted from the host element
  • Named Slots: Use slot="name" attribute to target named slots
  • Default Slot: Content without a slot attribute goes to the default slot
  • Reactive Updates: Slot content automatically updates when children change
  • Shadow DOM Only: Slots only work when Shadow DOM is enabled (shadow: true)

The slots prop structure:

{
  default?: VNode[];      // Default slot content
  named: {                // Named slot content
    [slotName: string]: VNode[]
  }
}

Examples

Simple User Card

interface UserCardProps {
  name?: string;
  email?: string;
  avatar?: string;
}

function UserCard({ name, email, avatar }: UserCardProps) {
  return (
    <div class="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
}

register(UserCard, 'user-card', {
  attributes: ['name', 'email', 'avatar'],
  shadow: true
});
<user-card
  name="Jane Doe"
  email="[email protected]"
  avatar="https://example.com/avatar.jpg"
></user-card>

Development

# Install dependencies
pnpm install

# Build the library
pnpm build

# Run example
pnpm example

Build Configuration

The project includes Rollup configuration for building both CommonJS and ES modules:

pnpm build  # Creates dist/index.js and dist/index.esm.js

TypeScript

Full TypeScript support with type definitions included. The framework infers prop types from your component.

Browser Support

Works in all modern browsers that support:

  • Custom Elements v1
  • Shadow DOM v1
  • ES2020

License

MIT