Quick start
One command gives you a running app from a template. For an existing project run npx @yugen-ui/cli@latest init, or do the three steps yourself (install yugen-ui, add one CSS line, wrap in YugenProvider).
Fastest: one command
The CLI copies a template, installs dependencies and starts the dev server. Nothing else to configure.
npx create-yugen-ui@latest my-app --template starter
cd my-app
npm run devThe starter comes with a remembered theme choice, toast, an @/ alias, SEO basics and a welcome page to delete when you start. Swap starter for saas (8-page SaaS app), blog (Markdown blog), dashboard, ai-chat, glass (travel journal with glass navigation), landing (product landing page), admin (back office: tables, forms, sign-in), next-starter (Next.js App Router), docs (Markdown documentation site) or store (shop: cart, multi-step checkout), or drop --template to pick in the terminal. More options on @yugen-ui/cli.
Add to an existing project
React 19 and Tailwind CSS v4. No aliases or bundler config.
npx @yugen-ui/cli@latest initPick package when asked: the CLI installs yugen-ui, adds the theme line to your main CSS file (or styles.css without Tailwind v4) and prints the YugenProvider wrapper with the file to put it in. Prefer doing it by hand? Follow the three steps below.
Install the package
npm install yugen-uiRadix primitives, Fluent icons and helpers are all dependencies of the package — nothing else to install.
Add the theme to your CSS
/* src/index.css (Vite) · app/globals.css (Next.js) */ @import "tailwindcss"; @import "yugen-ui/theme.css";Not using Tailwind? Skip that and import the precompiled stylesheet (preflight + tokens + every utility the components use):
// src/main.tsx — no Tailwind import "yugen-ui/styles.css";Wrap your app in YugenProvider
// src/main.tsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { Toaster, YugenProvider } from "yugen-ui"; import App from "./App"; import "./index.css"; createRoot(document.getElementById("root")!).render( <StrictMode> <YugenProvider applyToDocument> <Toaster /> <App /> </YugenProvider> </StrictMode>, );applyToDocumentwrites the theme to<html>so portalled Dialog, Menu and Tooltip match; PutToasteronce inside the root provider to calltoast()anywhere. Done — try your first component below.
Need deeper customization?
Copy component source into your project and edit it directly, instead of using the prebuilt npm package. Run init once, then add each component.
npx @yugen-ui/cli@latest init --mode sourcenpx @yugen-ui/cli@latest add button dialog data-gridThe CLI writes the theme, cn() and YugenProvider, copies the components each one depends on, installs missing npm packages and rewrites imports to your project's alias. Details on @yugen-ui/cli. Already using the shadcn CLI? See Install with the shadcn CLI.
Your first component
Field wires the label, hint and aria-* to the Input; toast() shows brief feedback after submitting.
import { Button, Field, Input, toast } from "yugen-ui";
export function NewsletterForm() {
return (
<form
className="flex w-full max-w-sm flex-col gap-3"
onSubmit={(e) => {
e.preventDefault();
toast({ intent: "success", title: "Subscribed", body: "See you in the next issue." });
}}
>
<Field label="Email" hint="One email a month, unsubscribe anytime." required>
<Input type="email" placeholder="you@example.com" />
</Field>
<Button appearance="primary" type="submit" className="self-start">
Subscribe
</Button>
</form>
);
}Import paths
Each component folder is also a subpath of the package: yugen-ui/Button, yugen-ui/Chart, yugen-ui/hooks.
// The root: everything from one place. Bundlers keep only what you use.
import { Dialog, DialogSurface, DialogTrigger } from "yugen-ui";
// A subpath: only that component and what it uses.
import { Button } from "yugen-ui/Button";
import { LineChart, Sparkline } from "yugen-ui/Chart";
import { useDisclosure } from "yugen-ui/hooks";With Vite, Rollup, webpack and Next.js a production build is the same size either way. With esbuild code splitting (splitting: true), import from subpaths: from the root, esbuild puts the parts that load on demand into the first chunk (see Performance). Subpaths also help where nothing tree-shakes the package: tests (Vitest, Jest), server rendering in Node and the dev server. A cold import { Button } from "yugen-ui/Button" in Node loads 14 modules instead of about 500. Both paths load the same files, so you can mix them.
The subpath is the folder of the component: CardHeader comes from yugen-ui/Card, DateRangePicker from yugen-ui/DatePicker. The exceptions are yugen-ui/AppShell/Sidebar, yugen-ui/YugenProvider/document, yugen-ui/messages-en and yugen-ui/messages-vi. cn and the brand color helpers are only on the root. readAppShellCookie runs in a Server Component only when imported from yugen-ui; yugenHtmlAttributes and yugenThemeScript when imported from yugen-ui or yugen-ui/YugenProvider/document.
TypeScript with moduleResolution: node (node10) does not read exports, so it finds no types for subpaths there: import from yugen-ui, or switch to bundler or nodenext.
Next.js: to keep root imports and still load only what a page uses in development and on the server, add the package to optimizePackageImports.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: { optimizePackageImports: ["yugen-ui"] },
};
export default nextConfig;Remember the theme, no flash (optional)
Templates already do this. Doing it yourself takes two pieces: state that stores the choice and a tiny script that sets the theme before the page paints.
// src/App.tsx — remember the user's theme
import * as React from "react";
import { Toaster, YugenProvider, type YugenTheme } from "yugen-ui";
const STORAGE_KEY = "yugen-ui-theme";
// The script in index.html already set data-theme before React ran — read it back to match.
const initialTheme = (): YugenTheme =>
document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
export default function App() {
const [theme, setTheme] = React.useState(initialTheme);
React.useEffect(() => {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch {
/* storage blocked */
}
}, [theme]);
return (
<YugenProvider applyToDocument theme={theme}>
<Toaster />
<AppShell onThemeChange={setTheme} />
</YugenProvider>
);
}YugenProvider writes the attributes when React runs, after the browser has painted index.html, so add a script to <head> to avoid a white flash for users who picked the dark theme. For Next.js, see SSR & Next.js.
<!-- index.html — place BEFORE the app script -->
<script>
(function () {
var t;
try { t = localStorage.getItem("yugen-ui-theme"); } catch (e) {}
if (t !== "light" && t !== "dark") t = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", t);
})();
</script>yugen-ui-theme) and the same set of values, or the theme jumps once React runs.Requirements
| Technology | Version | Notes |
|---|---|---|
| React | 19 or later | React 18 was supported until 0.14. On React 18, stay on yugen-ui 0.14 until you upgrade. |
| Tailwind CSS | 4.x | Optional when using yugen-ui/styles.css. Tailwind 3 is not supported. |
| Bundler | Vite 6, Next.js 14+ | Vite uses @tailwindcss/vite; Next.js uses @tailwindcss/postcss. |
| TypeScript | 5.x | Optional — types ship with the package. |
| Node.js | 18+ | Required by Vite 6 and Tailwind 4. |