Library / SDK
hunvreus/basecoat avatar
hunvreus/basecoat

Basecoat: shadcn/ui Components Without React, Radix or a Framework Runtime

A components library built with Tailwind CSS that works with any web stack.

4,333 stars139 forksMDXMIT

At a glance

What is it?
Basecoat ports the shadcn/ui design system to semantic HTML, Tailwind CSS v4 and a small vanilla JavaScript layer, so the same components can ship from Django, Rails, Astro or a plain static page. The trade-off is that you own the markup and the init calls.
Who is it for?
Adopt Basecoat if you are building server-rendered HTML in a stack that has no React runtime, and you are willing to copy component markup into your own templates and call window.basecoat.initAll() after dynamic DOM changes. Do not adopt it if you want a component you configure through props and never touch again, or if you need a documented migration path between style packs; the README describes the CSS entrypoints but does not document rollback or an upgrade procedure.
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 70 days ago.
What is it written in?
Mainly MDX, 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.

DEEP OPEN-SOURCE ANALYSIS

The gap Basecoat fills: shadcn/ui markup for stacks without React

shadcn/ui became a common reference for component appearance and composition, but its distribution model assumes React and Radix primitives. Basecoat takes the same design vocabulary and reimplements it as semantic HTML with Tailwind CSS classes, plus a small vanilla JavaScript layer for the components that need behavior. The README describes it as "a Tailwind CSS, vanilla HTML/CSS/JavaScript implementation of the shadcn/ui design system" that works "without React, Radix, or framework runtime dependencies."

The audience follows from that. Anyone rendering HTML on the server (Django, Rails, Laravel, Phoenix, Go templates) or generating static pages gets components they can paste into a template instead of a React tree they cannot mount. The repository also ships Nunjucks and Jinja templates, which points at the same audience. If your application already runs React and you are happy with Radix, Basecoat solves a problem you do not have.

How the CSS is split, and why style packs matter more than the component list

