Menu

Change language

GitHub repo
5 sections · ~5 min · Web Components (Lit 3)

Setup. Any framework.

Custom elements built with Lit. One install works in plain HTML, Vue, React, Angular and Svelte — you own every installed file.

Core runtime + theme

terminal
npx shadcn@latest add https://uipkge.dev/r/lit/lit-core.json -y
01

Vite + Tailwind v4

Each 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.

terminal
npm install -D vite tailwindcss @tailwindcss/vite
vite.config.ts
import { 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()] })
02

components.json

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.

components.json
{
  "$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"
  }
}
03

Core runtime and theme tokens

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.

terminal
npx shadcn@latest add https://uipkge.dev/r/lit/lit-core.json -y
src/app.css
@import "./uip/styles/tokens.css";
04

Add a component

Install it, import its module once to register the element, then use the tag anywhere.

terminal
npx shadcn@latest add https://uipkge.dev/r/lit/button.json -y
src/main.ts
// src/main.ts — importing a module registers its element
import './uip/components/button/button'

// index.html
//   <uip-button variant="outline">Hello uipkge</uip-button>
05

Using them from a framework

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.

framework notes
// 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={...} />