Ponys 🦄

Declarative creation of browser-native web components.

Download (source, 2.1 kB) Download (minified, 1.1 kB)

CDN:

npm:

GitHub: https://github.com/jhuddle/ponys

Ponys simplifies the process of creating custom elements.

Making new browser-native web components typically comes with a lot of boilerplate:

when all you really wanted to do was write this once somewhere:

so that you can use this everywhere else:

Wouldn't it be cool if browsers let you do that? 🤔

This is what Ponys allows you to do - in three different ways!

That's correct: you can inline your templates server-side, create them dynamically, or import them as single-file components - each of these snippets results in the same custom element.

But what about interactivity, styling, etc?

Any class you can use with customElements.define(), you can use with Ponys. Just add a <script> tag with a setup attribute inside your template tag/string/file, and export the class that contains your properties and methods as default - no constructor required.

Likewise with <style>! Ponys will put your component's elements behind a shadow root wherever possible, so your CSS is fully encapsulated.

Here's an example of an inline template with a <script setup> tag, which extends the built-in <button> element (not supported in Safari):

Counter button

See the code

Note also that this template uses a special convenience method on the 'host' element of our component: this.$('#count') is equivalent to this.shadowRoot.querySelector('#count'). And what's more, every element defined in the template also has access to this method - as well as $$ for querySelectorAll, plus a host property that points directly at the host element - making it really easy for elements to reference each other within the current component.

With this capability, you can do a lot with relatively little JavaScript. Here's another more complicated example, this time imported as a single-file component:

Modal

See the code

Try adding some of the other custom elements defined above inside the <modal-container> tag - you'll see they work perfectly alongside the other elements rendered in our template's <slot>.

How can I use this with other JS code though?

Just import it! The <script setup> tag in your template is treated as being of type="module", which means you can use any library that's written as an ESM module: of course, you can import modules directly from your site's static assets, but you could also fetch libraries as needed from a CDN such as Skypack or esm.run by jsDelivr.

That's the approach taken in the example below: here, we import an ESM build of PapaParse from a CDN, in order to parse our custom element's text content as a CSV string. The rest of our code builds an HTML table from the resulting data object... click on the headers to sort by column!

CSV table

See the code

Nice, but I'm more familiar with reactive components...

No worries: the progressive-enhancement ecosystem has got you covered. Here's the exact same CSV table, but written with petite-vue; if you know regular Vue, you'll already know how it works. You could do much the same thing with Alpine.js too, for example. Or perhaps my next side-project... 😉

CSV table using petite-vue

See the code

But how do I...

OK, I'm sold. But... why the ponies?

Why not ponies? Ponies are awesome. 🦄

But to answer your question fully, there are a few actual reasons also:

With this project, my aim was to create a tool that would let me write web components the way I wish browsers would let me write web components; as such, the Ponys API is deliberately close to existing web standards, and constitutes my proposal for the future syntax/behavior of declarative web components.

My wish, therefore, is that Ponys will one day act as the ponyfill for the native API it suggests; I hope you find it convenient and joyful to use in the meantime. 🌈

- jhuddle