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

web-component-attributes

v0.1.1

Published

Tiny library to link HTML attributes with JS properties on Web Components

Readme

web-component-attributes

a tiny library to link HTML attributes with JS properties on Web Components

Rationale

The Web Components API allows you to use custom HTML attributes on your components, and provides a simple, flexible way to observe changes in their values. While this offers a lot of power in choosing how exactly your component should respond to attributes, it doesn't integrate the attributes seamlessly with Javascript the same way most built-in HTML attributes do.

As an example, take the id attribute. If you include an element <my-element id="foo"></my-element> in your HTML, you can access its id in JS in either of two ways: myElement.getAttribute('id') or myElement.id. Most devs choose the latter. The property access style is less verbose and therefore easier to read, but more importantly, it better represents the way we think about the operation. An element's id is, conceptually, one of many properties it may have as an object. When you access the id, what you're really doing is querying a key-value map of the element's attributes, but what it feels like you're doing is a simple property access.

In order to replicate this functionality for a custom attribute, the component needs to define a getter and setter for a property which acts as a proxy for the underlying HTML attribute. This isn't difficult, but it is boilerplate that would be nice to avoid. This package provides an alternative by programmatically creating getters and setters from a declarative description of an attribute.

Usage

The web-components-attributes package provides a single default export, the function WebComponentAttributesMixin(Component, attributes):

// Import the package
import WebComponentAttributesMixin from 'web-component-attributes';

This function takes a Web Component class Component as its first argument, and an object attributes describing the attributes to declare as its second argument. Component must be a Javascript class, such as one defined with the ES6 class syntax, which inherits from HTMLElement or one of its subclasses. The function is a mixin, meaning that it modifies the class it receives as its argument in order to add extra functionality to it. It's intended to be used like so:

class MyWebComponent extends HTMLElement {
    /* ... add some custom behavior ... */
}

// Set up properties which act as proxies for HTML attributes
WebComponentAttributesMixin(MyWebComponent, {
    /* ... attribute definitions ... */
});

customElements.define('my-web-component', MyWebComponent);

Attribute Definition Format

The attributes argument to WebComponentAttributesMixin takes the form of an object which acts as a key-value map. Each key (property name) is the name of a proxy property as you would spell it when using obj.prop accessor syntax in Javascript. Each corresponding value is an object which lists a type, a default value, and an htmlName:

WebComponentAttributesMixin(MyWebComponent, {
    myStringProperty: {
        type: 'string',
        default: "foo bar",
        htmlName: "my-string-attribute",
    },
    myNumberProperty: {
        type: 'number',
        default: 6.28,
        htmlName: "my-number-attribute",
    },
});

type determines the type of the JS property. All HTML attributes are stored as strings; when you get the value of the property, the getter will attempt to convert the string value to a value of the appropriate type, and when you set the value of the property, the new value will be stored in the backing attribute in a string representation. This is similar to the way numeric attributes like <input>'s size work. A complete list of supported types is in the Types section below.

default determines the default value of the JS property if the corresponding HTML attribute doesn't exist on the element or if the string value of the attribute can't be parsed into a valid value of the type. Custom elements created with the class constructor or with document.createElement(...) start without any attributes, so this will also be the value of the property when the element is first created. Note that it's possible for an attribute to be present but have its value as the empty string, in which case the getter will attempt to parse the empty string as a value of the type.

The default value may be of a different type than the listed type. This allows you to use sentinel values (e.g. null) to indicate the lack of a valid value.

htmlName determines the name of the attribute as it appears in HTML. This is also the name you'd use when accessing the attribute via getAttribute(...) and related methods.

If you used the example above to define .myStringProperty and .myNumberProperty proxies on MyWebComponent, you'd be able to use them as follows:

// x1 and x2 will be the same:
const x1 = myWebComponent.myStringProperty;
const x2 = myWebComponent.hasAttribute('my-string-attribute')
    ? myWebComponent.getAttribute('my-string-attribute')
    : "foo bar";
