Orbit: Radial Interfaces Where CSS Does the Geometry
💫 Orbit is a framework designed for building any kind of radial UI.
At a glance
- What is it?
- Orbit is a MIT licensed JavaScript library that arranges HTML elements around a center, with CSS controlling geometry and appearance and a small runtime handling layout and arcs. It suits developers building gauges, radial menus and charts where a normal flexbox or grid layout would fight the shape.
- Who is it for?
- Orbit fits a developer who needs a genuinely circular interface, a radial menu, a gauge or a chart, and who wants the geometry expressed in CSS custom properties rather than in a canvas drawing loop. It is the wrong tool for ordinary interface layout, since a flexbox does that better, and it is the wrong tool if you cannot use modern CSS, because the layout depends on trigonometric functions and on :has() and :nth-child(... of ...) selectors.
- Can I use it commercially?
- Yes. MIT 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 47 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 October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A layout framework for shapes that are not rectangles
Orbit arranges HTML elements around a center, and the stated use is building radial menus, charts, gauges and other circular interfaces. The interesting architectural decision is the split of responsibility. CSS controls geometry and appearance, and a small JavaScript runtime updates the layout and draws arcs when elements, styles or values change. That is unusual for a layout library, and it is what makes the result inspectable with browser dev tools rather than locked inside a canvas or a computed coordinate system. The elements in question are still ordinary HTML, and Orbit works with plain HTML, React, Vue and Svelte, so it fits into an existing application without a framework migration. The cost of that approach is that the layout rules live in a cascade of custom properties, and the documentation has to be precise about which property does what.
Installing it and wiring it into a client entrypoint
The package installs from npm under a scoped name.
npm install @zumer/orbitThen you import the stylesheet and the runtime in your app's client entrypoint.
import '@zumer/orbit/style'
import '@zumer/orbit'The import of the package during server rendering is safe, and the documentation says to include it in the browser entrypoint as well, so that it registers the custom elements and observes the rendered layout. The module exports Orbit, OrbitArc, OrbitProgress and registerOrbit, with registration happening on import and repeated calls to registerOrbit() being safe, which matters for a library whose custom elements are registered as a side effect of loading. For a plain HTML page the readme shows loading the files from a CDN, with the stylesheet and the minified script referenced from unpkg at the versioned path.
The container, the origin and the rings
The layout vocabulary is a small set of class names with physical metaphors. A `.bigbang` container holds a `.gravity-spot`, which defines the center, and rings inside it position the content. The `.orbit` and `.orbit-N` classes are a ring with an automatic index or an explicit level, such as `.orbit-6`, a `.satellite` positions an item on a ring, a `.capsule` wraps content inside a satellite, a `.vector` places a tick or marker on a ring, and a `.side` places content along the straight line between adjacent ring positions. Two custom elements do the drawing, with `<o-arc>` drawing a segment where sibling arcs stack in HTML order, and `<o-progress>` drawing a progress value over a background arc. There is one structural rule that is easy to break, which is that rings go directly inside `.gravity-spot` and not inside other rings.
Building a progress ring in a plain HTML page
The readme's first example is a self-contained HTML file that loads Orbit from the CDN and draws a ring at 72 percent. The markup nests a gauge container inside a gravity spot inside an orbit, with the progress element carrying the value, a shape and the accessibility attributes.
The markup nests a gauge container inside a gravity spot inside an orbit, with the progress element carrying the value, a rounded shape and the accessibility attributes, and the styles that make it work are equally short: the gauge sized, the gravity spot given a force value that sets the ring scale, and the progress colours set as fill and back-fill custom properties.
The styles that make it work are equally short, with the gauge sized, the gravity spot given a force value that sets the ring scale, and the progress colours set as fill and back-fill custom properties. Updating the ring means changing the value attribute.
const progress = document.querySelector('o-progress')
progress.setAttribute('value', '40')
progress.setAttribute('aria-valuenow', '40')The documentation is explicit that the runtime redraws the ring automatically, and that a value of 0 leaves only the background while 100 fills the orbit's range.
Arc values, interactivity and the stroke variant
Arcs and progress elements use value relative to max, which defaults to 100, and they support circular rings. The accessibility and interaction defaults are worth reading carefully. You add interactive when you need the SVG shapes to receive pointer events, because they are decorative by default, and the documentation is explicit that you supply the keyboard behavior and accessible labels for any controls you build. That is the right division, since the library draws the shape and cannot know what a control does. The drawing style also has two modes. Progress rings draw filled bands by default, and for a stroked line you use the stroke variant and set the stroke and back-stroke custom properties instead of the fill colours. The angular geometry has its own controls, with --o-range setting the span and --o-from rotating it, and utility classes such as range-270, from-180 and fit-range for common settings.
What the runtime observes, and when you have to nudge it
Orbit observes added, removed and reordered elements, style and attribute changes, and container resizes, and updates are grouped into the next animation frame. That grouping is what keeps a busy interface from doing work on every mutation. The escape hatch is Orbit.refresh(), which you call when you need an immediate update or after changes Orbit cannot observe, such as editing stylesheet rules through CSSOM or changing an external control used by a CSS selector. It accepts an element, a document or a shadow root, and with no argument it refreshes the document. The CDN script exposes the same API as window.Orbit. For a layout that has to scale with its container's width there is Orbit.resize(), which you call after the container mounts and which returns a function you call when the component unmounts, with a 500 pixel base scale. The shadow DOM case has its own rule, since the stylesheet has to be included inside each shadow root, and a shadow tree containing only CSS elements has to be registered explicitly with Orbit.refresh(shadowRoot).
The CSS-only fallback and its limits
There is a fallback and its limits are stated. Orbit needs CSS trigonometric functions, sin and cos, and without JavaScript a CSS fallback can position simple elements on up to 24 ring levels with up to 60 children per type. The fallback also requires :has() and :nth-child(... of ...) selectors. The JavaScript runtime supports larger layouts, and the two arc elements always need JavaScript. That split is worth understanding before you decide whether to depend on the fallback, because the answer changes with the size of the layout. A small ring of icons can render without a script, which matters for a static page, while a dashboard with dozens of satellites needs the runtime. The debugging story is also CSS-first, since adding dev-orbit to a container shows ring boundaries and invalid nesting, and a theme-cyan class gives the built-in cyan colours, with the two combinable.
The package build, the tests and the surrounding tooling
The package manifest describes a small, focused build. The library is compiled with esbuild through a config file at the root, the sass package is a dev dependency alongside esbuild and playwright, and the published files are only the dist directory and the readme, with the exports map pointing at a main build, a module build and the style entry. The test script compiles first and then runs three separate suites, one for the package, one in a browser and one for shadow DOM, which is a sensible split because the three failure modes are different. There is also a demo script that compiles and then runs a demo script, and a bump pair of scripts that update the version and regenerate the changelog. The examples directory in the repository is a long list of standalone HTML files, from a minimal gauge through a donut, a radial menu, an arrow gauge, a knob, a semicircle, satellites, nested orbits, a compass and a radar, which is the fastest way to see what the library can express.
Editorial conclusion
Orbit fits a developer who needs a genuinely circular interface, a radial menu, a gauge or a chart, and who wants the geometry expressed in CSS custom properties rather than in a canvas drawing loop. It is the wrong tool for ordinary interface layout, since a flexbox does that better, and it is the wrong tool if you cannot use modern CSS, because the layout depends on trigonometric functions and on :has() and :nth-child(... of ...) selectors. Before you build on it, try the plain HTML progress ring from the readme and change the value attribute, which is the fastest way to see whether the geometry behaves the way your design needs.
Frequently asked questions
What does Orbit do and how is it installed?
Orbit arranges HTML elements around a center for radial menus, charts, gauges and other circular interfaces. It installs with npm install @zumer/orbit, and you import the stylesheet and the runtime in your app's client entrypoint, or load both from a CDN on a plain HTML page.
How do the layout classes fit together?
A .bigbang container holds a .gravity-spot, which defines the center. Rings, .orbit or .orbit-N, go directly inside the gravity spot, a .satellite positions an item on a ring and a .capsule wraps its content, while .vector places a tick and .side places content between adjacent ring positions.
Can Orbit work without JavaScript?
Partly. A CSS fallback can position simple elements on up to 24 ring levels with up to 60 children per type, using trigonometric functions plus :has() and :nth-child(... of ...) selectors. The JavaScript runtime supports larger layouts, and the o-arc and o-progress elements always need JavaScript.
When do I need to call Orbit.refresh()?
When you need an immediate update or after a change Orbit cannot observe, such as editing stylesheet rules through CSSOM or changing an external control used by a CSS selector. It accepts an element, a document or a shadow root, and refreshes the document when called with no argument.
How do the arc and progress elements handle accessibility?
They are decorative by default, so you add the interactive attribute when the SVG shapes need to receive pointer events, and you supply the keyboard behavior and accessible labels yourself. A progress element takes value relative to max, which defaults to 100, and the readme example keeps the ARIA values in sync with the value attribute.
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/zumerlab-orbit)Was this useful?