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

@seahax/elemental

v0.8.7

Published

Functional, reactive, web component base library.

Readme

@seahax/elemental

Functional, reactive, web component base library.

Contains everything you need to build anything from a single component up to a full reactive application, with minimal overhead.

  • Create fully portable web components
  • Direct and safe access to the DOM (no virtual DOM)
  • React-style code reuse using composable hooks
  • Global & local state reactivity
  • Client-side routing
  • No Build Tooling
  • No Dependencies
  • Tiny Bundle Size

NPM BundleJS (GZIP) builds.sr.ht status

Define A Web Component

import {
  defineComponent,
  h,
  useRef,
  useStore,
  useAttributes,
  useParent,
  useDocument,
  useRoute,
  useAsync,
  useEffect,
  useStyleEffect,
  useChildEffect,
  useDisconnectEffect,
  useElementInternals,
  useHost,
  useShadow,
} from '@seahax/elemental';

export const MyComponent = defineComponent((shadow) => {
  // This function is called every time the component is connected to the
  // document.
  // 
  // For the most part, a component's lifecycle should be handled as if it
  // starts on connect and ends on disconnect, even though the component
  // can be reconnected to the document. Restore internal state on
  // connection from host attributes and properties. Effect hooks are
  // designed to facilitate this pattern.

  // Create HTML elements and save references to them.
  const myInput = h('input');

  // Render content to the shadow DOM.
  h(shadow, [
    h('p', { class: 'hello' }, ['Hello, World!']),
    h('div', { class: 'inputs' }, [
      myInput,
    ]),
  ]);

  // Use a reference (reactive state) value.
  const localStateRef = useRef('initial value', (newValue) => {
    // Handle
  });

  // Use a reference (reactive state) bound to a (shared) store.
  const globalStateRef = useStore(myStore, select, mutate);

  // Use references (reactive state) bound to the component's attributes.
  const [dataValueRef, ...] = useAttributes('data-value', ...);

  // Use a reference (reactive state) bound to the component's parent node.
  const parentNode = useParent();

  // Use a reference (reactive state) bound to the component's owner document.
  const ownerDocument = useDocument();

  // Use references (reactive state) bound to route properties.
  const [pathnameRef, searchRef, hashRef] = useRoute('pathname', 'search', 'hash');

  // Use a reference (reactive state) bound to an async loader function.
  const asyncRef = useAsync([
    // dependency references
  ], async (signal, ...dependencyValues) => {
    // Reactive async code runs when the component is connected to the
    // document, and when any of the dependencies change. The signal is
    // aborted if the dependencies change before the promise returned by
    // this function is resolved.
  });

  // React to reference changes.
  useEffect([
    // dependency references
    localStateRef,
    globalStateRef,
    dataValueRef,
    pathnameRef,
    searchRef,
    hashRef,
    asyncRef,
  ], (...dependencyValues) => {
    // Reactive code runs when the component is connected to the document,
    // and when any of the dependencies change.

    return () => {
      // Cleanup after dependency refs are changed (before the next effect
      // callback) and after the component is disconnected from the
      // document.
    };
  });

  // React to reference changes and apply CSS styles to the shadow root.
  useStyleEffect([
    // dependency references
    localStateRef
  ], (localState) => {
    // Reactive code runs when the component is connected to the document,
    // and when any of the dependencies change.

    // Return a CSS string to be adopted by the shadow root.
    return /* css */ `
      .hello {
        color: ${localState};
      }
    `;
  });

  // React to child list changes.
  useChildEffect(() => {
    // Reactive code runs when the component is connected to the document,
    // and when the children of the component change. Access the current
    // children using the `shadow.host.children` property.

    return () => {
      // Cleanup before the next effect callback and after the component
      // is disconnected from the document.
    };
  });

  // Register a document disconnection callback.
  useDisconnectEffect(() => {
    // Called when the component is disconnected from the document.
  });

  // Use the element's internals.
  const elementInternals = useElementInternals();

  // Use the component host element.
  const host = useHost();

  // Use the component shadow root.
  const shadow = useShadow();
});

Add Styles

const MyComponent = defineComponent(
  (shadow) => {
    ...
  },
  {
    // Styles to be adopted by the shadow root. String values are parsed
    // as CSS to create new `CSSStyleSheet` instances.
    styles: [cssText, cssStyleSheet],
  }
)

Customize The Shadow Root

const MyComponent = defineComponent(
  (shadow) => {
    ...
  },
  {
    // Use custom shadow root initialization options.
    // (default: { mode: 'open' }).
    shadow: {
      mode: 'closed',
      ...
    },
  }
);

Enable Form Association

import {
  useElementInternals,
  useForm,
  useFormDisabled,
  useFormResetCallback,
  useFormRestoreCallback,
} from '@seahax/elemental';

