CLI tool
Tencent/omi avatar
Tencent/omi

Tencent/omi: a Web Components framework built on signals and JSX

Web Components Framework - Web组件框架

13,271 stars1,254 forksTypeScriptNOASSERTION

At a glance

What is it?
Omi is a TypeScript framework for building custom elements with signal-driven reactivity, JSX, and constructable stylesheets. It suits teams that want standard Web Components rather than a framework-owned component model, and it asks you to accept a Tencent-maintained codebase whose last push was on 2026-03-27.
Who is it for?
Adopt omi if you need standards-based custom elements with signal reactivity and you are comfortable reading a README that is richer than its prose documentation. Do not adopt it if you need a documented stability policy, because the README does not state one.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What omi solves, and who ends up using it

Most component frameworks ask you to accept their component model. A React component is not a DOM element. A Vue single-file component is not a DOM element either. Omi takes the opposite position: the unit you write is a custom element, registered with the browser through a decorator, and the framework is the layer that makes writing those elements tolerable.

The README frames this as a Web Components framework, and the topics list confirms the intent: custom-elements, shadow-dom, webcomponents. That means the output of an omi build is something a browser already understands. You can drop the result into a page that has never heard of omi, or hand a component to a team using a different stack, and it still works.

The audience is therefore narrower than a general-purpose UI framework. It is for teams that have decided the component boundary should be the platform, not the library. It is also for teams already inside Tencent's ecosystem, since the README points at TDesign Web Components and a set of OMI templates as the surrounding furniture. If you are choosing a framework purely on ecosystem size and hiring pool, omi is not competing on those terms and the README does not pretend otherwise.

Signals, the tag decorator, and constructable stylesheets

The mechanism has three visible parts. First, reactivity comes from a signal primitive. The README's counter example declares const count = signal(0), then reads count.value inside render and mutates it from event handlers. There is no setState call and no component-level state object in that example. The signal is module-scoped, which means the same signal can be shared across components without prop drilling.

Second, registration is a decorator. The class is exported with @tag('counter-demo'), and the README shows three ways to mount it: render(<counter-demo />, document.body), render(<CounterDemo />, document.body) after importing the class, or plain document.body.appendChild(document.createElement('counter-demo')). That third form is the interesting one. It is the escape hatch that proves the framework is not required at consumption time.

Third, styling uses constructable stylesheets. The README says omi harnesses them to manage and share styles, and the counter example sets static css = 'span { color: red; }' directly on the class. A static string on a class is a small thing, but it means styles are declared next to the component rather than in a separate file, and the browser can adopt the same stylesheet object across many instances instead of re-parsing a style tag per element.

The repository layout supports this reading: packages/ holds the core, omi-router, omi-form, omiu, omi-suspense, and a set of starter kits, while examples/ holds runnable samples for form, forward-ref, suspense, and transition. The README notes that the starter kits are not published to npm, so they are reference code rather than dependencies.

Installing omi and running a first component

The README gives two paths. The direct dependency is a single package. Run this in your project directory:

bash
npm i omi

That installs the core framework. The README does not document a peer dependency or a required bundler for this path, so treat it as the low-level option when you already have a build setup.

The faster path is the CLI, which scaffolds a project. The README shows a TypeScript or JavaScript variant:

bash
npx omi-cli init my-app
cd my-app
npm start
npm run build

The README also notes npx omi-cli init-js my-app for a JavaScript project. After npm start, the expectation is a dev server from the generated Vite setup, and npm run build produces the release output.

If you want the fuller stack, the README documents a second scaffold that adds routing, signals, Suspense, and Tailwind:

bash
npx omi-cli init-spa my-app
cd my-app
npm start
npm run build

Once a project exists, the smallest real component follows the README's counter pattern. This is the shape you should expect to write:

tsx
import { render, signal, tag, Component, h } from 'omi'

const count = signal(0)

@tag('counter-demo')
export class CounterDemo extends Component {
  static css = 'span { color: red; }'

  render() {
    return (
      <>
        <button onClick={() => count.value--}>-</button>
        <span>{count.value}</span>
        <button onClick={() => count.value++}>+</button>
      </>
    )
  }
}

Note the h import. The README's own example imports h even though the JSX in the snippet does not reference it directly, which is the usual signal that the JSX transform expects it in scope. If your build complains about an undefined h, that import is the first thing to check. After registering the element, mount it with render(<counter-demo />, document.body), and you should see two buttons and a red number that moves when you click.

Where omi is the wrong tool

The most concrete limitation is documentation depth. The README is a launch page: a feature list, one counter example, install commands, and a long index of packages. It does not document an upgrade path between major versions, and it does not state a support window for older releases. The release history shows v7.6.6 in February 2024, v7.6.18 in September 2024, and v7.7.0 on 2024-09-08, with no later release listed. The repository's last push was on 2026-03-27, which is more than six months before today, so the code has moved since the last tagged release but the README gives no guidance on what that means for consumers.