// This sets the HTML attribute my-string-attribute to "baz":
myWebComponent.myStringProperty = "baz";

// y1 and y2 will be the same:
const y1 = myWebComponent.myNumberProperty;
const y2 = myWebComponent.hasAttribute('my-number-attribute')
    ? parseFloat(myWebComponent.getAttribute('my-number-attribute'))
    : 6.28;
// This sets the HTML attribute my-number-attribute to "100":
myWebComponent.myNumberProperty = 100;

Other Attribute Definition Formats

In some cases you can use a shorthand version of the attribute declaration format, instead of writing out the full type, default, and htmlName.

If htmlName is omitted, it will be generated from the name of the JS property. This will convert the property's camel case name to a kebab case attribute name. The attribute name will always be in all lowercase letters.

WebComponentAttributesMixin(MyWebComponent, {
    someOptionalStringProperty: {
        type: 'string',
        default: null,
        // htmlName generated as "some-optional-string-property"
    },
});

If default is omitted, the default value of the property will be inferred to be null. The attributes specifiesNullDefault and nullDefaultInferred will thus behave identically:

WebComponentAttributesMixin(MyWebComponent, {
    specifiesNullDefault: {
        type: 'string',
        default: null,
        htmlName: "specifies-null-default",
    },
    nullDefaultInferred: {
        type: 'string',
        htmlName: "null-default-inferred",
    },
});

If, instead of an object containing type, default, and htmlName fields, you use a primitive value, default will be set to that value, type will be inferred from the value's type, and htmlName will be generated from the property name. The attributes explicit and implicit will thus behave identically:

WebComponentAttributesMixin(MyWebComponent, {
    explicit: {
        type: 'string',
        default: "foo bar",
    },
    implicit: "foo bar",
});

Strings, Numbers, BigInts, and Booleans will be inferred as type 'string', 'number', 'bigint', and 'boolean', respectively.

Types

This section is organized into subsections based on the value of the type field of an attribute.

'string'

The getter accesses the raw string data of the attribute. The setter stringifies the value it receives using .toString() to convert a value of any type into a string representation. This is the same as the usual behavior for getting and setting attributes, as with .getAttribute(...) and .setAttribute(...).

'number'

The getter attempts to parse a floating-point number from the attribute string. The setter requires that the value it receives is a Number, a BigInt, or a String from which a number can be parsed.

'bigint'

The getter attempts to parse a BigInt from the attribute string. If the setter receives a Number, it will first convert it to an integer before setting the attribute string. Otherwise, it will use JS's built-in BigInt(...) conversion function before stringifying the value.

'int'/'integer'

The getter returns an integer Number value as parsed from the attribute string. The setter converts its received argument to a Number, and then rounds it.

'bool'/'boolean'

The getter returns false if the attribute string is the empty string, or true if it's non-empty. The setter converts its received value to a Boolean using the standard Javascript truthiness rules.

Enums

Enums are special types that limit their possible values to a particular set of Strings. To declare an attribute as an enum type, set its type as an array of strings:

WebComponentAttributesMixin(MyWebComponent, {
    classicalElement: {
        type: ["fire", "earth", "water", "air"],
    },
});

An enum getter returns the default value if the attribute string doesn't match one of the elements of its array exactly. An enum setter will throw an error if it receives a value that doesn't match.

'tokenlist'

Attributes of the tokenlist type are different from all other types, because instead of getting and setting the property directly, it exposes a DOMTokenList which you can use to access the attribute as a set of tokens. This works the same as the .classList property. Also unlike other attributes, tokenlist attributes do not allow specifying a default value. If the backing HTML attribute does not exist on the element, the tokenlist will act as if the attribute's value were the empty string containing no tokens.

WebComponentAttributesMixin(MyWebComponent, {
    myTokenlist: {
        type: 'tokenlist',
    },
});

// ...

const myComponent = new MyWebComponent();
myComponent.myTokenlist.add("red", "green", "blue");
// prints "red green blue"
console.log(myComponent.getAttribute('my-tokenlist'));