From shadcn/ui
Yugen UI uses the same copy-the-source model and the same Radix primitives as shadcn/ui. The differences are the Fluent variant names, the color tokens and a few renamed components.
Similarities & differences
If you know shadcn/ui, most of what you know still applies: the same Radix, cmdk, react-day-picker and Tailwind, and the same idea that the source code is yours.
| shadcn/ui | Yugen UI | |
|---|---|---|
| Install | npx shadcn add button | npx @yugen-ui/cli add button |
| File structure | components/ui/button.tsx | components/ui/Button/Button.tsx + the @/components/ui barrel |
| Primitives | Radix, cmdk, react-day-picker, vaul, sonner, embla | Radix, cmdk, react-day-picker, react-resizable-panels (no vaul / sonner / embla) |
| Design language | Neutral, minimal | Fluent 2: type ramp, elevation, motion and interaction states |
| Variant API | variant, size: sm | default | lg | icon | appearance, size: small | medium | large, shape |
| Color tokens | --primary, --muted… (oklch) | Fluent names: --colorBrandBackground, --colorNeutralForeground3… (hex) |
| Dark mode | .dark + dark: | Nestable data-theme, no dark: |
| Icons | lucide-react | Fluent icons built in. For your own icons @fluentui/react-icons keeps the Fluent look; lucide-react works too, because icon slots size any SVG with CSS. |
Component mapping
Many components have the same name (Tabs, Tooltip, Command, ContextMenu, Menubar, HoverCard, ScrollArea, Resizable, Kbd, Empty). The table below covers the ones that differ.
| shadcn/ui | Yugen UI | Notes |
|---|---|---|
AccordionAccordionTriggerAccordionContent | AccordionAccordionHeaderAccordionPanel | Same Radix props (type, collapsible). |
AlertAlertTitleAlertDescription | MessageBar | variant="destructive" → intent="error"; the title goes in title; also has actions, onDismiss. |
AlertDialogAlertDialogContentAlertDialogActionAlertDialogCancel | DialogDialogSurface modalType="alert"DialogClose | Hide the X button with <DialogTitle action={null}>. |
AspectRatio | AspectRatio | ratio, as in shadcn. |
AvatarAvatarImageAvatarFallback | Avatar | One component: name (initials are generated), src, presence. |
Badge | Badge | variant → appearance + color. |
BreadcrumbBreadcrumbLinkBreadcrumbPageBreadcrumbSeparatorBreadcrumbEllipsis | BreadcrumbBreadcrumbButtonBreadcrumbButton currentBreadcrumbDividerBreadcrumbOverflow | Or pass an items array. |
ButtonButtonGroup | ButtonButtonGroup | See the variant table below. |
CardCardTitleCardDescriptionCardAction | CardCardHeader | <CardHeader header description action image />. |
Carousel (embla) | CarouselCarouselViewportCarouselCardCarouselNavContainer | No embla dependency. |
Chart | — | Not available yet. |
CheckboxRadioGroupRadioGroupItem | CheckboxRadioGroupRadio | Built-in label prop. |
Combobox (Popover + Command) | Combobox | A complete component: options, multiselect, freeform. |
Data Table (TanStack) | Table | DataTable pattern on the Table page, no TanStack needed. |
Date Picker | DatePickerDateRangePicker | A complete component instead of a recipe. |
DialogDialogContentDialogHeaderDialogFooter | DialogDialogSurfaceDialogTitleDialogActions | Note: in shadcn, DialogContent is the surface; in Yugen it is the body. |
Drawer (vaul) | DrawerDrawerSurface position="bottom" | No drag to close. |
DropdownMenuDropdownMenuContentDropdownMenuItemDropdownMenuLabelDropdownMenuSeparatorDropdownMenuShortcut | MenuMenuPopoverMenuItemMenuGroupHeaderMenuDividerMenuItem secondaryContent | DropdownMenuCheckboxItem / RadioItem / Sub → MenuItemCheckbox / MenuItemRadio / MenuSub. |
FieldFieldLabelFieldDescriptionFieldError | Field | One component: label, hint, validationState, validationMessage. |
Form (react-hook-form) | Field | Use the form library you prefer and pass errors to validationMessage. |
Input Group | Input contentBefore / contentAfter | |
Input OTPInputOTPGroupInputOTPSlot | InputOTP | maxLength → length; group slots with groups={[3, 3]}. |
Item | ListListItem | media, title, description, actions. |
Navigation Menu | — | Not available yet. Use Nav (vertical) or Menubar. |
Native Select | Select | A native <select>. |
PaginationPaginationLinkPaginationNext | Pagination | One component: pageCount, page / onPageChange. |
PopoverPopoverContent | PopoverPopoverSurface | Adds appearance, withArrow. |
Progress | ProgressBar | shadcn takes 0–100; Yugen defaults to max=1, so pass max={100} or a value from 0 to 1. |
SelectSelectTriggerSelectValueSelectContentSelectItem | DropdownDropdownOption | Trigger, value and content are merged into Dropdown. |
Separator | Divider | Can contain text; vertical, appearance. |
SheetSheetContent side | DrawerDrawerSurface position | left / right → start / end (logical). |
Sidebar | NavInlineDrawer | |
Sonner / Toast | Toastertoast()useToast | toast("Saved", { description }) → toast({ title, body }). |
TableTableHeadTableCaptionTableFooter | TableTableHeaderCell | For the caption / footer, use native <caption> and <tfoot>. |
ToggleToggleGroup | ToggleButtonToggleGroup | pressed → checked. |
Typography | TextTitle1…Caption2 |
Variants & sizes
Names follow Fluent. The difference most often missed: a Button with no appearance is secondary, not primary.
| shadcn/ui | Yugen UI |
|---|---|
size="sm" / "default" / "lg" | size="small" / "medium" / "large" |
size="icon" | icon without children gives a square button; aria-label is required |
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>Save</Button>
<Button variant="destructive" size="sm">Delete</Button>
<Button variant="ghost" size="icon"><Trash2 /></Button>
// Yugen UI. NOTE: Button defaults to secondary, not primary
<Button appearance="primary">Save</Button>
<Button appearance="danger" size="small">Delete</Button>
<Button appearance="subtle" icon={<Delete20Regular />} aria-label="Delete" />CSS variables & utilities
Map by role, not by value: find the Fluent token that does the same job.
| shadcn/ui | Yugen alias | Yugen utility |
|---|---|---|
--background | --colorShellBackground / --colorNeutralBackground1 | bg-shell (page background), bg-background-1 (surfaces) |
--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 on the bottom edge) | border-stroke-1, or 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… (use sparingly) |
| shadcn utility | Yugen utility |
|---|---|
text-xs / text-sm / text-base / text-lg | text-caption1 / text-body1 / text-body2 / text-subtitle1 |
font-medium | font-semibold (only normal / semibold exist) |
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 |
Theming differences
shadcn switches themes with a .dark class and a set of CSS variables. Yugen uses two nestable attributes and tokens checked for contrast.
/* 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"> + dark: variants in classes */
/* Yugen UI: tokens.css already has light, dark and the brands */
/* <html data-theme="dark" data-brand="web">, no dark: needed */- Tailwind's default scales (
bg-zinc-900,text-sm,rounded-lg) are turned off. Classes copied from shadcn have no effect; replace them using the tables above. - There is no
dark:: every light / dark difference is in the tokens. Nest a theme withdata-themeon any element. - The brand color is a 16-step ramp (
--brand-10 … 160) instead of a single--primaryvariable: hover, pressed, selected tint, focus and link all come from the ramp. Create a ramp in the Theme Designer.
Migration example
A shadcn Card and the matching Yugen version.
// 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>Notifications</CardTitle>
<CardDescription>You have 3 unread messages.</CardDescription>
<CardAction>
<Button variant="ghost" size="icon"><MoreHorizontal /></Button>
</CardAction>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
The design team meeting has moved to 3:00 PM.
</CardContent>
<CardFooter className="gap-2">
<Badge variant="secondary">Calendar</Badge>
<Button variant="outline" size="sm" className="ml-auto">View all</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="Notifications"
description="You have 3 unread messages."
action={<Button appearance="subtle" icon={<MoreHorizontal20Regular />} aria-label="More actions" />}
/>
<CardContent className="text-body1 text-foreground-3">
The design team meeting has moved to 3:00 PM.
</CardContent>
<CardFooter className="gap-2">
<Badge appearance="tint" color="informative">Calendar</Badge>
<Button appearance="outline" size="small" className="ms-auto">View all</Button>
</CardFooter>
</Card>@/components/ui; variant → appearance; size sm / lg → small / large; shadcn color classes → token utilities; ml / mr / left / right → logical; lucide → Fluent icons at size 20.- Add appearance="primary" to the main button; the default is secondary.
- Map classes by role (muted-foreground → foreground-3), not by similar lightness.
- Use the complete components (Combobox, DatePicker, Pagination) instead of hand-assembled recipes.
- Don't bring over the Tailwind palette or dark:; neither exists here.
- Don't confuse shadcn's DialogContent (the surface) with Yugen's DialogContent (the body).
- Don't use font-medium; the only weights are normal and semibold.