Yugen UI

Bắt đầu nhanh

Một lệnh là có ứng dụng chạy được từ template. Dự án có sẵn: chạy npx @yugen-ui/cli@latest init, hoặc tự làm ba bước (cài yugen-ui, thêm một dòng CSS, bọc YugenProvider).

Nhanh nhất: một lệnh

CLI chép template, cài phụ thuộc rồi chạy dev server. Không cần cấu hình gì thêm.

npx create-yugen-ui@latest my-app --template starter
cd my-app
npm run dev

Dự án starter có sẵn: lưu theme người dùng chọn, toast, alias @/, SEO cơ bản và một trang chào để xoá khi bắt đầu. Đổi starter thành saas (ứng dụng SaaS 8 trang), blog (blog Markdown), dashboard, ai-chat, glass (nhật ký du lịch với điều hướng glass), landing (trang giới thiệu sản phẩm), admin (trang quản trị: bảng, form, đăng nhập), next-starter (Next.js App Router), docs (trang tài liệu từ Markdown) hoặc store (cửa hàng: giỏ hàng, thanh toán nhiều bước), hoặc bỏ --template để chọn trong terminal. Các tuỳ chọn khác ở @yugen-ui/cli.

Thêm vào dự án có sẵn

React 19 và Tailwind CSS v4. Không cần alias hay cấu hình bundler.

npx @yugen-ui/cli@latest init

Chọn package khi được hỏi: CLI cài yugen-ui, thêm dòng theme vào file CSS chính (hoặc styles.css nếu không có Tailwind v4) và in đoạn bọc YugenProvider kèm tên file cần sửa. Muốn tự làm thì theo ba bước dưới đây.

  1. Cài gói

    npm install yugen-ui

    Mọi primitive Radix, icon Fluent và helper đều là dependency của gói — không cần cài thêm gì.

  2. Thêm theme vào CSS

    /* src/index.css (Vite) · app/globals.css (Next.js) */
    @import "tailwindcss";
    @import "yugen-ui/theme.css";

    Không dùng Tailwind? Bỏ dòng trên và import bản CSS biên dịch sẵn (preflight + token + mọi utility component cần):

    // src/main.tsx — no Tailwind
    import "yugen-ui/styles.css";
  3. Bọc ứng dụng bằng 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>,
    );

    applyToDocument ghi theme lên <html> để Dialog, Menu, Tooltip (render qua portal) cũng đúng màu; Đặt Toaster một lần trong provider gốc để gọi toast() ở bất kỳ đâu. Xong — thử ngay với component đầu tiên bên dưới.

Cần tuỳ biến sâu?

Chép mã nguồn component vào dự án để sửa trực tiếp, thay vì dùng bản dựng sẵn trong gói npm. Chạy init một lần, sau đó add từng component.

npx @yugen-ui/cli@latest init --mode source
npx @yugen-ui/cli@latest add button dialog data-grid

CLI ghi theme, cn() và YugenProvider, chép kèm component phụ thuộc, cài gói npm còn thiếu và đổi import theo alias của dự án. Chi tiết ở @yugen-ui/cli. Dự án đã dùng shadcn CLI thì xem Cài bằng shadcn CLI.

Component đầu tiên

Field gắn nhãn, gợi ý và aria-* cho Input; toast() hiện phản hồi ngắn sau khi gửi.

Mỗi tháng một thư, huỷ bất cứ lúc nào.
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: "Đã đăng ký", body: "Hẹn gặp bạn ở số bản tin tiếp theo." });
      }}
    >
      <Field label="Email" hint="Mỗi tháng một thư, huỷ bất cứ lúc nào." required>
        <Input type="email" placeholder="ban@vidu.vn" />
      </Field>
      <Button appearance="primary" type="submit" className="self-start">
        Đăng ký
      </Button>
    </form>
  );
}

Đường dẫn import

