Zero-runtime UI with the shadcn aesthetic — no React, no Tailwind, no Radix, no hydration. 65 HTML/CSS components and patterns for server-rendered, static, HTMX, Astro, and AI-generated apps. ~20.2 KB gzipped, zero dependencies, zero JS runtime. Complex widgets are clearly marked.
▶ Live demo: shadcss.vercel.app · 📖 Docs: /docs · 🤖 /llms.txt
shadcss brings the shadcn/ui visual language to plain HTML and CSS. Where
shadcn uses React + Radix + Tailwind, shadcss is CSS you paste into any
server-rendered template — built on :has(), the Popover API, native
<dialog>, and design tokens.
No JS framework. Zero dependencies. Zero framework lock-in. The CSS bundle
ships 0 JS; native <dialog>/Popover/toast need a one-line native trigger
(showModal()/showPopover()).
Not affiliated with, endorsed by, or sponsored by shadcn/ui. Inspired by its design language.
Paste this into any HTML page and open it — the button renders in the shadcn style. No build step, no JavaScript.
<link rel="stylesheet" href="https://unpkg.com/@russfranky/shadcss@0.1.12/dist/shadcss.min.css">
<button class="btn">Hello, shadcss</button><!-- CDN — pin a version in production so a future major can't break you -->
<link rel="stylesheet" href="https://unpkg.com/@russfranky/shadcss@0.1.12/dist/shadcss.min.css"># npm
npm install @russfranky/shadcss/* Bare-specifier @import — resolves ONLY through a bundler (Vite, webpack,
Parcel, esbuild, etc.) that honors the package "exports"/"style" field.
Plain CSS has no Node resolution, so this 404s without a bundler. */
@import "@russfranky/shadcss";
/* Plain CSS / no bundler: import the built file by path instead. */
@import "@russfranky/shadcss/dist/shadcss.min.css";npx @russfranky/shadcss-cli add button card dialog # copy components (+ deps) into ./shadcss
npx @russfranky/shadcss-cli diff button # see what changed upstream vs your copy
npx @russfranky/shadcss-cli check ./index.html # lint HTML for a11y/markup foot-guns
npx @russfranky/shadcss-cli list # all 65 componentsadd gives you shadcn-style ownership (the CSS lives in your repo); diff answers shadcn's #1 complaint by showing exactly what changed upstream so fixes don't silently pass you by. Zero dependencies, fetches from the CDN. See packages/cli.
shadcss is zero-runtime CSS by default. For the few interactions the platform can't yet complete (menu keyboard nav, full ARIA tabs), @russfranky/shadcss-js ships tiny, dependency-free, opt-in helpers — ~1 KB each, per-element, no global runtime.
<div class="dropdown-menu" popover id="m" data-sc-menu> … </div>
<div class="tabs" data-sc-tabs> … </div>
<script type="module">import "@russfranky/shadcss-js/menu"; import "@russfranky/shadcss-js/tabs";</script>Without them the components stay accessible native HTML (tabs are a real radiogroup); with them you get the full keyboard/ARIA pattern. See packages/shadcss-js.
No config files, no env vars — shadcss is CSS. Theming is done by overriding
design tokens (CSS custom properties), never by editing components. All
tokens live in
packages/shadcss/src/base/tokens.css
(12 groups: surfaces, brand, status, borders, sidebar, chart, radii,
typography, spacing, elevation, motion, z-index). Colors are full OKLCH
values (0.2.0+), applied directly as var(--token).
:root {
--primary: oklch(0.5 0.15 250); /* your brand color */
--primary-foreground: oklch(1 0 0);
--radius: 0.75rem;
}Dark mode ships built in — declarative or automatic:
<html data-theme="dark"> <!-- force dark -->
<html> <!-- follows prefers-color-scheme -->Per-component CSS files in packages/shadcss/dist/components/ are
standalone — include only what you use to shrink the bundle (see
"Import only what you use" in the framework README).
shadcss drops React, Radix, and Tailwind, which structurally removes most of the things people complain about with shadcn/ui:
| Common shadcn/ui complaint | shadcss answer |
|---|---|
"Your component, your problem" — copy-paste code you must maintain; no npm update |
shadcss is both: copy a single CSS file, or npm install the package and update it like any dependency. SemVer'd. |
Dependency bugs (a cmdk/Radix breaking change breaks your components) |
Zero dependencies. Nothing upstream can break you. |
| Tailwind: "ugly" HTML, utility-class soup, no selector targeting | Semantic classes (<button class="btn">), real CSS, theme via CSS variables. Readable markup, no utility soup. |
| SSR / hydration mismatches | No JavaScript runtime → no hydration, ever. Works in any SSR/SSG/server-rendered/email context. |
| A11y breaks when you customize, with no safety net | Ships an axe-core a11y gate + markup guards you can run against your own build (npm run check, check:a11y). Light and dark themes verified at 0 axe violations. |
| Not responsive by default (dialogs/tables overflow on mobile) | Overlays clamp to the viewport (min(size, 100vw - 2rem)); tables scroll; OTP wraps; the calendar shrinks. No horizontal overflow at 375px+. |
| "Too much freedom" — undisciplined edits create inconsistency | Retheme by overriding design tokens, not by editing components — consistency by construction. |
Honest about the trade-off: like shadcn, the components are intentionally simple, and a class of interactivity (menu keyboard nav, command search, live toggles) still needs a few lines of your JavaScript — documented, never faked. shadcss is the accessible shadcn aesthetic with zero runtime, not a drop-in Radix replacement.
This repository mirrors the shadcn-ui/ui layout:
shadcss-ui/
├── apps/
│ └── www/ ← live component showcase + docs
└── packages/
└── shadcss/ ← the framework (published to npm as `shadcss`)
├── src/ ← source CSS (base + 65 components)
├── dist/ ← built bundles
├── registry.json ← machine-readable component spec
└── AI_GUIDE.md ← patterns for AI code generation
npm install # install workspace deps
npm run build # build the framework → packages/shadcss/dist
npm run dev # rebuild on change (watch)
npm run www # serve the showcase at http://localhost:3333The showcase (
apps/www/index.html) links the builtshadcss.min.css, which the framework build generates and copies in. Runnpm run buildfirst (thewwwdev/build scripts now do this for you) — opening the rawindex.htmlbefore any build produces an unstyled page.
VERCEL_TOKEN=xxx npm run deployBuilds, stages a self-contained copy (CSS + doc-links repointed to GitHub), and ships it to the shadcss Vercel project → shadcss.vercel.app. The token is read from the environment (never committed); override the team/project with VERCEL_SCOPE / VERCEL_PROJECT_ID if needed.
npm install
npm run build # Lightning CSS bundle + minify (fails on invalid CSS)
npm run check # static guards (no browser needed)npm run check runs two no-dependency guards that encode the regressions prior QA passes found, so they can't silently return:
check-consistency.mjs— version sync, component count, gzip badge, "no absolutist 0-JS claim," "no false shadcn$schema" — all asserted against reality.check-markup.mjs— noanchor()without ananchor-nameproducer, nopopoveron.navigation-menu-content, no staticaria-selectedon tabs, no invalidfocus-visible-anchor, balanced markup, valid registry JSON.
Runtime accessibility (headless Chromium + axe-core), one-time browser install:
npx playwright install chromium
npx serve apps/www -l 3333 & # serve the built showcase
npm run check:a11y # axe-core on light AND dark themescheck:a11y scans both themes (with a settle delay so it doesn't sample mid-transition colors) and fails on any serious or critical violation; moderate/minor are reported as warnings. The showcase currently passes at 0 violations of any impact in both light and dark — the WCAG AA work (including a darkened status palette vs shadcn's defaults) is recorded in qa/iteration-3/.
- Framework README —
packages/shadcss/README.md - AI guide —
packages/shadcss/AI_GUIDE.md - Component registry —
packages/shadcss/registry.json
See CONTRIBUTING.md. PRs welcome — each component is one
file in packages/shadcss/src/components/; use the design tokens, not raw
values; run npm run build and npm run check before pushing.
MIT — see LICENSE.md.