Library / SDK
riot/riot avatar
riot/riot

riot ships one runtime dependency, commits its own build output, and credits a single maintainer

Simple and elegant component-based UI library

14,925 stars953 forksJavaScriptNOASSERTION

At a glance

What is it?
Riot is a component library for the browser that compiles custom tags down to plain JavaScript and updates the DOM directly rather than through a virtual one. It is MIT licensed, at version 10.1.6, with a push on 2026-08-06. Three things in the repository are worth knowing before you adopt it. It has a single runtime dependency, it commits its built bundles to the repository, and its credits section names exactly one person.
Who is it for?
This suits a team that wants the web components model without the browser's version of it, particularly for a page with a lot of static structure and a few live regions, since direct DOM updates are cheaper than a virtual tree in that shape.
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 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

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

From daily snapshots

Star history

  • riot/riot14,925 ★ · +5
Star history
Repository2026-09-222026-11-13
riot/riot14.9k14.9k

5 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The runtime has one dependency, and everything else is a separate project

The dependency list for this library is one entry long, and that is the point of the whole design.

The single runtime dependency is the package that implements the expression binder, the part that evaluates the expressions in your templates against your state and updates the DOM accordingly.

Everything else you might expect to be in the box is in a separate repository. The compiler. The tag parser. Server-side rendering. Hydration for a single-page application. A router. A lazy component loader. A hot-reload plugin. A native custom elements implementation. And a command line tool to compile tags locally.

So the library you install to run Riot in the browser is not the library you install to build a Riot application. The compiler is a build-time concern and it is not a dependency of the runtime, which is why the runtime stays small.

That is a coherent architecture and it is also the thing to understand before you start. You cannot write a component and expect it to work without a compiler somewhere in your pipeline, and the front page lists four bundler integrations (webpack, Rollup, parcel, Browserify) plus the standalone command line tool for doing that.

The consequence is that choosing Riot usually means a build step, unless you use the riot+compiler bundle the package also exports. If your team has no bundler, that is friction you are choosing on purpose, and there is a documented alternative for rendering tags in a server process without one.

The built bundles are committed to the repository and shipped in the package

The top level of this repository contains the library's built output sitting next to its source, and the build configuration treats those files as generated artefacts.

There are four of them at the root: the library and its minified form, and the compiler with the compiler included and its minified form. Alongside them are the type declarations and the source directory.

The Makefile declares riot.js and riot+compiler.js as generated files, and the minifier is invoked with aggressive options including unsafe compression, unsafe symbol renaming and five passes, with comments stripped.

So the repository is both the source of truth and the distribution, which has an obvious benefit: you can read what actually ships by opening a file in the repository. There is a matching benefit on the release page, since the size of what you download is visible in the repository rather than inferred.

The cost is that every source change produces a large diff in the same commit unless the contributor takes the extra step of not regenerating, and reviewing a change means reviewing both.

The package's published file list confirms the arrangement. It includes the module and commonjs directories, the unminified and minified library, the type declarations, and both compiler bundles, and nothing else. No tests and no source directory.

So the consequence is that you can verify what you are installing by reading the repository, and the price is a noisier commit history than a project that builds on publish.

The credits section names exactly one person

There is a section on the front page introducing itself as actively maintained, and it contains a single name.

The table has one row, one link, and one person, who is also the author named in the package metadata and the person who wrote the line inviting contributions further up the page.

That line is worth quoting in spirit: it says the author tries their best working as much as they can and that help is always appreciated. Then it asks contributors who want to help with maintenance to check the open issues first to see whether there is a task they could take.

So the project is candid about its size. One person does the work and asks for help with the backlog rather than claiming a team.

And the contribution instructions end with a step that is literally titled be patient.

So the consequence is that this is a one-person project with a real following, not a stalled one. For a dependency that most applications pull in once and never touch, that is often fine. For one whose compiler you will be configuring, it is a factor in the decision.

Running the tests means running make, and make reads the version out of the package manifest

The package's test script is one line that delegates to a make target, which is unusual and tells you how the project is built.

The makefile is not a convenience wrapper. It is the test and build entry point. The unit test target starts a static file server, waits for it to be listening on a port, then runs the coverage tool around the test runner with a setup file and a glob over the specification files.

The full test target chains three things: a lint target, the unit tests, and a separate typing target. Linting itself runs a formatter check across the whole tree and a linter over the source and test directories.

The browser tests are a separate target. It builds a bundle with the bundler, serves it, and runs the browser driver against a configuration file at the top of the repository. There is also a Sauce Labs test-status badge on the front page.

And the version. The makefile gets it by running node to read the package manifest and print the version field, with a comment noting that the variable is expanded so the build works on branches without a manifest. It then derives the minor version from that with a text substitution.

So the consequence is that make is a hard requirement for testing, the build couples to node being on the path, and the browser suite needs a real served page rather than a headless component test.

The performance claims are absolute and carry no measurement

The performance section of the front page makes several claims and supports none of them with a number.

It claims the smallest possible amount of DOM updates and reflows, absolutely. It claims fast expression bindings instead of the memory performance issues and drawbacks of a virtual DOM. It claims expressions are pre-compiled and cached.

