Hi, we're YQteam 👋 We just open-sourced yq-sanyi (v0.2.0, our initial release): define a component as a native HTML tag, then drop <yq-counter> into a plain page — zero dependencies, zero build.
The first question people ask us is: there are already so many component solutions — why build your own?
This post is not about the API. It's about the decision process behind the project.
A scenario we kept hitting
The need is simple: a reusable counter, a panel, a todo list — instead of copy-pasting the same HTML plus interaction logic into every page.
But in practice you usually have only two options:
Adopt a framework. For a "lightweight reuse" need, you pay for a whole runtime plus a build toolchain. For existing static pages, multi-page sites, or server-rendered template pages, that feels like moving house just to buy a shelf.
Hand-rolled copy-paste. Great the first time, painful from the second: boilerplate, styles bleeding into each other, event listeners nobody remembers to clean up.
We wanted a third way: as light as copy-paste, with the reuse experience of a framework.
The three paths we walked
Native Web Components: the right direction, but parts, not a product
customElements standardized "UI as an element" — that's genuinely great. But to actually use a component comfortably you still have to solve a long list of problems yourself:
How do attributes and internal state stay in sync?
How should the event system be designed?
Where does reactive state come from?
What about style isolation? If you reach for Shadow DOM, form-element piercing, CSS-variable theming, and injecting external styles all become design problems of their own;
Who cleans up listeners and side effects on unmount?
The standard doesn't build those wheels for you. Using it raw means boilerplate; filling the gaps means writing a framework anyway.
A framework runtime: the cost doesn't match the benefit
Our actual need was "a few reusable interactive widgets", not page-wide state management, routing, and an ecosystem. Bringing in the latter for the former is like opening a supermarket to buy a bottle of water.
This is especially true when the target is plain HTML / multi-page / static pages, or a page embedded inside someone else's system — a framework runtime plus build output can easily weigh more than the components themselves.
A minimal self-built loop: do just one thing — components
So we implemented it ourselves, shrinking the scope to the smallest coherent unit:
Let template, behavior and scoped style be one definition, one native tag, and one shared lifecycle.
The name "yq-sanyi" ("trinity") comes exactly from that idea.
The rules we set for ourselves
Web-standard APIs only. No JSX, no virtual DOM, no compiler, no framework runtime;
Single source of truth. Template, behavior and style live in one unit — easy to read, reuse and audit;
Mutate state, the view updates itself. Tags auto-mount, auto-update and auto-cleanup;
Style isolation without mandatory Shadow DOM. Selector-scoping by default, CSS-variable theming, opt-in Shadow DOM when you need strong encapsulation;
Failure isolation. A broken component renders an error placeholder plus a structured warning while the rest of the page keeps working;
Size is a budget, not a souvenir. The core is ~8.5 kB gzipped, and the repo ships dependency-graph and bundle-size gates plus benchmarks so it can't quietly bloat.
What it looks like now
yq.define('yq-counter', {
template: '<button yq-on:click="inc">count {{ count }}</button>',
style: 'button { font-size: 18px; padding: 8px 18px; }',
script: function () {
return {
state: { count: 0 },
inc: function (state) {
state.count = state.count + 1
}
}
}
})
Define it once. After that, the usage site is a single line:
<yq-counter></yq-counter>
For template syntax, state, lists and scoped styles, the README and the English tutorial cover everything — no need to repeat it here.
We're not telling anyone to abandon their framework
Different constraints call for different choices. If your project is deeply embedded in a framework ecosystem, needs SSR, or depends on a full CLI-driven pipeline — a framework is right for you, and we explicitly list those cases as "deliberately not supported" in our docs.
But if you're on the "I just want to reuse a piece of UI, lightly" side — no build, plain HTML pages, curious about how components actually work under the hood — come say hi at the repo:
Bilingual tutorials and runnable examples live in the repo
v0.2.0 is our initial release and we will keep iterating: fixing known limitations first (event binding inside yq-for rows), evolving the template layer and devtools, and keeping the English/Chinese docs in sync. Stars, issues, and pull requests are all welcome — and if the project saves you time, you can support us on Afdian to help keep it free, open and zero-dependency.
Top comments (0)