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!
-
Detect any
<template>
elements that have aname
attribute, and promote them to custom elements:... Hello,world ! -
Define a custom element by passing its name and its content as a string:
-
Import the content for a new custom element from a separate file:
- either though your app's JS:
- or in your HTML, by adding a
src
attribute to a named template element:...
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...
-
...choose not to use a shadow root?
You can prevent Ponys from creating a shadow root on your custom element with the following:
-
...stop the flash of unstyled content (FOUC)?
The easy way to handle this for all your custom elements at once: add the following CSS to your page...
-
...reflect properties as attributes, and vice-versa?
You could use a bunch of getter and setter methods for this...
...but I wouldn't! Consider using data attributes with your element's
dataset
instead: it's what they're there for. -
...trigger behavior when an attribute/property is changed?
Using the data attributes suggested by the example above, you could try something like this:
For more general information and advice on working with web components, you may wish to consult the relevant pages on web.dev from Google, or MDN from Mozilla; both are excellent resources.
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:
- The word "ponies" is what I imagined may come out when small children try to say "components"
- The spelling "Ponys" was chosen as a tribute to my German-speaking friends - plus it was free on npm
- And lastly, but most importantly: as a nod to the ideal of the ponyfill
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