Then the design consequences that make those claims plausible. One-way data flow, with updates and unmounts propagated downwards from parent to children. No magic or smart reactive properties and no hooks.

The absence of hooks is the load-bearing part. Most component libraries give you a reactivity system so that changing a value updates the interface automatically. This one does not: the example in the front page sets up an interval and calls an update method with a new state object every second. Nothing happens because a value changed.

That design is what makes the update count small, because nothing updates unless you say so. It is also the thing you will feel, because you write the update calls.

The update call is one line in the front page example:

javascript
this.update({ time: ++this.state.time })

So the consequence is that you are trading convenience for control, and the front page asks you to accept the trade on the strength of an adjective. There is a size badge on the page, which is at least a number, but no update-count figure and no benchmark.

Judge it by reading the example and asking whether manual updates suit how your team writes code.

Development is done on Unix and the instructions say so

There is a paragraph in the contribution section that tells you the project is developed on one operating system family, and that is worth reading before you plan to contribute.

It says the project is mainly developed on Unix systems, so you will be able to run all the commands needed to build and test using the makefile. Then it says that on a Microsoft machine it could be harder to set up the development environment properly.

The hedge is doing a lot of work in that sentence. Harder rather than impossible, but the author is warning you.

The makefile itself is where the difficulty lives. It chains a static file server and a test runner together in a single recipe, which on a Unix shell is a pipeline with a command substitution. It derives the minor version with a backtick substitution and a text substitution over the result.

None of that is exotic, but none of it runs in the default shell on Windows either, and the front page offers no Windows-specific instructions.

Note that this is about contributing, not using. The library itself runs in browsers and the package declares no operating system restriction, with a runtime requirement of Node 18 or above for the build.

So the consequence is that using it from Windows is unremarkable and contributing to it from Windows is the awkward path. Do not confuse the two when you weigh whether the project is a good fit for your team.

The package publishes four module formats and two entry points

The package manifest is a short read and it settles the module format question completely.

There are two entry points. The main one is the runtime library, and the second is the compiler bundled with it.

Each entry point resolves four ways. Types, pointing at riot.d.ts. An import condition pointing at the module build. A require condition pointing at a commonjs build. And a browser condition pointing at a prebuilt bundle.

So the same package serves a bundler that understands module syntax, a runtime that requires commonjs, a browser that wants a single file with no module system at all, and a type checker.

The legacy fields are set too, so older tooling that reads the older fields still resolves, with the module build as the fallback for module-aware resolvers and the prebuilt bundle as the main entry.

The package is also declared free of side effects, which lets a bundler drop it if nothing is imported from it.

So the consequence is that the module format is not something you have to decide. If you are reaching for a script tag, the browser condition and the CDN listings cover you. If you are in a bundler, the import condition covers you, and the same install serves both.

Editorial conclusion

This suits a team that wants the web components model without the browser's version of it, particularly for a page with a lot of static structure and a few live regions, since direct DOM updates are cheaper than a virtual tree in that shape. The compiler being a separate package is the structural decision to weigh: you get a very small runtime at the cost of a build step, and the front page links a project to load tags directly in a server process if you would rather not compile. Two things to check. Whether your rendering volume justifies giving up a virtual DOM, since the performance claim is asserted rather than measured. And whether a single-maintainer dependency fits your risk profile, because the front page is explicit about who maintains it.

Frequently asked questions

What is riot?

A component library for the browser. It takes custom tags with embedded script blocks, compiles them to plain JavaScript, and updates the DOM directly rather than through a virtual one. Components are defined with a default export holding lifecycle methods, mounted with a mount call and an initial props object, and nested by putting tags inside tags. It is MIT licensed and runs on Node 18 or above for the build.

How do I install riot?

Install the package from the registry, or load a prebuilt bundle from one of three content delivery networks listed on the front page. For development you clone the repository, install dependencies with npm, and build and test through the makefile. The package publishes two entry points, the runtime and a compiler bundled with it, each resolving for module imports, commonjs requires, browsers and type checking.

Does riot use a virtual DOM?

No. The front page states it uses fast expression bindings instead of the memory performance issues and drawbacks of a virtual DOM, and claims the smallest possible amount of DOM updates and reflows. It also has no reactive properties and no hooks: the example sets an interval and calls an update method with new state each second, so nothing re-renders unless you call it.

Who maintains riot?

The credits section of the front page names exactly one person, who is also the author in the package metadata and wrote the invitation to contribute. The same section asks developers interested in helping with maintenance to check the open issues for a task they could take, and the contribution steps end with a note to be patient. The front page also says the project is mainly developed on Unix systems and that setting up a development environment on a Microsoft machine could be harder.

What are riot's runtime dependencies?

One. The only runtime dependency is the package that implements the expression binder. Everything else is a separate project: the compiler, the parser, server-side rendering, hydration, a router, a lazy loader, a hot-reload plugin and a native custom elements implementation. The library also ships a compiler bundle as a second entry point for when you want the build step and the runtime in one install.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. riot/riot 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/riot-riot.svg)](https://hysenlabs.com/projects/riot-riot)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/riot-riot.svg)](https://hysenlabs.com/projects/riot-riot)

Was this useful?