Riot.js 10: A Component Library Closer to HTML Than to a Framework
Simple and elegant component-based UI library
At a glance
- What is it?
- Riot.js compiles custom tags into JavaScript and updates the DOM directly instead of diffing a virtual tree. It suits small to medium interfaces that must stay readable, and it is a poor fit for teams that want a large framework runtime.
- Who is it for?
- Adopt Riot.js for interfaces where you can read the whole component in one file and you do not want a virtual DOM between you and the browser: the tag format, the one-way updates from parent to child, and the MIT licence make it easy to justify.
- 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 55 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 September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Riot.js solves: components without a framework runtime
Most component libraries ask you to accept a runtime that owns rendering, state propagation and often routing. Riot.js takes the opposite position. Its own description is "Simple and elegant component-based UI library", and the README frames the goal as bringing custom components to modern browsers while offering what the author wished the native web components API provided. The target reader is a developer who already writes HTML and JavaScript and wants components that look like both.
The design bet is that HTML is already a language for describing nested interfaces. A Riot tag is an HTML element with its own markup and a script block, so nesting is expressed by putting one tag inside another rather than by composing render functions. The README's timetable example places three timer tags inside a timetable tag and stops there. If that style of composition matches how you think about a page, the library removes a layer of abstraction rather than adding one.
How Riot.js updates the DOM: expressions, one-way flow, no virtual tree
The README states that Riot.js performs "fast expressions bindings instead of virtual DOM memory performance issues and drawbacks" and aims for "absolutely the smallest possible amount of DOM updates and reflows". Expressions are pre-compiled and cached. In practice this means the compiler turns the markup into code that knows which DOM nodes correspond to which expressions, so an update touches those nodes instead of producing a new tree and comparing it with the old one.
Data flow is deliberately one-directional. The README says updates and unmounts propagate downwards from parent to children, and it explicitly avoids what it calls "magic" or "smart" reactive properties or hooks. A child receives props when it mounts and does not push changes back up by itself. That is a real constraint: shared state between distant components has to be arranged by you, typically through props passed down or through a store you write.
Lifecycle methods are the other half of the mechanism. The README's timer tag uses onBeforeMount to create initial state from props and start a setInterval, and onUnmounted to clear it. The tick method calls this.update with a new state object. That is the entire update model in the example: mutate your state object, call update, and the bound expressions re-render. There is no dependency tracking to reason about and no scheduler to wait on.
Installing Riot.js and mounting a first tag
The README does not give a package install command for consumers; it points to the official website at riot.js.org and lists CDN hosting on unpkg, jsDelivr and cdnjs. The package name on npm is riot, and the package.json declares "engines": { "node": ">=18.0.0" } for the build and test tooling. If you install it as a dependency, the package exposes an ESM entry at esm/riot.js, a CommonJS entry at cjs/riot.cjs, and a browser build at riot.js, plus a combined riot+compiler build for compiling tags in the browser.
A minimal start is to add the package and import it:
npm install riotThen define a tag. The README's timer example is the canonical one, with a markup block, an expression in braces, and a script block exporting a default object:
<timer>
<p>Seconds Elapsed: { state.time }</p>
<script>
export default {
tick() {
this.update({ time: ++this.state.time })
},
onBeforeMount(props) {
this.state = {
time: props.start,
}
this.timer = setInterval(this.tick, 1000)
},
onUnmounted() {
clearInterval(this.timer)
},
}
</script>
</timer>Mounting takes the tag name and its initial props. The README shows exactly this call:
// mount the timer with its initial props
riot.mount('timer', { start: 0 })What you should see is a paragraph reading "Seconds Elapsed: 0" that increments once per second, with the interval cleared when the tag is unmounted. Nesting works by placing tags inside other tags, and the README's timetable example passes different start values as attributes: <timer start="0"></timer>, <timer start="10"></timer>, <timer start="20"></timer>. Attributes on a parent tag become props for the child.
Where Riot.js stops fitting
The one-way data flow is the first wall. Because updates propagate downwards only, a deeply nested child cannot notify an ancestor without a callback passed as a prop or an external store. Libraries with two-way binding hide that plumbing; Riot.js makes you write it. For a form-heavy admin screen with many interdependent fields, that plumbing is the bulk of the work.
The second wall is tooling. The README is explicit that Riot is mainly developed on UNIX systems and that "if you are on a Microsoft machine it could be harder to set up your development environment properly". The repository's build and test flow runs through a Makefile with targets such as make riot, make raw, make lint, make unit-test and make e2e-test, and the unit test target starts a static server on port 3000 before running Mocha. That is fine on macOS or Linux and awkward on Windows without a compatibility layer.
The third wall is ecosystem shape. Riot is modular by design, which the README presents as a benefit: you pick only what you need. The cost is that routing, server-side rendering, hydration, lazy loading and hot reload live in separate packages (@riotjs/route, @riotjs/ssr, @riotjs/hydrate, @riotjs/lazy, @riotjs/hot-reload). Each is a decision and a version to track. If you want one team to own the whole stack, this is the wrong tool.
Riot.js compared with Vue and React
The clearest contrast is with React. React renders a virtual tree and reconciles it; Riot.js compiles expressions against known DOM nodes and updates those nodes directly, which is why the README can claim no virtual DOM overhead. React components are functions returning markup in JavaScript; Riot tags are HTML files with a script block. If your team is comfortable in JSX, React's model is more familiar. If your team writes HTML and wants the markup to stay HTML, Riot's model is closer to the file.
Vue sits between the two. It also uses a template syntax with expressions in braces and a component file format, but it ships a larger first-party surface (router, state management, build tooling) under one project. Riot's answer is to keep the core small and let you choose the surrounding packages. That trade is real: Vue gives you fewer integration decisions, Riot gives you a smaller core to read. Neither is better in the abstract; the deciding question is how much of the stack you want maintained by one team.
A narrower comparison is with native custom elements, since Riot's topics include customelements and the README says the library is designed to offer what the native API did not. Riot tags are not custom elements by default; the @riotjs/custom-elements package is the bridge if you need that. So Riot is not a polyfill for the platform, it is a compiler and a small runtime that you can later expose as custom elements.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-08-06, with release v10.1.6 tagged the same day. Releases v10.1.5 and v10.1.4 landed on 2026-07-23 and 2026-05-09. That is a steady patch cadence on the 10.x line rather than a burst of activity, and the README credits a single maintainer, Gianluca Guarini, alongside contributors and sponsors. A single-maintainer project is not a defect, but it is a fact to weigh when you plan long-term upgrades.
The package.json declares "license": "MIT", and the README carries an MIT License badge. The repository metadata labels the licence NOASSERTION, which usually means the licence file could not be classified automatically; the LICENSE.txt file is present at the top level, so read it rather than relying on the badge. MIT is permissive and imposes no copyleft obligation on your application, but this is a description of the licence text, not legal advice.
Upgrade cost is shaped by the modular layout. The core package depends on @riotjs/dom-bindings, and the compiler, SSR, router and other pieces are separate packages with their own versions. A major bump in the core does not automatically move the compiler or the router with it, so an upgrade plan has to name each package you actually use. The engines field requires Node 18 or newer for the toolchain, which is a hard floor if you build on an older runtime.
Editorial conclusion
Adopt Riot.js for interfaces where you can read the whole component in one file and you do not want a virtual DOM between you and the browser: the tag format, the one-way updates from parent to child, and the MIT licence make it easy to justify. Skip it if you need a large first-party ecosystem with a router, state manager and formatter maintained by the same team, because Riot splits those into separate packages such as @riotjs/route and @riotjs/ssr that you assemble yourself. Before committing, verify two things on your own code: that your build step can run the tag compiler (the README points to @riotjs/cli, webpack, Rollup, parcel and Browserify loaders), and that your target browsers match what the project supports. The last push to the repository was on 2026-08-06, so check that cadence against your own release schedule.
Frequently asked questions
What is Riot.js?
Riot.js is a component-based UI library that lets you define custom tags in HTML with a script block, then mount them with riot.mount. Its README describes it as simple and elegant, with expressions pre-compiled and cached, one-way data flow from parent to children, and no virtual DOM.
How do I install Riot.js?
The package on npm is named riot, so npm install riot adds it as a dependency. The README itself points to the official website at riot.js.org and lists CDN hosting on unpkg, jsDelivr and cdnjs rather than giving a package install command.
Does Riot.js use a virtual DOM?
No. The README states that Riot.js uses fast expression bindings instead of virtual DOM, aiming for the smallest possible amount of DOM updates and reflows, with expressions pre-compiled and cached.
Which build tools work with Riot.js tags?
The README lists @riotjs/cli, a webpack loader, a Rollup plugin, a parcel plugin and riotify for Browserify. It also notes that tags can be written in CoffeeScript, Jade, LiveScript, TypeScript, ES6 or any pre-processor.
What licence does Riot.js use?
The package.json declares the MIT licence and the README shows an MIT License badge, though the repository metadata reports the licence as NOASSERTION. Read LICENSE.txt at the top level of the repository for the actual terms.
Can Riot.js render on the server?
Server-side rendering is not part of the core package. The README lists @riotjs/ssr as a separate module for rendering markup on the server, and @riotjs/hydrate as the hydration strategy for the client.
Official sources
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.
[](https://hysenlabs.com/projects/riot-riot)