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

html-traits

v0.1.0

Published

Built-in web components for all elements.

Readme

html-traits

Warning: This is alpha software and is highly experimental. Although it is meant to mimic built-in web components the API might change and not all the API may be implemented.

Example are here.

Introduction

Imagine you want to add multiple interactive behaviors to a single HTML element—maybe an auto-expanding textarea that also validates character limits. With traditional web components, you'd end up with nested wrapper elements cluttering your markup, or you'd be limited to Safari's refusal to support built-in web components.

What if you could simply write <textarea traits="elastic character-limit"> and be done?

HTML Traits is a lightweight JavaScript library that makes this possible. It adds multiple behaviors to existing HTML elements through a simple traits attribute, giving you clean, composable functionality without the wrapper element overhead.

While built-in web components offer progressive enhancement through the is attribute, they come with significant limitations: Safari doesn't support them, you can only add one behavior per element, and behaviors are tied to specific element types. Regular web components solve some of these issues but force you to wrap your semantic HTML in unnecessary container elements.

HTML Traits

Enter html-traits. This little library makes it so you can add multiple behaviors to a single element. It makes it so you don't need nesting of elements. You just add an attribute. No need for definitions to be defined with a dash. Some advantages are:

  1. Composition over inheritance.
  2. Multiple traits to one element.
  3. Ordered traits.
  4. Built for progressive enhancement.
  5. No need for extra elements on your page just to get new runtime behavior.
  6. Cleaner HTML.
  7. No need to reimplement native element behavior as you just keep the original element.
  8. Use native HTML for your elements rather than making everything custom and needing to learn custom APIs.
  9. If you would like to use inheritance for helper functions that you use across all your traits it is easy to do so. Although composition over inheritance is desirable sometimes inheritance is the best solution.

A use case of this pattern could be a textarea element. We would like it to automatically expand as you type. The old way, using web components, would be to do this:

<elastic-textarea>
    <textarea></textarea>
</elastic-textarea>

But with HTML Traits:

<textarea traits="elastic-textarea"></textarea>

But now we would also like to make the textarea turn red, if there are more than 250 characters. The old way:

<elastic-textarea>
    <character-limit data-character-limit="250">
        <textarea></textarea>
    </character-limit>
</elastic-textarea>

With HTML Traits:

<textarea traits="elastic-textarea character-limit" maxlength="250">
</textarea>

This makes making MPA-first applications much easier and cleaner with reusable functionality written in a declarative way.

Limitations

HTML Traits is built specifically for progressive enhancement. It probably isn't the right library for you if you want to go all into JS. It also doesn't work with the Shadow DOM. Neither is it a full implementation of web components. Although I'm trying to follow the same API as web components, it will never be a true web component.

A working example

Here's a full implementation of the component character-limit.

class CharacterLimit {
    constructor(el) {
        if (!(el instanceof HTMLTextAreaElement)) {
            console.warn(`Expected an HTMLTextAreaElement, but received: ${el} for 'character-limit'.`);
            return
        }

        this.el = el
        this.maxLength = parseInt(this.el.getAttribute('maxlength'), 10);

        if (isNaN(this.maxLength)) {
            console.warn(`Invalid 'maxlength' attribute on element: ${this.el}. Expected a number.`);
            return
        }

        this.threshold = Math.floor(this.maxLength * 0.75);

        this.el.style.outlineColor = 'auto';
        el.addEventListener('input', this);
    }

    disconnectedCallback() {
        // Here we can clean up any resources or event listeners.
        // In this case we do not need to remove the event listener since it
        // will be automatically garbage collected.
        console.log(`CharacterLimit trait disconnected from element: ${this.el}`);
    }

    handleEvent(event) {
        this[event.type]?.(event);
    }

    input() {
        if (this.el.value.length < this.threshold) {
            this.el.style.outlineColor = 'auto';
            return;
        }

        if (this.el.value.length >= this.threshold) {
            this.el.style.outlineColor = 'orange';
        }

        if (this.el.value.length >= this.maxLength - 1) {
            this.el.style.outlineColor = 'red';
        }
    }

}

// Initialize the trait
window.defineTrait('character-limit', CharacterLimit);

And below is the HTML. Notice how the native attribute maxlength is used? It is nice to only need to learn as little APIs as possible for a code base. Just learn HTML and use what is already native! Also, notice in the code above, I didn't need to implement cutting the text down, it was automatically implemented by the browser with the attribute maxlength!

<head>
    <script type="module" src="./html-traits.js"></script>
    <script type="module" src="./character-limit.js"></script>
</head>

<body>
    <textarea traits="character-limit" maxlength="20"></textarea>
</body>