Unovis's structure table fills two columns with one link, and the install command ships a placeholder you must edit
Modular data visualization framework for React, Angular, Svelte, Vue, and vanilla TypeScript or JavaScript
At a glance
- What is it?
- A modular data visualization framework that publishes a core TypeScript package plus one binding per frontend framework. The quick start is accurate and short, and the repository index around it is looser than the code it describes.
- Who is it for?
- Reach for this if you want one visualization layer that survives a change of frontend framework, or you want charts that can be produced on a server rather than only in a browser, since the SVG and PNG path exists as a published package. It is a reasonable fit for a design system that already owns its color tokens, because customisation goes through CSS variables rather than a theming API you have to learn.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 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 October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The repository table promises two data columns and fills both with one link
The package index in the README has four columns, and two of them carry no information.
The header reads Package, Description, Version, Downloads. For each of the framework packages, the Version cell and the Downloads cell are the same link, to a package page on npmx.dev. So the table looks like it reports versions and download counts and reports neither, and a reader looking for the current published version of `@unovis/react` gets a link that would have to be followed and read separately.
The rows themselves are the useful part. There is a core TypeScript package, `@unovis/ts`, and then one binding per frontend framework: React, Angular, Svelte, Vue and Solid. Two of the rows are not framework bindings at all. `@unovis/ssr` covers server-side rendering of charts to SVG or PNG in Node, and `@unovis/mcp` is an MCP server that generates charts as SVG. The last row, the website package, carries em dashes in both data columns rather than a link.
So the published surface is nine packages where a reader expecting a chart library would count seven. The server-side rendering package is the one that changes what you can build, since it moves chart generation out of the browser, and the MCP package moves it into a tool an agent can call.
Neither of those two is mentioned anywhere else in the quick start.
The install command contains a placeholder, and Solid is missing from one list
The quick start is four lines long and three of them matter.
npm install -P @unovis/ts @unovis/<react|angular|svelte|vue|solid>That command is a template, not something to paste. The second argument is a choice written with angle brackets and pipes, and it has to be replaced with one of five package names before the command means anything. The core package comes first and is always needed; the framework package is the one you pick. The flag is `-P`, the short form of the save-to-produments flag, which is an unusual spelling to encounter in a quick start.
The list of frameworks is where the counts disagree. This repository's description names React, Angular, Svelte, Vue and vanilla TypeScript or JavaScript, five targets, and does not mention Solid. The README's own opening line names React, Angular, Svelte, Vue, Solid and vanilla, six. The package table has six. The documentation sentence says the docs contain code snippets for React, Angular, Svelte and TypeScript, four. And the quick start page it points to is described as covering Angular, Svelte, Vue, Solid and TypeScript.
So Solid appears in the README and in the install command but not in the project description, and Vue and Solid appear in neither documentation sentence.
Two more workspace packages are missing from the table altogether. The root scripts point at a `packages/dev` directory and filter a package called `@unovis/shared`, and neither appears in the index.
Node 24 is the floor while the package manager floor is two majors behind
The manifest declares a recent Node requirement and a much older package manager requirement, and pins the package manager to something in between.
`engines` asks for Node 24.0.0 or newer and pnpm 8.0.0 or newer. `packageManager` pins pnpm at 10.23.0. The root also carries an `.nvmrc`, which is the file a version manager reads to pick a Node version.
So the effective requirement is Node 24 plus pnpm 10, and the engine floor for pnpm is two majors lower than what the project actually uses. That gap is harmless in practice because `packageManager` is the field a modern toolchain reads first, and it is the field that would cause a mismatch to be reported rather than silently accepted. What it does mean is that the declared support window for the package manager is wider than the tested one.
The Node side is the sharper constraint. A visualization library that renders in a browser is consumed by people whose build tooling may be older than their own machines, and asking for Node 24 at the root propagates into every contributor's environment and into continuous integration, whether or not any part of the build needs a feature that recent.
Two other files sit next to `.nvmrc` and describe the same class of decision. `commitlint.config.ts` with a `.husky/` directory means commit messages are checked by a hook, and `.eslintrc` is the older single-file lint configuration format rather than the flat config that newer versions of the linter expect.
Publishing is per package and one framework takes a different path
Nothing is published from the root, and the seven library packages do not all publish the same way.
The root manifest is marked `private: true`, which is correct for a workspace root, and each publish script changes into its own package directory first. Five of them follow the pattern of building and then running a `publish:dist` step: the core TypeScript package, React, Svelte, Vue and Solid. The Angular one is the exception, building and then running `publish` rather than `publish:dist`.
That single difference means Angular's directory defines its own publish script and the other five do not, and it is not explained. It may be an oversight left over from when the packages were added in a different order, or it may be that the Angular package has a different output layout. Either way, a contributor adding a new framework binding has to work out which of the two patterns to copy, and the only evidence is which existing packages do what.
The MCP package has its own pair of scripts, one plain and one tagged `beta`, which publish under a `beta` tag rather than `latest`. Its build is also the only one with a dependency on another package: `build:mcp` runs the SSR build first and then builds MCP, so the MCP server cannot be built without the server-side rendering package having been built in the same command.
And there is a `publish:all` that chains the individual publish scripts, which means a full publish is a sequence of separate registry operations rather than one atomic step.
Five entries at the top level exist for AI coding assistants
The repository root carries configuration for three separate AI coding tools, two instruction files, and a directory whose name is simply `ai`.
There is a `.claude/` directory, a `.codex/` directory and a `.cursor/` directory, an `AGENTS.md` and a `CLAUDE.md`, and an `ai/` directory alongside them. That is five entries whose purpose is to be read or applied by an automated assistant rather than by a contributor reading documentation, sitting at the same level as `LICENSE` and `README.md`.
The conventional governance files are present too and are conventional in content: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `SECURITY.md`. The contribution rule stated in the README is that pull requests are welcome, and that major changes should open an issue first to discuss what would change.
The remaining root entries are build and release machinery. There is `postinstall.sh` and `update-version.sh`, two shell scripts, the second of which is the version bump for a repository whose version has to stay in step across nine packages. There is `lic-report-config.json`, which is a licence reporting configuration, sitting next to an Apache-2.0 licence and a dependency tree that spans five frontend frameworks. There is `pnpm-workspace.yaml` with `pnpm-lock.yaml`, so the packages directory is a real workspace rather than a directory that happens to hold several projects.
Two image files, `cover.png` and `examples.png`, are the only non-source assets at the top level, and they are there for the README.
The default build produces the library, the MCP server and the website
The top level build is a chain of eight steps, and two of them are not the library.
The order is the core TypeScript package, then MCP, then React, Angular, Svelte, Vue and Solid, and finally the website. So `pnpm build` at the root does not produce a publishable library and stop; it also produces the server-side rendering package as a side effect of the MCP step, produces the MCP server, and builds the documentation site.
That is a defensible choice for a repository where the site is the primary documentation, and it is also why a full build is slower than a contributor working on one binding needs it to be. Every binding has its own `build:*` script, so the narrow path exists; it is only the default that is wide.
The development scripts show the same split. `dev` changes into a `packages/dev` directory and starts it, `website` changes into the website package, and the two gallery scripts filter on a package called `@unovis/shared` rather than changing directory. One of the gallery scripts has a `csp` variant, which is the same gallery started under a Content Security Policy, and that variant is the reason to think the project has a strict-CSP demo mode at all, since nothing else in the documentation mentions one.
Both `packages/dev` and `@unovis/shared` are referenced by these scripts and neither appears in the package index, so the workspace is larger than the table suggests.
The React example memoises its accessors with an empty dependency list
The quick start example is short enough to read in full, and one detail in it is doing quiet work.
import React, { useCallback } from 'react'
import { VisXYContainer, VisLine, VisAxis } from '@unovis/react'
type DataRecord = { x: number; y: number }Both accessors are passed as memoised callbacks. The x accessor is a `useCallback` that returns `d.x` and the y accessor returns `d.y`, and both take an empty array as their dependency list, which means each function is created once and keeps the same identity for the lifetime of the component.
The chart receives the record type as a generic argument on the line component itself, which is why the example is TypeScript rather than JavaScript, and the data array is three points with a local type declared above it. Two axis components sit inside the container, one for the x axis and one for the y, and the container is what receives the data.
The empty dependency array is the part to notice. Accessors are called for many points and are expected to keep a stable identity, so the pattern a React user copies has to be the memoised one, and the documentation does not say why the plain arrow function form would not work. The same example also carries a small typo in the surrounding prose, where the word for building is misspelled, which is a fair signal of how much editing this section has had.
For the other frameworks the README sends you to a quick start page rather than showing a second example here.
Editorial conclusion
Reach for this if you want one visualization layer that survives a change of frontend framework, or you want charts that can be produced on a server rather than only in a browser, since the SVG and PNG path exists as a published package. It is a reasonable fit for a design system that already owns its color tokens, because customisation goes through CSS variables rather than a theming API you have to learn. Four things to check first. Which packages you actually install, because the documented command contains a placeholder you must replace and the framework list in the repository description is one short of the one in the documentation. Whether your Node version qualifies, since the build floor is Node 24 while the declared package manager floor is two majors below the version the project pins. How much of the default build you want, because the top level build chain produces the MCP server and the documentation site as well as the library. And whether the accessors in the React example make sense to you as a pattern, because the example memoises them with an empty dependency array and the reason is not written down anywhere.
Frequently asked questions
What is Unovis?
A modular data visualization framework covering charts, maps and network graphs, licensed under Apache-2.0 and published as a core TypeScript package plus one binding per frontend framework for React, Angular, Svelte, Vue and Solid, with customisation through CSS variables.
How do I install Unovis?
From npm, with npm install -P @unovis/ts followed by the framework package you use. The documented command writes that second argument as a placeholder with angle brackets and pipes, so one of react, angular, svelte, vue or solid has to replace it before the command runs.
Can Unovis render charts on a server?
Yes, through the @unovis/ssr package, which covers server-side rendering of charts to SVG or PNG in Node. There is also @unovis/mcp, an MCP server that generates charts as SVG, whose build depends on the SSR package being built first.
What does building Unovis require?
Node 24.0.0 or newer, and pnpm, which the manifest pins at 10.23.0 while setting an engine floor of 8.0.0. The root also carries an .nvmrc, and the default build chain produces the library packages, the MCP server and the website.
How does the Unovis React example define its data accessors?
Both the x and y accessors are wrapped in useCallback with an empty dependency array, so each function keeps the same identity across renders, and the line component receives the record type as a generic argument. Two axis components sit inside the container, one per axis.
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/f5-unovis)