Oruga, and a Vue component library that deliberately ships no CSS
🐛 Oruga is a lightweight Vue.js UI component library without any CSS dependency
At a glance
- What is it?
- Oruga is a Vue 3 component library with no CSS framework dependency and, unusually, no styling of its own: every component is customised through class mapping, and ready-made looks come from separate themes. The API is a createOruga instance rather than a global registration, and the versioning scheme openly says it is still in beta.
- Who is it for?
- Adopt Oruga if you want Vue 3 components that carry behaviour rather than styling, so your design system owns the look and the library only owns the interaction, and you are comfortable with a class mapping configuration file per component. Do not adopt it if you want a drop in library with a grid, utilities and a coherent default theme out of the box, because Oruga provides neither the grid nor the CSS utilities and its own default appearance is unstyled.
- 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 1 day ago.
- What is it written in?
- Mainly Vue, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A component library without any CSS dependency
Oruga is a lightweight UI components library for Vue.js without any dependency, and it offers a set of easily customisable components while not depending on any specific style or CSS framework such as Bootstrap, Bulma or TailwindCSS.
The consequence is stated directly. Oruga does not provide a grid system or CSS utilities, but you can integrate any CSS framework you like. It provides a set of functional components, so you can focus only on the UI and UX aspects of your application and be entirely flexible for future changes without having to touch a line of JavaScript.
That is a coherent position, and it is worth being clear about what it buys. A library that carries its own styles imposes them on you: either you accept the design or you fight the specificity, and the fighting gets worse as the app grows. A library that ships no styles makes you write the CSS, and that is more work up front and more work later if the components change.
The three claimed benefits are CSS framework agnosticism, components with steroids, meaning most of them are not a simple wrapper of native elements but add new and custom features, and lightness, with no other internal dependency and only the components you need imported.
The middle one is the substantive claim. A wrapper around a native button is not worth a dependency, and a component that adds a behaviour the platform does not have is. The autocomplete, the sidebar and the rest are that kind of component.
The licence is MIT, the primary language is Vue, the default branch is develop, and the recent releases are v0.14.4 from 2026-09-21, v0.14.3 from 2026-09-03 and v0.14.2 from 2026-08-31.
Setup: an instance, not a global plugin
The quick start starts with the install.
npm install @oruga-ui/oruga-nextThen the setup, which is a composable rather than a plugin you import for its side effects.
import { createApp } from "vue"
import { createOruga } from "@oruga-ui/oruga-next";
const oruga = createOruga();
createApp(...).use(oruga, {
// here goes the global config
});You create a new Oruga instance with the createOruga composable, and the instance then has to be passed to the app.use() function of your current Vue app. The second argument to app.use is the global configuration, which is where cross component settings live.
That shape is the modern Vue 3 pattern, and it is a small but meaningful difference from the older style where a library installs itself into the global namespace. Here the library is an object you hold, which means you can create more than one, and the configuration is data rather than a module level side effect.
The project also documents the alternative path, which is to import components directly in your single file components, using the Vue local registration guide.
The documentation is at oruga-ui.com, with a components section, a configuration section, and an example section, and there is a note that the source code of the documentation examples lives in the examples directories for each component, where it serves as the demo as well. That is a good arrangement for a component library, because the demo and the example are the same file, so they cannot drift apart.
Tree shaking, and why nothing is registered globally
The registration model is the detail that decides how a component library feels in a real application, and Oruga is deliberate about it.
To take advantage of the bundler's tree shaking optimisations, no components are registered globally by default. If you want to register a component globally, you extend your Oruga instance with the relevant component plugin, which makes that component and its subcomponents, as well as any related programmatic components, globally available.
import { createOruga, Autocomplete, Sidebar } from "@oruga-ui/oruga-next";
const oruga = createOruga();
// register any necessary components globally
oruga.use(Autocomplete);
oruga.use(Sidebar);The reason to spell this out is that a component library which registers everything globally is convenient in a small app and a liability in a large one. Every component ends up in the main bundle whether or not the route that uses it is present, and a library that has grown to thirty components will be several times the size it needs to be.
Here, the default is nothing, and the global registration is opt in per component. The rest of the components are imported locally in the components that need them, which is where the bundler can drop the ones a route never reaches.
There is one wrinkle in the wording, and it is worth noticing. Registering a component globally also makes its subcomponents and related programmatic components available, which means the global surface is larger than the name you registered. A project that registers ten components globally is not registering ten things.
Customisation by class mapping, and themes as a separate answer
Oruga's stated superpower is its configurability and its CSS framework agnostic approach, and the mechanism is specific.
Each component can be individually customised and configured by defining specific classes using a class mapping approach. Therefore Oruga comes without any styling by default. However, there are several official predefined configurations called themes, which you can include and extend to give your application an individual look and feel, and all components come with predefined classes by default.
So the model has two layers. A component renders with the classes it defines out of the box, and the class mapping configuration says which of your own classes replace those. A theme is a prepared set of those mappings, which is how the project offers ready-made looks without shipping CSS.
The project publishes a multiframework example with a fully customised registration form in Tailwind, Bulma, Bootstrap, Material or any other CSS framework, with the source in a demo-multiframework repository, and a separate TailwindCSS demo. That pair of demos is the fastest way to judge the claim, because it shows the same component rendered under four different styling systems.
The documentation points at the configuration section as the thing to read carefully, and the project says so directly, which is a signal that this is where the learning curve is. A library whose customisation is a configuration file is easy to adopt badly, and the documentation knows it.
The themes are also the answer for the user who does not want to style everything themselves, which is the concession the project makes to the fact that not everybody wants to own a design system.
Nuxt support, which is a plugin file rather than a module
Oruga does not provide a Nuxt.js module at the moment, and the workaround is described in full.
You can use the Nuxt.js plugin system by adding a file, for example oruga.js, in the plugins folder.
import Oruga from "@oruga-ui/oruga-next";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Oruga);
});To make this plugin available in your app, you add this file to the plugins array in your nuxt.config.js.
plugins: [{ src: "~plugins/oruga.js" }];And the project links the Nuxt plugin documentation for how the plugin system works.
The absence of a module is a real limitation, and the reason is worth understanding. A Nuxt module does more than register a plugin: it can configure the library through the module options, register components with auto import, and hook into the build. A hand written plugin file gives you the registration and nothing else, so a Nuxt user has to wire up the auto import and the configuration by hand.
The workaround is short, and it is the kind of thing a project does while it decides whether a module is worth maintaining. What it means for an adopter is that the Nuxt integration is a file you own, and if the library's plugin API changes, that file is the thing that breaks.
A versioning scheme that admits it is in beta
Oruga uses Semantic Versioning 2.0.0 for package versions, and then it says something more.
While it is still in beta, versions follow the pattern v0.Y.Z, where Y is the major, meaning breaking changes, and Z is the minor or patch.
That is worth reading carefully, because it inverts the usual meaning of the numbers. In a normal semver package, 0.14.2 means the first component is the major and the second the minor. Here, the second position carries the breaking changes and the third carries the minor or patch, so v0.14.3 to v0.14.4 is a minor or patch change, and a jump in the second position is the breaking one.
The practical implication is that a consumer cannot apply the usual rule that a minor bump is safe within a 0.x package. Here it is the other way round, and the same number that means major in semver means minor in this scheme. The CHANGELOG.md at the top level of the repository, together with the conventional-changelog script in the package scripts, is where the details of each change live.
The release numbers themselves are consistent with the scheme, since the recent versions move in the second position, v0.14.2, v0.14.3 and v0.14.4, all within a month, which under this convention means ordinary feature and fix work rather than breaking changes.
The project is maintained by Walter Tommasi, who is the author in the package manifest, with Marcel Moravek as a maintainer, and the community runs through a Discord server and a Ko-fi page for the author. The contributing guidelines are in the .github directory, and there is a CODE_OF_CONDUCT.md and a CONTRIBUTORS.md.
A monorepo with generated types and three workspaces
The repository is an npm workspace with three members, packages/oruga for the library, packages/examples and packages/docs, and the root package is a private package named root at version 0.14.4 with the MIT licence and the homepage of the documentation site.
The scripts show how the project is built, and the pattern is the npm-run-all style of running several tasks in sequence or in parallel. The dev script runs the library watch and the docs watch in parallel, the build script runs all the build tasks in sequence, and the test script delegates to the library workspace, with a separate coverage script.
The generation scripts are the interesting part. There is a script to generate the Volar globals declaration, one to generate the configuration types, and one to generate the docs. The first two exist because Oruga's configuration is typed, and the configuration type definitions have to stay in step with the components that the configuration applies to. A library with a typed config that is generated rather than hand written does not drift.
The version script runs a sync-version script and then reinstalls, which is how a single version number is propagated across the workspaces, and the changelog script uses conventional-changelog, so release notes come from the commit messages rather than from a template.
The lint setup is a lint script that runs ESLint with --fix and Prettier with --write, and a separate test:lint set that runs the same two tools in check mode, so the CI equivalent runs the checks without rewriting files.
The default branch is develop, the repository has a CHANGELOG.md, and the top level also carries an eslint.config.mjs, a prettier.config.mjs, a tsconfig.json and a .public directory for assets.
Editorial conclusion
Adopt Oruga if you want Vue 3 components that carry behaviour rather than styling, so your design system owns the look and the library only owns the interaction, and you are comfortable with a class mapping configuration file per component. Do not adopt it if you want a drop in library with a grid, utilities and a coherent default theme out of the box, because Oruga provides neither the grid nor the CSS utilities and its own default appearance is unstyled. Before you commit, read the configuration section and pick a theme to start from, check the version scheme, which the project describes as beta and where the major version is carried in the Y position, and verify that the example components in the examples directories match the markup your own components need.
Frequently asked questions
What is Oruga and does it include any CSS?
Oruga is a lightweight Vue 3 component library with no dependency and no CSS framework dependency. It ships without styling by default, does not provide a grid system or CSS utilities, and its components are customised through a class mapping approach.
How do I install and set up Oruga?
Install with `npm install @oruga-ui/oruga-next`, create an instance with the createOruga composable, and pass it to your Vue app's use() function along with the global configuration. Components can also be imported locally in a single file component.
Why are no components registered globally by default?
So the bundler's tree shaking optimisations work, since only the components you import end up in the bundle. If you want a component globally available, you register it explicitly with oruga.use(Autocomplete), which also makes its subcomponents and related programmatic components global.
Does Oruga have a Nuxt module?
Not at the moment. The project documents using the Nuxt plugin system instead, with an oruga.js file in the plugins folder that calls defineNuxtPlugin, and adding that file to the plugins array in nuxt.config.js.
How does Oruga version its releases?
It uses Semantic Versioning 2.0.0, but while it is in beta the versions follow v0.Y.Z, where Y carries the major, meaning breaking changes, and Z carries the minor or patch. The recent releases are v0.14.4, v0.14.3 and v0.14.2 from September and August 2026.
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/oruga-ui-oruga)Was this useful?