The architecture separates structure from appearance. According to the README, src/css/base/base.css holds "shared tokens and semantic utilities", src/css/components/*.css holds "component layout, structure, accessibility selectors, and behavior hooks", and src/css/styles/*.css holds "style-pack visuals such as color, radius, shadow, typography, spacing, variants, and state styles".

That split is the most consequential design decision in the project. A component's DOM and class hooks stay fixed while a style pack changes how it looks, so switching from Vega to Nova is a CSS import change rather than a markup rewrite. The README lists eight standalone style packs: Vega, Nova, Maia, Lyra, Mira, Luma, Sera and Rhea. Vega is the default and is described as backward compatible.

The entrypoints are generated and committed. src/css/basecoat.css is the default Vega bundle, basecoat-base.css is base plus components with no style pack, basecoat-components.css is component imports only, and basecoat-<style>.css and basecoat-<style>.cdn.css cover a single style pack and its CDN wrapper. The README states these are produced by scripts/generate-css-entrypoints.js and the build scripts. Committing generated files is a deliberate choice for import transparency; it also means a diff can contain generated output that nobody hand-wrote.

Install Basecoat with npm and render your first component

The README gives npm as the install path. The package is named basecoat-css, and the package.json points main and style at dist/basecoat.css while exporting per-component JavaScript modules such as ./accordion, ./combobox and ./dropdown-menu.

The default bundle is imported after Tailwind itself, so Tailwind processes both in one pass.

bash
npm install basecoat-css
css
@import "tailwindcss";
@import "basecoat-css";

If you want a different look, the README shows importing a named style bundle instead of the default. The example it gives is Nova, and the same pattern applies to the other packs.

css
@import "tailwindcss";
@import "basecoat-css/nova";

A third path exists for teams that want to supply their own visuals: import the styleless base plus a local style file. This is the route to take if you intend to keep your own design tokens rather than adopt one of the eight packs.

css
@import "tailwindcss";
@import "basecoat-css/base";
@import "./style-acme.css";

With CSS in place, the JavaScript side is explicit. window.basecoat.initAll() initializes every uninitialized registered component, and window.basecoat.init(name) targets one. You should see interactive components start responding after the init call runs; components that need no behavior do nothing. The README also documents window.basecoat.refresh(element), which asks an initialized component to rescan dynamic children when the component supports refresh.

The init lifecycle is the part that will bite you

Basecoat does not observe the DOM for you. Components are initialized by explicit calls, which is fine on a full page load and less fine when a framework or a navigation library swaps content in place.

For that case the README documents force mode: window.basecoat.init(name, { force: true }) and window.basecoat.initAll({ force: true }) destroy and reinitialize matching components. The stated use case is exactly the one that causes trouble, namely a framework or navigation library restoring previously initialized DOM from a cache. Force mode resets transient state such as open menus, focus state and active interactions. If you restore cached DOM without force, you can end up with components that believe they are already initialized while their listeners point at nodes that no longer exist.

The README also says components use internal destroy hooks to clean up listeners when initialized roots are removed from the DOM. That is the correct design, but it puts the burden on you to remove roots properly rather than detach them silently. Teams used to a component that manages its own mounting will find this manual.

Where Basecoat is the wrong choice

The clearest limitation is that Basecoat is not a component framework. There is no props API, no render function, no typed component you import and configure. You get CSS, a JavaScript bundle, and markup you write or copy. If your team expects to upgrade a component by bumping a version and changing two props, this library will feel like a regression.

Second, the JavaScript surface is small on purpose. The README describes it as "small vanilla JavaScript for components that need behavior", and the lifecycle functions cover init, force reinit and refresh. Anything beyond that, such as coordinating two components, is your code.

Third, the project does not document a migration or rollback procedure. The README explains the CSS entrypoints and the style packs, but it does not describe what happens to your markup when a style pack changes or how to revert an upgrade. Treat the generated entrypoints and the dist output as the contract you depend on, and read CHANGELOG.md before moving versions. The last push to the repository was on 2026-07-21, and the latest listed release is 1.0.1 from 2026-06-28.

Basecoat against DaisyUI and plain shadcn/ui

DaisyUI is the closest comparison in the Tailwind ecosystem: a plugin that adds component classes on top of Tailwind. The difference is where the design comes from. DaisyUI defines its own visual language, while Basecoat explicitly implements the shadcn/ui design system and states compatibility with shadcn/ui themes. If you want shadcn's look in a non-React stack, that compatibility is the reason to pick Basecoat; if you want a self-contained Tailwind plugin with its own theme, DaisyUI is the more direct fit.

Against shadcn/ui itself, the difference is the runtime. shadcn/ui assumes React and Radix primitives, and its distribution model is copy-in components built for that runtime. Basecoat removes React, Radix and framework runtime dependencies entirely, which is what makes it usable from Jinja, Nunjucks or plain HTML. The cost is that shadcn/ui's ecosystem of component variants and community additions does not transfer. You get the design system, not the React component library.

Licence and the cost of staying current

Basecoat is MIT licensed, and package.json declares "license": "MIT" with the full text in LICENSE.md. MIT is permissive: you can use it commercially, modify it and redistribute it, provided you keep the copyright notice and licence text. This is not legal advice, and if you redistribute the package or a modified version inside a product, have your own counsel review the notice requirements.

The upgrade cost is unusual because generated files are committed. src/css/basecoat.css and the other entrypoints are produced by scripts/generate-css-entrypoints.js and the build scripts, and package.json ships dist/ and templates/. That means a version bump can change both the source you read and the generated output you import. The README does not describe a rollback path, so pin the version in package.json and check CHANGELOG.md before upgrading. The repository includes AGENTS.md and ROADMAP.md at the top level, which suggests the project documents its own direction, but the README does not state a support window for older releases.

Editorial conclusion

Adopt Basecoat if you are building server-rendered HTML in a stack that has no React runtime, and you are willing to copy component markup into your own templates and call window.basecoat.initAll() after dynamic DOM changes. Do not adopt it if you want a component you configure through props and never touch again, or if you need a documented migration path between style packs; the README describes the CSS entrypoints but does not document rollback or an upgrade procedure. Before committing, check that your build pipeline can import basecoat-css through Tailwind CSS v4 and that your templating layer can render the component markup, since the package ships CSS and JavaScript rather than framework components.

Frequently asked questions

How do I install Basecoat?

Install the package with npm install basecoat-css, then import Tailwind followed by basecoat-css in your CSS. The README also documents importing a named style bundle such as basecoat-css/nova or the styleless base plus your own style file.

Does Basecoat require React or Radix?

No. The README describes Basecoat as a vanilla HTML, CSS and JavaScript implementation of the shadcn/ui design system that works without React, Radix or framework runtime dependencies. Behavior comes from a small vanilla JavaScript layer instead.

What is the difference between the Basecoat style packs?

The README lists eight standalone style packs, Vega, Nova, Maia, Lyra, Mira, Luma, Sera and Rhea, and says they hold the visuals such as color, radius, shadow, typography, spacing, variants and state styles. Component structure lives separately in src/css/components, so a pack changes appearance rather than markup. Vega is the default bundle.

How do I reinitialize Basecoat components after the DOM changes?

Call window.basecoat.init(name, { force: true }) or window.basecoat.initAll({ force: true }). The README says force mode destroys and reinitializes matching components and resets transient state such as open menus and focus, and that it is meant for cases where a framework or navigation library restores previously initialized DOM from a cache.

Official sources

  1. hunvreus/basecoat on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
For maintainers

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/hunvreus-basecoat.svg)](https://hysenlabs.com/projects/hunvreus-basecoat)
Community notes

Community notes