Mỗi thư mục component cũng là một subpath của gói: yugen-ui/Button, yugen-ui/Chart, yugen-ui/hooks.

// Gốc gói: mọi thứ từ một chỗ. Bundler chỉ giữ phần bạn dùng.
import { Dialog, DialogSurface, DialogTrigger } from "yugen-ui";

// Subpath: chỉ component đó và những gì nó dùng.
import { Button } from "yugen-ui/Button";
import { LineChart, Sparkline } from "yugen-ui/Chart";
import { useDisclosure } from "yugen-ui/hooks";

Với Vite, Rollup, webpack và Next.js, bản build production có cùng dung lượng với cả hai cách. Với esbuild có code splitting (splitting: true), hãy import từ subpath: import từ gốc gói thì esbuild đưa các phần nạp lúc cần vào chunk đầu (xem Hiệu năng). Subpath còn có ích ở nơi không có tree-shaking: test (Vitest, Jest), render phía server bằng Node và dev server. Trong Node, import { Button } from "yugen-ui/Button" lần đầu nạp 14 module thay vì khoảng 500. Hai cách nạp cùng các file nên có thể dùng lẫn.

Subpath là thư mục của component: CardHeader lấy từ yugen-ui/Card, DateRangePicker từ yugen-ui/DatePicker. Ngoại lệ là yugen-ui/AppShell/Sidebar, yugen-ui/YugenProvider/document, yugen-ui/messages-en và yugen-ui/messages-vi. cn và các hàm màu thương hiệu chỉ có ở gốc gói; readAppShellCookie chạy được trong Server Component chỉ khi import từ yugen-ui, còn yugenHtmlAttributes và yugenThemeScript khi import từ yugen-ui hoặc yugen-ui/YugenProvider/document.

TypeScript với moduleResolution: node (node10) không đọc exports nên không thấy kiểu của subpath: import từ yugen-ui, hoặc chuyển sang bundler hay nodenext.

Next.js: để giữ import từ gốc gói mà vẫn chỉ nạp phần trang dùng khi phát triển và ở server, thêm gói vào optimizePackageImports.

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: { optimizePackageImports: ["yugen-ui"] },
};

export default nextConfig;

Nhớ theme & chống nháy (tuỳ chọn)

Template đã làm sẵn phần này. Tự dựng thì cần hai mảnh: state lưu lựa chọn và script nhỏ đặt theme trước khi trang hiện.

// src/App.tsx — nhớ theme người dùng chọn
import * as React from "react";
import { Toaster, YugenProvider, type YugenTheme } from "yugen-ui";

const STORAGE_KEY = "yugen-ui-theme";

// Script trong index.html đã đặt data-theme trước khi React chạy — đọc lại cho khớp.
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 bị chặn */
    }
  }, [theme]);

  return (
    <YugenProvider applyToDocument theme={theme}>
      <Toaster />
      <AppShell onThemeChange={setTheme} />
    </YugenProvider>
  );
}

YugenProvider ghi attribute khi React chạy, lúc trình duyệt đã vẽ index.html, nên thêm script vào <head> để không chớp nền trắng khi người dùng đã chọn theme tối. Với Next.js, xem SSR & Next.js.

<!-- index.html — đặt TRƯỚC script của ứng dụng -->
<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>

Yêu cầu

Công nghệPhiên bảnGhi chú
React19 trở lênReact 18 được hỗ trợ đến 0.14. Dự án còn dùng React 18 thì giữ yugen-ui 0.14 tới khi nâng cấp.
Tailwind CSS4.xKhông bắt buộc nếu dùng yugen-ui/styles.css. Tailwind 3 không được hỗ trợ.
BundlerVite 6, Next.js 14+Vite dùng @tailwindcss/vite; Next.js dùng @tailwindcss/postcss.
TypeScript5.xKhông bắt buộc — gói đã kèm kiểu.
Node.js18+Theo yêu cầu của Vite 6 và Tailwind 4.