const MyComponent = defineComponent(
  (shadow) => {
    // Use the element's internals.
    const elementInternals = useElementInternals();

    // Use a reference (reactive state) bound to the associated form.
    const formRef = useForm();

    // Use a reference (reactive state) bound to the form disabled state.
    const formDisabledRef = useFormDisabled();

    // Register a form reset callback.
    useFormResetCallback(() => {
      // Called when the associated form is reset. Only called on connect
      // if the form was reset while the component was disconnected.
    });

    // Register a form restore callback.
    useFormRestoreCallback((state, reason) => {
      // Called when the associated form is restored. Only called on connect
      // if the form was restored while the component was disconnected.
    });
  },
  {
    // Enable form association.
    formAssociated: true,
  }
);

Add Web Component Properties

interface Props {
  checked: boolean;
}

const MyComponent = defineComponent<Props>(
  (shadow, propRefs) => {
    // Get properties (the ref value is initially undefined).
    const isChecked = propRefs.checked.value ?? shadow.host.hasAttribute('checked');

    // Set properties.
    propRefs.checked.value = true;

    // Alternatively, access the property on the host element.
    const isChecked = shadow.host.checked;
    shadow.host.checked = true;

    // React to property changes.
    useEffect([propRefs.checked], (checked) => {
      ...
    });
  },
  {
    props: {
      // Return a property descriptor that uses a pre-defined ref and the
      // host element. The property descriptor must have a `get` function,
      // `value` is not allowed, and all other properties are optional.
      // The ref value is initially undefined.
      checked: (ref, host) => {
        return {
          get: () => ref.value ?? host.hasAttribute('checked'),
          set: (value) => (ref.value = value),
        };
      },
    },
  }),
);

const element = new MyComponent();

// Properties are defined publicly on component (`HTMLElement`) instances.
element.checked = true;

Register The Custom Element On Definition

const MyComponent = defineComponent((shadow) => {
  ...
}, {
  // Register the component as a custom element with this tag name.
  tagName: 'my-component',
});

Extend Component Definition

import { extendComponentDefinition } from '@seahax/elemental';

// Create a new component definition factory (ie. `defineComponent`
// function) with common styles and hooks (callbacks) that can be used to
// add shared behavior to all defined components.
export const defineComponent = extendComponentDefinition({
  styles: [...],
  preInit: (shadow) => {...},
  postInit: (shadow) => {...},
  preRender: (shadow) => {...},
  postRender: (shadow) => {...},
});

Render An Element

// By tag name.
const element = h('div', {
  // Set attributes.
  class: 'my-class',
  // Set properties.
  ':id': 'my-id',
}, [
  // Set children.
  h('p', [text]),
]);

// By custom element constructor.
const element = h(MyComponent, {
  // Set attributes.
  class: 'my-class',
  // Set properties.
  ':id': 'my-id',
}, [
  // Set children.
  h('p', [text]),
]);

Update An Element

// Update an existing element.
h(element, {
  // Set attributes.
  class: 'my-class',
  // Remove attributes.
  class: null,
  // Set properties.
  ':id': 'my-id',
  // Attributes and properties that are not provided are left alone.
}, [
  // Replace all children. Children are left alone if no child array is
  // provided (undefined or omitted).
  h('p', [text]),
]);

Combine Conditional Classes

import { classes } from '@seahax/elemental';

// Using a dictionary of boolean values.
const classNames = classes({
  'my-class-0': true,
  'my-class-1': false,
  'my-class-2': undefined,
  'my-class-3': null,
  'my-class-4': true,
}); // = 'my-class-0 my-class-4'

// Using a sparse array.
const classNames = classes([
  'my-class-0',
  false,
  undefined,
  null,
  'my-class-4',
]); // = 'my-class-0 my-class-4'

Define A Router Component

import { defineRouter } from '@seahax/elemental/router';

export const Router = defineRouter({
  // (optional) Register the component as a custom element with this tag name.
  tagName: 'ce-router',
  // (optional) Define a component to render when a route parameters callback
  // throws an error.
  invalid: MyInvalidComponent,
  // (optional) Define a fallback component to render when no route matches.
  fallback: MyFallbackComponent,
})
  // Simple exact pathname match.
  .addRoute('/', MyHomeComponent)
  // Exact pathname match with search (query) parameters.
  .addRoute('/blog', MyBlogIndexComponent, (pathParams, searchParams) => {
    // Get search (query) parameters.
    const tag = searchParams.get('tag') ?? '';
    // Validate parameters.
    assert(!/[^a-z0-9-]/iu.test(tag), 'Invalid tag.');
    // Return attributes to be set on the component.
    return {
      'data-blog-tag': tag,
    };
  })
  // Dynamic pathname match with a path parameter. Path parameters match
  // exactly one path segment (does not match forward slashes).
  .addRoute('/blog/:id', MyBlogPostComponent, (pathParams) => {
    // Validate parameters.
    assert(!/[^a-z0-9-]/iu.test(pathParams.id), 'Invalid blog ID.');
    // Return attributes to be set on the component.
    return {
      'data-blog-id': pathParams.id,
    };
  })
  // Dynamic pathname match with a path splat parameter. Splats match one
  // or more (not zero) path segments at the end of the pathname.
  .addRoute('/resources/*path', MyResourcesComponent, (pathParams) => {
    return {
      'data-path': validatePath(pathParams.path),
    }
  })
  .build();