Yugen UI

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/uiYugen UI
Installnpx shadcn add buttonnpx @yugen-ui/cli add button
File structurecomponents/ui/button.tsxcomponents/ui/Button/Button.tsx + the @/components/ui barrel
PrimitivesRadix, cmdk, react-day-picker, vaul, sonner, emblaRadix, cmdk, react-day-picker, react-resizable-panels (no vaul / sonner / embla)
Design languageNeutral, minimalFluent 2: type ramp, elevation, motion and interaction states
Variant APIvariant, size: sm | default | lg | iconappearance, size: small | medium | large, shape
Color tokens--primary, --muted… (oklch)Fluent names: --colorBrandBackground, --colorNeutralForeground3… (hex)
Dark mode.dark + dark:Nestable data-theme, no dark:
Iconslucide-reactFluent 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/uiYugen UINotes
AccordionAccordionTriggerAccordionContentAccordionAccordionHeaderAccordionPanelSame Radix props (type, collapsible).
AlertAlertTitleAlertDescriptionMessageBarvariant="destructive" → intent="error"; the title goes in title; also has actions, onDismiss.
AlertDialogAlertDialogContentAlertDialogActionAlertDialogCancelDialogDialogSurface modalType="alert"DialogCloseHide the X button with <DialogTitle action={null}>.
AspectRatioAspectRatioratio, as in shadcn.
AvatarAvatarImageAvatarFallbackAvatarOne component: name (initials are generated), src, presence.
BadgeBadgevariant → appearance + color.
BreadcrumbBreadcrumbLinkBreadcrumbPageBreadcrumbSeparatorBreadcrumbEllipsisBreadcrumbBreadcrumbButtonBreadcrumbButton currentBreadcrumbDividerBreadcrumbOverflowOr pass an items array.
ButtonButtonGroupButtonButtonGroupSee the variant table below.
CardCardTitleCardDescriptionCardActionCardCardHeader<CardHeader header description action image />.
Carousel (embla)CarouselCarouselViewportCarouselCardCarouselNavContainerNo embla dependency.
Chart—Not available yet.
CheckboxRadioGroupRadioGroupItemCheckboxRadioGroupRadioBuilt-in label prop.
Combobox (Popover + Command)ComboboxA complete component: options, multiselect, freeform.
Data Table (TanStack)TableDataTable pattern on the Table page, no TanStack needed.
Date PickerDatePickerDateRangePickerA complete component instead of a recipe.
DialogDialogContentDialogHeaderDialogFooterDialogDialogSurfaceDialogTitleDialogActionsNote: in shadcn, DialogContent is the surface; in Yugen it is the body.
Drawer (vaul)DrawerDrawerSurface position="bottom"No drag to close.
DropdownMenuDropdownMenuContentDropdownMenuItemDropdownMenuLabelDropdownMenuSeparatorDropdownMenuShortcutMenuMenuPopoverMenuItemMenuGroupHeaderMenuDividerMenuItem secondaryContentDropdownMenuCheckboxItem / RadioItem / Sub → MenuItemCheckbox / MenuItemRadio / MenuSub.
FieldFieldLabelFieldDescriptionFieldErrorFieldOne component: label, hint, validationState, validationMessage.
Form (react-hook-form)FieldUse the form library you prefer and pass errors to validationMessage.
Input GroupInput contentBefore / contentAfter
Input OTPInputOTPGroupInputOTPSlotInputOTPmaxLength → length; group slots with groups={[3, 3]}.
ItemListListItemmedia, title, description, actions.
Navigation Menu—Not available yet. Use Nav (vertical) or Menubar.
Native SelectSelectA native <select>.
PaginationPaginationLinkPaginationNextPaginationOne component: pageCount, page / onPageChange.
PopoverPopoverContentPopoverPopoverSurfaceAdds appearance, withArrow.
ProgressProgressBarshadcn takes 0–100; Yugen defaults to max=1, so pass max={100} or a value from 0 to 1.
SelectSelectTriggerSelectValueSelectContentSelectItemDropdownDropdownOptionTrigger, value and content are merged into Dropdown.
SeparatorDividerCan contain text; vertical, appearance.
SheetSheetContent sideDrawerDrawerSurface positionleft / right → start / end (logical).
SidebarNavInlineDrawer
Sonner / ToastToastertoast()useToasttoast("Saved", { description }) → toast({ title, body }).
TableTableHeadTableCaptionTableFooterTableTableHeaderCellFor the caption / footer, use native <caption> and <tfoot>.
ToggleToggleGroupToggleButtonToggleGrouppressed → checked.
TypographyTextTitle1…Caption2

Variants & sizes

Names follow Fluent. The difference most often missed: a Button with no appearance is secondary, not 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 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/uiYugen aliasYugen utility
--background--colorShellBackground / --colorNeutralBackground1bg-shell (page background), bg-background-1 (surfaces)
--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 on the bottom edge)border-stroke-1, or 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… (use sparingly)
shadcn utilityYugen utility
text-xs / text-sm / text-base / text-lgtext-caption1 / text-body1 / text-body2 / text-subtitle1
font-mediumfont-semibold (only normal / semibold exist)
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

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 with data-theme on any element.
  • The brand color is a 16-step ramp (--brand-10 … 160) instead of a single --primary variable: 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>
Notifications
You have 3 unread messages.
The design team meeting has moved to 3:00 PM.
Calendar
Checklist for each file:change imports to @/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.
Do
  • 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
  • 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.