Custom elements built with Lit. One install works in plain HTML, Vue, React, Angular and Svelte — you own every installed file.
Core runtime + theme
npx shadcn@latest add https://uipkge.dev/r/lit/lit-core.json -yEach element adopts one shared Tailwind stylesheet into its shadow root, imported asshadow.css?inline. The Tailwind Vite plugin compiles it, so the project needs Vite (directly or through your framework) and Tailwind v4.
npm install -D vite tailwindcss @tailwindcss/viteimport { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
// The elements import their shadow stylesheet as `shadow.css?inline`;
// the Tailwind plugin compiles it (and your page CSS).
export default defineConfig({ plugins: [tailwindcss()] })The shadcn CLI needs this file before it adds anything. Keep"tsx": true so the element sources are copied as TypeScript unchanged — with falsethe CLI's JavaScript conversion leaves type syntax behind.
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": { "config": "", "css": "src/app.css", "baseColor": "neutral", "cssVariables": true },
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}lit-core installs the shared runtime (shadow stylesheet, theme bridge, positioning, icons, cn()) under src/uip/, plus the design tokens. Every component depends on it, so this also happens on your first component install. Then import the tokens from your app CSS — the elements read them through their shadow roots, and adark class on<html> switches the theme.
npx shadcn@latest add https://uipkge.dev/r/lit/lit-core.json -y@import "./uip/styles/tokens.css";Install it, import its module once to register the element, then use the tag anywhere.
npx shadcn@latest add https://uipkge.dev/r/lit/button.json -y// src/main.ts — importing a module registers its element
import './uip/components/button/button'
// index.html
// <uip-button variant="outline">Hello uipkge</uip-button>The same elements work in every framework. Values use valueplus input/changeevents, so Vue's v-model works on them directly. Elements render in the browser; server rendering is not supported yet.
// Vue — vite.config: treat uip-* as custom elements
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('uip-') } } })
// React 19 — works directly: <uip-button onClick={...} />, events as on<event> props
// Angular — add CUSTOM_ELEMENTS_SCHEMA to the component's schemas
// Svelte 5 — works directly: <uip-select value={v} onchange={...} />