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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@auzmartist/hybrids-helpers

v1.0.3

Published

> Helper factories and functions for [Hybrids](https://hybrids.js.org/#/) Web Components.

Readme

hybrids-helpers

Helper factories and functions for Hybrids Web Components.

Installation

hybrids-helpers depends on hybrids >= 9.0.0

pnpm i @auzmartist/hybrids-helpers --save

Documentation

pnpm docs:preview

Motivation

For larger projects, Hybrids' delightfully simple API and terse syntax, while powerful, can result in some repetitive patterns and quality-of-life issues. In particular, if you find yourself implementing the same factory patterns and custom descriptors across your projects, this library may be helpful to you.

Usage

Hybrids offers deep configurability of each property which extends an HTMLElement. This can come at the cost of long winded properties. Consider the following sample:

Let's define a web component with a single property "bool" with some custom behaviors. "bool" should be false by default, but reflected as an attribute whenever it changes. It's value should also be set as a CSS Custom Property on the host AND logged to the console if truthy.

In Hybrids 9, we write this like so:

import { define, html } from 'hybrids'

define({
  tag: 'my-vanilla-component',
  bool: {
    value: false,
    reflect: true,
    observe(host, val, last) {
      if(!!val) {
        console.log(val)
      }
      host.style.setProperty('--custom-bit', val ? '1' : '0')
    }
  },
})

With hybrids-helpers, we can compress complex properties into functional poetry.

import { define, html } from 'hybrids'
import { reflect, effect, truthy, cssVar } from '@auzmartist/hybrids-helpers'

define({
  tag: 'my-helpers-powered-component',
  bool: reflect(effect(false,
    truthy((_, val) => console.log(val)),
    cssVar('--custom-bit', (val) => val ? '1' : '0')
  )),
})

Consult the full documentation for a complete list of helpers.


In 1.0.0, we introduce a build function to further simplify component writing.

import {build} from '@auzmartist/hybrids-helpers'

build(({ reflect, effect, truthy, cssVar }) => ({
  tag: 'my-component',
  bool: reflect(effect(false,
    truthy((_, val) => console.log(val)),
    cssVar('--custom-bit', (val) => val ? '1' : '0')
  ))
}))

The build function doesn't just simplify your imports. It provides safeguards around component re-definition, type currying, the full suite of exported helpers, and can even ornaments the html function for integration with other libraries like AlpineJS

Build

Safe Component Definition

The build helper provides save component definition to prevent accidental overwrites or naming conflicts.

define({tag: 'my-component', /* overwritten functionality */ })
define({tag: 'my-component', new: 'functionality' })

build(() => ({tag: 'my-safe-component', some: 'functionality' }))
build(() => ({tag: 'my-safe-component' })) // warns that the component is already defined.

Need to build now but compile later? Try build.compile. Works the same, but you can run customElements.define() later.

Type Currying

Hybrids has Typescript support, but component authors may find themselves type annotating the host repeatedly.

interface MyComponent extends HTMLElement {
  foo: string
  doubleFoo: string
}
define<MyComponent>({
  tag: 'my-component',
  foo: 'bar',
  doubleFoo: ({foo}: MyComponent) => `${foo}${foo}`,
})

The build helper can take care of the type inferencing for you. It's a small convenience, but it adds up.

interface MyComponent extends HTMLElement {
  foo: string
  doubleFoo: string
}
build<MyComponent>(() => ({
  tag: 'my-component',
  foo: 'bar',
  doubleFoo: ({foo}) => `${foo}${foo}`, // type annotations built-in
}))

To keep things terse, destructure your helpers when building elements:

build<any>(({ reflect, effect, cssVar }) => ({
  tag: 'my-component',
  // reflect the boolean to an attribute AND CSS custom property
  bool: reflect(effect(false, cssVar('--custom-bit'))),
}))

You

In 1.0.0, we introduce a build function to further simplify component writing.

import {build} from '@auzmartist/hybrids-helpers'

build(({ reflect, effect, truthy, cssVar }) => ({
  tag: 'my-component',
  bool: reflect(effect(false,
    truthy((_, val) => console.log(val)),
    cssVar('--custom-bit', (val) => val ? '1' : '0')
  ))
}))

The build function doesn't just simplify your imports. It provides safeguards around component re-definition, type currying, the full suite of exported helpers, and can even provision the html function for integration with other libraries like AlpineJS

Build

Safe Component Definition

The build helper provides save component definition to prevent accidental overwrites or naming conflicts.

define({tag: 'my-component', /* overwritten functionality */ })
define({tag: 'my-component', new: 'functionality' })

build(() => ({tag: 'my-safe-component', some: 'functionality' }))
build(() => ({tag: 'my-safe-component' })) // warns that the component is already defined.

Need to build now but compile later? Try build.compile. Works the same, but you can run customElements.define() later.

Type Currying

Hybrids has Typescript support, but component authors may find themselves type annotating the host repeatedly.

interface MyComponent extends HTMLElement {
  foo: string
  doubleFoo: string
}
define<MyComponent>({
  tag: 'my-component',
  foo: 'bar',
  doubleFoo: ({foo}: MyComponent) => `${foo}${foo}`,
})

The build helper can take care of the type inferencing for you. It's a small convenience, but it adds up.

interface MyComponent extends HTMLElement {
  foo: string
  doubleFoo: string
}
build<MyComponent>(() => ({
  tag: 'my-component',
  foo: 'bar',
  doubleFoo: ({foo}) => `${foo}${foo}`, // type annotations built-in
}))

To keep things terse, destructure your helpers when building elements:

build<any>(({ reflect, effect, cssVar }) => ({
  tag: 'my-component',
  // reflect the boolean to an attribute AND CSS custom property
  bool: reflect(effect(false, cssVar('--custom-bit'))),
}))

You

Templating

It can be difficult to manage complex templates in a single render function. The hy templating functions can help to break up the template into smaller, more manageable parts.

import { hy } from '@auzmartist/hybrids-helpers'

const template = (host: { type: string }) => html`
  <div>
    ${hy.if(host.type === 'foo', html`<span>Foo</span>`)}
    ${hy.case(host.type, {
      foo: html`<span>Foo</span>`,
      bar: html`<span>Bar</span>`,
      baz: html`<span>Baz</span>`,
      default: html`<span>Default</span>`,
    })}
  </div>
`

Alpine

Perhaps you're finding the the Hybrids template syntax difficult to read? Or maybe you have a need for some pseudo-private 'scoped' data which is not exposed as a property on the host element?

AlpineJS may be useful in these cases:

// This is awful syntax to read, especially if you have an autoformatter
define({
  tag: 'my-hybrids-element',
  boolList: { value: [false, true, true, false, false ] },
  render: ({boolList}) => html`
    ${boolList.map((bool) => html`
      ${bool ? html`
        <span>Truthy Content</span>
      ` : html`
        <span>Falsy Content</span>
      `}
    `)}
  `.css`
    /* CSS */
  `
})

Instead, let's clean things up:

import Alpine from 'alpinejs'
import {alpine, build} from '@auzmartist/hybrids-helpers'
alpine.config(Alpine) // important to register Alpine

build(({be}) => ({
  tag: 'my-alpine-element',
  boolList: be([false, true, true, false, false]),
  render: () => alpine`
    <template x-host x-for="bool in boolList">
      <span x-text="bool ? 'Truthy Content' : 'Falsy Content'"></span>
    </template>
  `
}))

Note the use of the x-host custom Alpine directive. This reactively binds properties from Hybrids to the Alpine data scope.

You can scope x-host to specific properties if needed:

build(({be}) => ({
  // ...
  render: () => alpine`
    <template x-host="['boolList']" x-for="bool in boolList">
      <span x-text="bool ? 'Truthy Content' : 'Falsy Content'"></span>
    </template>
  `
}))

Shared state between Hybrids and Alpine is seamless. You can update the variable in Alpine or Hybrids and the property will change in the other reactivity system.

Note: One exception are Hybrids computed properties. For these, use the xhost directive to give Alpine a nudge to update.

build(({xhost, html}) => ({
  counter: {
    value: 0,
    connect(host, key, invalidate) {
      const int = setInterval(() => {
        host[key]++
      }, 500)
    }
  },
  double: xhost(({count}) => count * 2), // 'xhost' triggers re-rendering
  render: () => html`
    <span x-host x-text="double"></span>
  `
}))

Alpine passes data by attribute by default. This can be imcompatible with property-based templating conventions in other web component libraries. To pass state to HTML element properties, use the x-host directive:

alpine`<my-component x-prop.arr="[0, 1, 2]"></my-component>`
// OR
alpine`<my-component x-prop="{ arr: [0, 1, 2] }"></my-component>`

Both x-host and x-prop are included via alpine.config() as directives by default. If you'd like to customize Alpine further, adding your own directives, be sure to also include xHost and xProp directives manually:

import Alpine from 'alpinejs'
import { alpine } from '@auzmartist/hybrids-helpers'

alpine.config(Alpine, {
  host: alpine.xHost,
  prop: alpine.xProp,
  custom: ..., // https://alpinejs.dev/advanced/extending
})