DEV Community

Jason Shimkoski
Jason Shimkoski

Posted on • Edited on

Introducing Custom Elements Runtime

For years, I wished for a magical open-source tool that would make building Custom Elements feel as easy and enjoyable as crafting components in Vue, Svelte, or React.

But that tool never showed up.

We’ve had options like Lit and Skate that work—but let’s be honest: they are dated and clunky.

Furthermore, why must a library require build steps and extensive boilerplate? What frontend developer actually wants to use a class-based syntax, or frigging decorators?

And, don’t even get me started on the pains of wrestling Tailwind CSS or other global CSS into the Shadow DOM—it’s like trying to fold origami with oven mitts.

What I really wanted was something:

  • With strong type auto-inference
  • Functional, not class-based
  • Free of dependencies
  • Stupidly simple to use
  • Tailwind-style utility classes and animations baked-in, at runtime
  • Friendly to SSR, routing, and inter-component communication (e.g., event bus & global store)
  • And, of course—CDN-ready so I could just drop it in without bundlers

So I stopped waiting and I built it. 💥

Meet my Custom Elements Runtime—a lightweight, no-frills tool that blends the best parts of Vue, React, Svelte, Tailwind CSS, and Web Components into one tasty package.

Define a Component

import { component, ref, html, useProps, useEmit } from '@jasonshimmy/custom-elements-runtime';

component('my-counter', () => {
  const props = useProps({ initialCount: 0 })
  const emit = useEmit();

  const count = ref(props.initialCount);

  const handleClick = () => {
    count.value++;
    emit('update:initial-count', { count: count.value });
  };

  return html`
    <button
      type="button"
      class="px-4 py-2 bg-blue-500 text-white rounded"
      @click.prevent="${handleClick}"
    >
      Count: ${count.value}
    </button>
  `;
});
Enter fullscreen mode Exit fullscreen mode

Use it in HTML

<my-counter
  initial-count="5"
></my-counter>

<script>
const counterEl = document.querySelector('my-counter');

counterEl.addEventListener('update:initial-count', (event) => {
  console.log('Count updated to:', event.detail.count);
})
</script>
Enter fullscreen mode Exit fullscreen mode

Why You’ll Love It

  • Blazing Fast: Minimal runtime, instant updates, zero dependencies.
  • JIT CSS: Utility-first styling on demand, directly in your HTML.
  • Built-in Animations: Vue-style Transition and TransitionGroup functions with presets.
  • No Build Required: Just write and run—no bundling overhead.
  • TypeScript First: Strong types, IntelliSense, and safety built in.
  • Functional API: No classes, no boilerplate—just functions.
  • SSR & HMR Ready: Universal rendering + instant hot reloads.
  • Extensible: Directives, routing, event bus, store, and more.
  • Developer Friendly: Clean docs, sharp examples, and it "just works."

It’s fast, flexible, powerful, genuinely fun to develop with, and is truly a one-stop shop with everything you would ever need.

It is The Complete Web Components Framework.

Try it out on Codepen and drop a ⭐ on GitHub.

While you're at it, play a fun little game I developed, Solatro, which is simplified version of the popular card-based game, Balatro (original credit for the Solatro tabletop game goes to TechDweeb). And, yes, I built the entire game with the Custom Elements Runtime.

Have an awesome day.

Top comments (0)