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/ui | Yugen UI | |
|---|---|---|
| Cài đặt | npx shadcn add button | npx @yugen-ui/cli add button |
| Cấu trúc file | components/ui/button.tsx | components/ui/Button/Button.tsx + barrel @/components/ui |
| Primitive | Radix, cmdk, react-day-picker, vaul, sonner, embla | Radix, cmdk, react-day-picker, react-resizable-panels (không vaul / sonner / embla) |
| Ngôn ngữ thiết kế | Trung tính, tối giản | Fluent 2: type ramp, elevation, motion, trạng thái theo Fluent |
| API variant | variant, size: sm | default | lg | icon | appearance, 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: |
| Icon | lucide-react | Icon 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/ui | Yugen UI | Ghi chú |
|---|---|---|
AccordionAccordionTriggerAccordionContent | AccordionAccordionHeaderAccordionPanel | Cùng prop Radix (type, collapsible). |
AlertAlertTitleAlertDescription | MessageBar | variant="destructive" → intent="error"; tiêu đề qua title; có actions, onDismiss. |
AlertDialogAlertDialogContentAlertDialogActionAlertDialogCancel | DialogDialogSurface modalType="alert"DialogClose | Ẩn nút X bằng <DialogTitle action={null}>. |
AspectRatio | AspectRatio | ratio như shadcn. |
AvatarAvatarImageAvatarFallback | Avatar | Một component: name (tự sinh chữ viết tắt), src, presence. |
Badge | Badge | variant → appearance + color. |
BreadcrumbBreadcrumbLinkBreadcrumbPageBreadcrumbSeparatorBreadcrumbEllipsis | BreadcrumbBreadcrumbButtonBreadcrumbButton currentBreadcrumbDividerBreadcrumbOverflow | Hoặc truyền mảng items. |
ButtonButtonGroup | ButtonButtonGroup | Xem bảng variant bên dưới. |
CardCardTitleCardDescriptionCardAction | CardCardHeader | <CardHeader header description action image />. |
Carousel (embla) | CarouselCarouselViewportCarouselCardCarouselNavContainer | Không phụ thuộc embla. |
Chart | — | Chưa có. |
CheckboxRadioGroupRadioGroupItem | CheckboxRadioGroupRadio | Có prop label sẵn. |
Combobox (Popover + Command) | Combobox | Component hoàn chỉnh: options, multiselect, freeform. |
Data Table (TanStack) | Table | Mẫu DataTable ở trang Table, không cần TanStack. |
Date Picker | DatePickerDateRangePicker | Component hoàn chỉnh thay vì công thức. |
DialogDialogContentDialogHeaderDialogFooter | DialogDialogSurfaceDialogTitleDialogActions | Chú ý: 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. |
DropdownMenuDropdownMenuContentDropdownMenuItemDropdownMenuLabelDropdownMenuSeparatorDropdownMenuShortcut | MenuMenuPopoverMenuItemMenuGroupHeaderMenuDividerMenuItem secondaryContent | DropdownMenuCheckboxItem / RadioItem / Sub → MenuItemCheckbox / MenuItemRadio / MenuSub. |
FieldFieldLabelFieldDescriptionFieldError | Field | Một component: label, hint, validationState, validationMessage. |
Form (react-hook-form) | Field | Dùng thư viện form bạn chọn; nối lỗi vào validationMessage. |
Input Group | Input contentBefore / contentAfter | |
Input OTPInputOTPGroupInputOTPSlot | InputOTP | maxLength → length; nhóm bằng groups={[3, 3]}. |
Item | ListListItem | media, title, description, actions. |
Navigation Menu | — | Chưa có — dùng Nav (dọc) hoặc Menubar. |
Native Select | Select | Thẻ <select> gốc. |
PaginationPaginationLinkPaginationNext | Pagination | Một component: pageCount, page / onPageChange. |
PopoverPopoverContent | PopoverPopoverSurface | Thêm appearance, withArrow. |
Progress | ProgressBar | shadcn nhận 0–100; Yugen mặc định max=1 → truyền max={100} hoặc giá trị 0–1. |
SelectSelectTriggerSelectValueSelectContentSelectItem | DropdownDropdownOption | Trigger, value và content đã gộp vào Dropdown. |
Separator | Divider | Có thể chứa chữ; vertical, appearance. |
SheetSheetContent side | DrawerDrawerSurface position | left / right → start / end (logical). |
Sidebar | NavInlineDrawer | |
Sonner / Toast | Toastertoast()useToast | toast("Đã lưu", { description }) → toast({ title, body }). |
TableTableHeadTableCaptionTableFooter | TableTableHeaderCell | Caption / footer dùng thẻ <caption>, <tfoot> gốc. |
ToggleToggleGroup | ToggleButtonToggleGroup | pressed → checked. |
Typography | TextTitle1…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.
| shadcn/ui | Yugen 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/ui | Yugen alias | Yugen utility |
|---|---|---|
--background | --colorShellBackground / --colorNeutralBackground1 | bg-shell (nền trang), bg-background-1 (bề mặt) |
--foreground | --colorNeutralForeground1 | text-foreground-1 |
--card, --popover | --colorNeutralBackground1 | bg-background-1 |
--primary | --colorBrandBackground | bg-brand-background |
--primary-foreground | --colorNeutralForegroundOnBrand | text-foreground-on-brand |
--secondary, --muted | --colorNeutralBackground3 | bg-background-3 |
--muted-foreground | --colorNeutralForeground3 | text-foreground-3 |
--accent | --colorSubtleBackgroundHover | hover:bg-subtle-hover |
--destructive | --colorStatusDangerBackground3 / Foreground1 | bg-danger-background-3, text-danger-foreground-1 |
--border | --colorNeutralStroke2 | border-stroke-2 |
--input | --colorNeutralStroke1 (+ StrokeAccessible ở đáy) | border-stroke-1, hoặc fieldControlClass |
--ring | --colorStrokeFocus2 | outline-stroke-focus |
--radius | --borderRadiusSmall … 4XLarge, Circular | rounded-small 2 · medium 4 · large 6 · xlarge 8 · circular |
--sidebar | --colorShellNavBackground | bg-shell-nav |
--chart-1 … 5 | Palette shared | bg-teal-bg, text-lavender-fg… (dùng tiết kiệm) |
| Utility shadcn | Utility Yugen |
|---|---|
text-xs / text-sm / text-base / text-lg | text-caption1 / text-body1 / text-body2 / text-subtitle1 |
font-medium | font-semibold (chỉ có normal / semibold) |
rounded-md / rounded-lg / rounded-full | rounded-medium / rounded-xlarge / rounded-circular |
shadow-xs / shadow-sm / shadow-md / shadow-lg | shadow-2 / shadow-4 / shadow-8 / shadow-16 |
ml-auto / pl-2 / left-0 | ms-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ằngdata-themetrê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>@/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.- 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 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.