Yugen UI

Từ shadcn/ui

Yugen UI dùng cùng mô hình copy-paste và cùng primitive Radix như shadcn/ui; khác biệt nằm ở tên variant theo Fluent, token màu và một số component đổi tên.

Giống & khác

Nếu bạn quen shadcn/ui, phần lớn kiến thức dùng lại được: cùng Radix, cùng cmdk, cùng react-day-picker, cùng Tailwind và cùng triết lý mã nguồn thuộc về bạn.

shadcn/uiYugen UI
Cài đặtnpx shadcn add buttonnpx @yugen-ui/cli add button
Cấu trúc filecomponents/ui/button.tsxcomponents/ui/Button/Button.tsx + barrel @/components/ui
PrimitiveRadix, cmdk, react-day-picker, vaul, sonner, emblaRadix, cmdk, react-day-picker, react-resizable-panels (không vaul / sonner / embla)
Ngôn ngữ thiết kếTrung tính, tối giảnFluent 2: type ramp, elevation, motion, trạng thái theo Fluent
API variantvariant, size: sm | default | lg | iconappearance, size: small | medium | large, shape
Token màu--primary, --muted… (oklch)Tên Fluent: --colorBrandBackground, --colorNeutralForeground3… (hex)
Dark mode.dark + dark:data-theme lồng được, không dùng dark:
Iconlucide-reactIcon Fluent có sẵn. Icon của bạn: @fluentui/react-icons giữ đúng phong cách Fluent; lucide-react vẫn dùng được, vì ô icon chỉnh kích thước mọi SVG bằng CSS.

Bảng tương ứng component

Nhiều component trùng tên (Tabs, Tooltip, Command, ContextMenu, Menubar, HoverCard, ScrollArea, Resizable, Kbd, Empty). Bảng dưới tập trung vào những chỗ khác.

shadcn/uiYugen UIGhi chú
AccordionAccordionTriggerAccordionContentAccordionAccordionHeaderAccordionPanelCùng prop Radix (type, collapsible).
AlertAlertTitleAlertDescriptionMessageBarvariant="destructive" → intent="error"; tiêu đề qua title; có actions, onDismiss.
AlertDialogAlertDialogContentAlertDialogActionAlertDialogCancelDialogDialogSurface modalType="alert"DialogCloseẨn nút X bằng <DialogTitle action={null}>.
AspectRatioAspectRatioratio như shadcn.
AvatarAvatarImageAvatarFallbackAvatarMột component: name (tự sinh chữ viết tắt), src, presence.
BadgeBadgevariant → appearance + color.
BreadcrumbBreadcrumbLinkBreadcrumbPageBreadcrumbSeparatorBreadcrumbEllipsisBreadcrumbBreadcrumbButtonBreadcrumbButton currentBreadcrumbDividerBreadcrumbOverflowHoặc truyền mảng items.
ButtonButtonGroupButtonButtonGroupXem bảng variant bên dưới.
CardCardTitleCardDescriptionCardActionCardCardHeader<CardHeader header description action image />.
Carousel (embla)CarouselCarouselViewportCarouselCardCarouselNavContainerKhông phụ thuộc embla.
Chart—Chưa có.
CheckboxRadioGroupRadioGroupItemCheckboxRadioGroupRadioCó prop label sẵn.
Combobox (Popover + Command)ComboboxComponent hoàn chỉnh: options, multiselect, freeform.
Data Table (TanStack)TableMẫu DataTable ở trang Table, không cần TanStack.
Date PickerDatePickerDateRangePickerComponent hoàn chỉnh thay vì công thức.
DialogDialogContentDialogHeaderDialogFooterDialogDialogSurfaceDialogTitleDialogActionsChú ý: DialogContent của shadcn là bề mặt; của Yugen là phần thân.
Drawer (vaul)DrawerDrawerSurface position="bottom"Không có kéo để đóng.
DropdownMenuDropdownMenuContentDropdownMenuItemDropdownMenuLabelDropdownMenuSeparatorDropdownMenuShortcutMenuMenuPopoverMenuItemMenuGroupHeaderMenuDividerMenuItem secondaryContentDropdownMenuCheckboxItem / RadioItem / Sub → MenuItemCheckbox / MenuItemRadio / MenuSub.
FieldFieldLabelFieldDescriptionFieldErrorFieldMột component: label, hint, validationState, validationMessage.
Form (react-hook-form)FieldDùng thư viện form bạn chọn; nối lỗi vào validationMessage.
Input GroupInput contentBefore / contentAfter
Input OTPInputOTPGroupInputOTPSlotInputOTPmaxLength → length; nhóm bằng groups={[3, 3]}.
ItemListListItemmedia, title, description, actions.
Navigation Menu—Chưa có — dùng Nav (dọc) hoặc Menubar.
Native SelectSelectThẻ <select> gốc.
PaginationPaginationLinkPaginationNextPaginationMột component: pageCount, page / onPageChange.
PopoverPopoverContentPopoverPopoverSurfaceThêm appearance, withArrow.
ProgressProgressBarshadcn nhận 0–100; Yugen mặc định max=1 → truyền max={100} hoặc giá trị 0–1.
SelectSelectTriggerSelectValueSelectContentSelectItemDropdownDropdownOptionTrigger, value và content đã gộp vào Dropdown.
SeparatorDividerCó thể chứa chữ; vertical, appearance.
SheetSheetContent sideDrawerDrawerSurface positionleft / right → start / end (logical).
SidebarNavInlineDrawer
Sonner / ToastToastertoast()useToasttoast("Đã lưu", { description }) → toast({ title, body }).
TableTableHeadTableCaptionTableFooterTableTableHeaderCellCaption / footer dùng thẻ <caption>, <tfoot> gốc.
ToggleToggleGroupToggleButtonToggleGrouppressed → checked.
TypographyTextTitle1…Caption2

