Re: [WICG/webcomponents] [templates] A declarative JavaScript templating API (Issue #1069)

atzufuki left a comment (WICG/webcomponents#1069)

We already have a good imperative API, why not use that but declaratively? Pure JavaScript, like requested.

I wrote this [mixin](https://github.com/atzufuki/html-props/blob/main/src/core/mixin.ts) to do exactly that, to convert existing web components to provide a declarative API with type infering from the imperative API. Then use it by passing an object to constructor with full type-safety.

Let's do the converting by wrapping an existing component with the mixin, comes out with a declarative API. Can be a built-in element or a third party Lit component or what ever, as long as the component is somewhat compliant with the Custom Elements standards, meaning it doesn't mess up with the element registration or something preventing class inheritance, like some frameworks actually might do.

```typescript
import { HTMLPropsMixin } from '@html-props/core';

// Wrap the native HTMLButtonElement
const Button = HTMLPropsMixin(HTMLButtonElement).define('button-with-props', {
  extends: 'button',
});

// Now instead of creating a button imperatively
const button = new HTMLButtonElement();
button.textContent = 'Click me';
button.onclick= () => console.log('clicked');
button.style.backgroundColor = 'purple';
button.style.color = 'white';

// Create it declaratively
new Button({
  textContent: 'Click me',
  onclick: () => console.log('clicked'),
  style: { backgroundColor: 'purple', color: 'white' },
});
```

It also goes reactive while defining new custom props for components. Here's an example of how to define the props with minimal boilerplate while providing support not only for the props API but for attributes, events and signal based properties as well. That's opt-in though, you can totally handle these manually as well. But I thought this simplicity could lure more users to use Web Components, especially from the React world.

```typescript
import { HTMLPropsMixin, prop } from '@html-props/core';
import { Button, Div } from '@html-props/built-ins';

class Counter extends HTMLPropsMixin(HTMLElement, {
  // Simple props (Type inferred from default)
  count: prop(0),
  isActive: prop(false),
  label: prop('Start'),

  // Explicit Types (Unions, Nullables)
  mode: prop<'light' | 'dark'>('light'),
  user: prop<User | null>(null, { type: Object }),

  // Full Configuration
  myProp: prop('val', {
    attribute: true, // Reflect to attribute (kebab-case)
    // or attribute: 'my-attr' for custom name
    event: 'my-prop-change', // Dispatch event on change
  }),

  // Defaults to existing props
  className: 'counter',
  style: { backgroundColor: 'purple', color: 'white' }
}) {
  render() {
    return new Div({
      content: [
        new Div({ textContent: `Count: ${this.count}` }),
        new Button({
          textContent: 'Increment',
          onclick: () => this.count++,
        }),
      ],
    });
  }
}
```

I'm requesting feedback for this idea and for the [library](https://github.com/atzufuki/html-props), since I have just bumped it to beta and started discussing about this idea in general with people.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/1069#issuecomment-3657303066
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/1069/3657303066@github.com>

Received on Monday, 15 December 2025 19:42:27 UTC