That combination matters if you are picking a framework for a multi-year project. A framework whose changelog stops at 2024 and whose README does not describe migration will leave you reading commit history to answer basic questions.

There is a second, structural limitation. Web Components are the selling point, and they are also the constraint. Shadow DOM encapsulation means styles outside the component do not reach inside it by default, so a design system that relies on global CSS resets or utility classes applied from the outside will fight the model. The README lists Tailwindcss support, but Tailwind is a utility-class system and utility classes have to be present inside the shadow root to apply. That is a real integration question the README does not answer.

Finally, if your team's value is in a large existing React or Vue codebase, omi is not a migration target. It is a way to build components that outlive the framework, which is a different problem.

How omi differs from Lit

The obvious comparison is Lit, which also compiles to custom elements and also targets the platform. The difference is in the programming model rather than the output.

Lit's reactivity is expressed through reactive properties declared on the class and a render method that returns a tagged template literal. State lives on the element, and updates are scheduled per element. Omi's README instead leads with signals, and its counter example puts the signal at module scope, outside the class. That is a genuinely different default: shared reactive state is the starting point rather than something you add with a store.

Omi also uses JSX, while Lit uses tagged templates. If your team already writes TSX, omi's component code will look familiar and you keep type checking on the JSX tree. If your team prefers keeping templates close to HTML, Lit's approach will read more naturally, and the README gives no indication that omi offers a template-string alternative.

The third difference is the surrounding package set. Omi ships its own router, Suspense implementation, form package, and transition and ripple directives as separate packages in the same repository. Lit leaves routing and forms to the wider ecosystem. That is a trade: omi gives you one place to look for a router, at the cost of that router being maintained by the same people and released on the same schedule as the core.

Maintenance signals, licence, and what upgrading costs

Maintenance status has to be read from two facts. The repository is not archived. The last push was on 2026-03-27. The most recent release listed is v7.7.0 on 2024-09-08. So there is activity in the repository after the last tagged release, but the release channel has been quiet for a long stretch, and the README does not describe what has accumulated since v7.7.0 or how to move onto it.

There is no documented deprecation policy, no stated support window, and no migration guide in the README. Practically, that means an upgrade from one 7.x release to another is a code-review exercise: read the diff, check whether your usage of signal, tag, or the router package touches changed code, and test. The README does not describe a codemod or an automated migration tool.

On licensing, the repository metadata reports NOASSERTION rather than a recognised identifier, and the README does not discuss licensing at all. The repository does contain a LICENSE file at the top level, so the terms exist and are readable, but the automated classification did not match a standard licence. Before you ship omi inside a product, read that file and route it to whoever handles your legal review. This is not a statement about what the licence permits; it is a statement that the machine-readable metadata does not tell you, and the README will not either.

Editorial conclusion

Adopt omi if you need standards-based custom elements with signal reactivity and you are comfortable reading a README that is richer than its prose documentation. Do not adopt it if you need a documented stability policy, because the README does not state one. Before committing, run npx omi-cli init-spa my-app, open the generated project, and confirm that the router, Suspense, and Tailwind integration match how your team actually builds. Then check the LICENSE file at the repository root, since the metadata reports NOASSERTION rather than a recognised identifier.

Frequently asked questions

How do I install omi?

The README gives two routes. Install the core package with npm i omi, or scaffold a project with npx omi-cli init my-app, which the README says creates an Omi + Vite + TS project, and then run npm start to develop and npm run build to release.

Does omi work with TypeScript and JSX?

Yes. The README's counter example is written in TSX, the primary language of the repository is TypeScript, and the scaffold command npx omi-cli init my-app produces a TypeScript project. A JavaScript variant is available through npx omi-cli init-js my-app.

What is the difference between omi and a framework like Lit?

Both produce custom elements, but omi's README leads with signal-driven reactivity where the signal in its example lives at module scope, and it uses JSX rather than tagged template literals. Omi also ships its own router, Suspense, and form packages in the same repository.

Is omi still maintained?

The repository is not archived, and its last push was on 2026-03-27. The most recent release listed is v7.7.0 from 2024-09-08, and the README does not document what changed after that release.

Can I use omi components outside an omi application?

The README shows that a registered element can be mounted with document.body.appendChild(document.createElement('counter-demo')), which does not require the omi render function. That is the path the README offers for using the output as a plain custom element.

What licence does omi use?

The repository metadata reports NOASSERTION rather than a recognised licence identifier, and the README does not discuss licensing. A LICENSE file exists at the repository root and is the document to read.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. Tencent/omi on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tencent-omi.svg)](https://hysenlabs.com/projects/tencent-omi)