Variant & size

Tên theo Fluent. Khác biệt dễ sót nhất: Button không truyền gì là secondary, không phải primary.

variant="default"appearance="primary"
variant="secondary"appearance="secondary"
variant="destructive"appearance="danger"
variant="outline"appearance="outline"
variant="ghost"appearance="subtle"
variant="link"appearance="transparent"
shadcn/uiYugen UI
size="sm" / "default" / "lg"size="small" / "medium" / "large"
size="icon"icon không kèm children → tự thành nút vuông; bắt buộc aria-label
Badge variant="default"appearance="filled" color="brand"
Badge variant="secondary"appearance="tint" color="informative"
Badge variant="destructive"color="danger"
Badge variant="outline"appearance="outline"
Alert variant="destructive"MessageBar intent="error"
// shadcn/ui
<Button>Lưu</Button>
<Button variant="destructive" size="sm">Xoá</Button>
<Button variant="ghost" size="icon"><Trash2 /></Button>

// Yugen UI — LƯU Ý: Button mặc định là secondary, không phải primary
<Button appearance="primary">Lưu</Button>
<Button appearance="danger" size="small">Xoá</Button>
<Button appearance="subtle" icon={<Delete20Regular />} aria-label="Xoá" />

Biến CSS & utility

Đổi theo vai trò, không theo giá trị: tìm token Fluent có cùng nhiệm vụ.

shadcn/uiYugen aliasYugen utility
--background--colorShellBackground / --colorNeutralBackground1bg-shell (nền trang), bg-background-1 (bề mặt)
--foreground--colorNeutralForeground1text-foreground-1
--card, --popover--colorNeutralBackground1bg-background-1
--primary--colorBrandBackgroundbg-brand-background
--primary-foreground--colorNeutralForegroundOnBrandtext-foreground-on-brand
--secondary, --muted--colorNeutralBackground3bg-background-3
--muted-foreground--colorNeutralForeground3text-foreground-3
--accent--colorSubtleBackgroundHoverhover:bg-subtle-hover
--destructive--colorStatusDangerBackground3 / Foreground1bg-danger-background-3, text-danger-foreground-1
--border--colorNeutralStroke2border-stroke-2
--input--colorNeutralStroke1 (+ StrokeAccessible ở đáy)border-stroke-1, hoặc fieldControlClass
--ring--colorStrokeFocus2outline-stroke-focus
--radius--borderRadiusSmall … 4XLarge, Circularrounded-small 2 · medium 4 · large 6 · xlarge 8 · circular
--sidebar--colorShellNavBackgroundbg-shell-nav
--chart-1 … 5Palette sharedbg-teal-bg, text-lavender-fg… (dùng tiết kiệm)
Utility shadcnUtility Yugen
text-xs / text-sm / text-base / text-lgtext-caption1 / text-body1 / text-body2 / text-subtitle1
font-mediumfont-semibold (chỉ có normal / semibold)
rounded-md / rounded-lg / rounded-fullrounded-medium / rounded-xlarge / rounded-circular
shadow-xs / shadow-sm / shadow-md / shadow-lgshadow-2 / shadow-4 / shadow-8 / shadow-16
ml-auto / pl-2 / left-0ms-auto / ps-2 / start-0

Khác biệt về theming

shadcn đổi theme bằng class .dark và bộ biến CSS; Yugen dùng hai attribute lồng được và token đã kiểm tra tương phản.

/* shadcn/ui — globals.css */
:root {
  --primary: oklch(0.205 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --radius: 0.625rem;
}
.dark {
  --primary: oklch(0.922 0 0);
}
/* <html class="dark"> + biến thể dark: trong class */

/* Yugen UI — tokens.css đã có sẵn light, dark và các brand */
/* <html data-theme="dark" data-brand="web"> — không cần dark: */
  • Palette mặc định của Tailwind (bg-zinc-900, text-sm, rounded-lg) đã bị tắt — class shadcn chép sang sẽ không có tác dụng, cần đổi theo bảng trên.
  • Không có dark:: mọi khác biệt sáng / tối nằm trong token. Lồng theme bằng data-theme trên bất kỳ phần tử nào.
  • Màu thương hiệu là một ramp 16 bậc (--brand-10 … 160) thay vì một biến --primary: hover, pressed, tint chọn, focus và link đều lấy từ ramp. Tạo ramp ở Theme Designer.

Ví dụ chuyển đổi

Một Card shadcn và bản Yugen tương ứng.

// shadcn/ui
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { MoreHorizontal } from "lucide-react";

<Card className="w-full max-w-sm">
  <CardHeader>
    <CardTitle>Thông báo</CardTitle>
    <CardDescription>Bạn có 3 tin chưa đọc.</CardDescription>
    <CardAction>
      <Button variant="ghost" size="icon"><MoreHorizontal /></Button>
    </CardAction>
  </CardHeader>
  <CardContent className="text-sm text-muted-foreground">
    Lịch họp nhóm thiết kế đã dời sang 15:00.
  </CardContent>
  <CardFooter className="gap-2">
    <Badge variant="secondary">Lịch</Badge>
    <Button variant="outline" size="sm" className="ml-auto">Xem tất cả</Button>
  </CardFooter>
</Card>
// Yugen UI
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
import { Badge, Button, Card, CardContent, CardFooter, CardHeader } from "@/components/ui";

<Card className="w-full max-w-sm">
  <CardHeader
    header="Thông báo"
    description="Bạn có 3 tin chưa đọc."
    action={<Button appearance="subtle" icon={<MoreHorizontal20Regular />} aria-label="Thêm thao tác" />}
  />
  <CardContent className="text-body1 text-foreground-3">
    Lịch họp nhóm thiết kế đã dời sang 15:00.
  </CardContent>
  <CardFooter className="gap-2">
    <Badge appearance="tint" color="informative">Lịch</Badge>
    <Button appearance="outline" size="small" className="ms-auto">Xem tất cả</Button>
  </CardFooter>
</Card>
Thông báo
Bạn có 3 tin chưa đọc.
Lịch họp nhóm thiết kế đã dời sang 15:00.
Lịch
Checklist khi chuyển một file:đổi import sang @/components/ui; variant → appearance; size sm / lg → small / large; class màu shadcn → utility token; ml / mr / left / right → logical; lucide → icon Fluent cỡ 20.
Nên
  • Thêm appearance="primary" cho nút chính — mặc định là secondary.
  • Đổi class theo vai trò (muted-foreground → foreground-3), không theo độ sáng gần giống.
  • Dùng component hoàn chỉnh (Combobox, DatePicker, Pagination) thay cho công thức ghép tay.
Không nên
  • Không mang palette Tailwind hay dark: sang — chúng không tồn tại.
  • Không nhầm DialogContent của shadcn (bề mặt) với DialogContent của Yugen (thân).
  • Không dùng font-medium; weight chỉ có normal và semibold.