{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "user-button",
  "title": "User Button",
  "description": "Renders a user button with dropdown menu showing user info, settings link, plugin-contributed menu items (e.g. theme toggle, account switcher), and sign-in/sign-out actions.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "better-auth",
    "cn",
    "lucide-react"
  ],
  "registryDependencies": [
    "avatar",
    "button",
    "dropdown-menu",
    "https://better-auth-ui.com/r/radix-nova/user-view.json",
    "skeleton"
  ],
  "files": [
    {
      "path": "examples/start-shadcn-example/src/components/auth/user/user-avatar.tsx",
      "content": "\"use client\"\n\nimport type { UsernameAuthClient } from \"@better-auth-ui/core/plugins/username\"\nimport { useAuth, useSession } from \"@better-auth-ui/react\"\nimport type { User } from \"better-auth\"\nimport { User2 } from \"lucide-react\"\nimport type { ReactNode } from \"react\"\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\n\nexport type UserAvatarProps = {\n  className?: string\n  fallback?: ReactNode\n  isPending?: boolean\n  /** @remarks `User` */\n  user?: User & { username?: string | null; displayUsername?: string | null }\n}\n\n/**\n * Display a user's avatar using session information or an explicit user prop.\n *\n * Renders a circular avatar that shows the user's image when available, a fallback node if provided, or the user's first two initials; while the session is loading (or when `isPending` is true) and no `user` prop is supplied, renders a skeleton placeholder.\n *\n * @param className - Additional CSS classes applied to the avatar container\n * @param user - Optional user object to display instead of the session user\n * @param isPending - When true, treat the component as loading and show the skeleton if no `user` is provided\n * @param fallback - Node to render inside the avatar fallback area before initials or the default icon\n * @returns The avatar element to render (JSX)\n */\nexport function UserAvatar({\n  className,\n  user,\n  isPending,\n  fallback\n}: UserAvatarProps) {\n  const { authClient } = useAuth<UsernameAuthClient>()\n  const { data: session, isPending: sessionPending } = useSession(authClient, {\n    enabled: !user && !isPending\n  })\n\n  if ((isPending || sessionPending) && !user) {\n    return <Skeleton className={cn(\"size-8 rounded-full\", className)} />\n  }\n\n  const resolvedUser = user ?? session?.user\n\n  const initials = (\n    resolvedUser?.username ||\n    resolvedUser?.name ||\n    resolvedUser?.email\n  )\n    ?.slice(0, 2)\n    .toUpperCase()\n\n  return (\n    <Avatar\n      className={cn(\n        \"size-8 bg-muted text-foreground text-sm rounded-full\",\n        className\n      )}\n    >\n      <AvatarImage\n        src={resolvedUser?.image ?? undefined}\n        alt={\n          resolvedUser?.displayUsername ||\n          resolvedUser?.name ||\n          resolvedUser?.email\n        }\n      />\n\n      <AvatarFallback className=\"text-muted-foreground!\">\n        {fallback || initials || <User2 className=\"size-4\" />}\n      </AvatarFallback>\n    </Avatar>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/user/user-avatar.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/user/user-button.tsx",
      "content": "\"use client\"\n\nimport type { MultiSessionAuthClient } from \"@better-auth-ui/core/plugins/multi-session\"\nimport { useAuth, useSession } from \"@better-auth-ui/react\"\nimport { useSetActiveSession } from \"@better-auth-ui/react/plugins/multi-session\"\nimport {\n  ChevronsUpDown,\n  LogIn,\n  LogOut,\n  Settings,\n  UserPlus2\n} from \"lucide-react\"\nimport { isValidElement, type ReactElement, type ReactNode } from \"react\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger\n} from \"@/components/ui/dropdown-menu\"\nimport { cn } from \"cn\"\nimport { UserAvatar } from \"./user-avatar\"\nimport { UserView } from \"./user-view\"\n\n/** Auth states a `UserButton` link can be visible in. */\nexport type UserButtonLinkVisibility =\n  | \"authenticated\"\n  | \"unauthenticated\"\n  | \"always\"\n\n/** A simple link entry rendered as a `DropdownMenuItem` in the `UserButton` menu. */\nexport type UserButtonLink = {\n  /** Visible label. */\n  label: ReactNode\n  /** Destination URL. */\n  href: string\n  /** Optional leading icon. Sized/coloured to match built-in items. */\n  icon?: ReactNode\n  /** Forwarded to the underlying `DropdownMenuItem`. */\n  variant?: \"default\" | \"destructive\"\n  /**\n   * When this link is visible based on auth state.\n   * @default \"always\"\n   */\n  visibility?: UserButtonLinkVisibility\n}\n\nexport type UserButtonProps = {\n  className?: string\n  align?: \"center\" | \"end\" | \"start\" | undefined\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\"\n  sideOffset?: number\n  size?: \"default\" | \"icon\"\n  variant?:\n    | \"default\"\n    | \"destructive\"\n    | \"ghost\"\n    | \"link\"\n    | \"outline\"\n    | \"secondary\"\n  /** Additional menu entries rendered above the built-in items. */\n  links?: (UserButtonLink | ReactElement)[]\n  /** Hide the built-in \"Settings\" link. Useful when replacing it via `links`. */\n  hideSettings?: boolean\n}\n\nfunction renderUserLink(\n  link: UserButtonLink | ReactElement,\n  navigate: (options: { to: string; replace?: boolean }) => void,\n  fallbackKey: string\n): ReactNode {\n  if (isValidElement(link)) return link\n\n  const { label, href, icon, variant } = link\n  return (\n    <DropdownMenuItem\n      key={fallbackKey}\n      variant={variant}\n      onClick={() => navigate({ to: href })}\n    >\n      {icon}\n      {label}\n    </DropdownMenuItem>\n  )\n}\n\n/**\n * Render a user dropdown button that shows user info, settings, theme controls, and authentication actions.\n *\n * Includes user profile, settings link, optional multi-session account switching, theme picker,\n * and sign-in/sign-up/sign-out actions depending on authentication state.\n *\n * @param className - Additional CSS classes applied to the button trigger\n * @param align - Alignment of the dropdown menu relative to the trigger\n * @param side - Preferred side of the trigger on which to show the dropdown menu\n * @param sideOffset - Offset between the trigger and the dropdown menu\n * @param size - \"icon\" renders only the avatar; \"default\" renders a full button with label and chevron\n * @param variant - Visual variant of the trigger button\n * @param links - Additional menu entries rendered above the built-in items\n * @param hideSettings - Hide the built-in \"Settings\" link\n * @returns The dropdown menu component with user actions\n */\nexport function UserButton({\n  className,\n  align,\n  side,\n  sideOffset,\n  size = \"default\",\n  variant = \"ghost\",\n  links,\n  hideSettings = false\n}: UserButtonProps) {\n  const { authClient, basePaths, viewPaths, localization, plugins, navigate } =\n    useAuth<MultiSessionAuthClient>()\n\n  const { isPending: settingActiveSession } = useSetActiveSession(authClient)\n  const { data: session, isPending: sessionPending } = useSession(authClient)\n\n  const userLinks = links?.flatMap((link, index) => {\n    if (!isValidElement(link)) {\n      const visibility = link.visibility ?? \"always\"\n      if (visibility === \"authenticated\" && !session) return []\n      if (visibility === \"unauthenticated\" && session) return []\n    }\n    return [\n      renderUserLink(link, navigate, `user-button-link-${index.toString()}`)\n    ]\n  })\n\n  // Whether anything renders between the user info label and the\n  // sign-out item, so the leading separator isn't shown with nothing\n  // to separate (see #439).\n  const hasSessionMenuItems =\n    (userLinks?.length ?? 0) > 0 ||\n    !hideSettings ||\n    plugins.some((plugin) => (plugin.userMenuItems?.length ?? 0) > 0)\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        aria-label={size === \"icon\" ? localization.auth.account : undefined}\n        className={\n          size === \"icon\"\n            ? cn(\"rounded-full\", className)\n            : cn(\n                buttonVariants({ variant, size: \"lg\" }),\n                \"py-2.5 h-auto font-normal\",\n                className\n              )\n        }\n      >\n        {size === \"icon\" ? (\n          <UserAvatar />\n        ) : (\n          <>\n            {session || sessionPending || settingActiveSession ? (\n              <UserView isPending={!!settingActiveSession} />\n            ) : (\n              <>\n                <UserAvatar />\n\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  {localization.auth.account}\n                </div>\n              </>\n            )}\n\n            <ChevronsUpDown className=\"ml-auto size-4\" />\n          </>\n        )}\n      </DropdownMenuTrigger>\n\n      <DropdownMenuContent\n        className=\"min-w-40 md:min-w-56 max-w-[48svw]\"\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n      >\n        {session && (\n          <>\n            <DropdownMenuGroup>\n              <DropdownMenuLabel className=\"text-sm font-normal\">\n                <UserView />\n              </DropdownMenuLabel>\n            </DropdownMenuGroup>\n\n            {hasSessionMenuItems && <DropdownMenuSeparator />}\n          </>\n        )}\n\n        {session ? (\n          <>\n            {userLinks}\n\n            {!hideSettings && (\n              <DropdownMenuItem\n                onClick={() =>\n                  navigate({\n                    to: `${basePaths.settings}/${viewPaths.settings.account}`\n                  })\n                }\n              >\n                <Settings className=\"text-muted-foreground\" />\n\n                {localization.settings.settings}\n              </DropdownMenuItem>\n            )}\n\n            {plugins.flatMap((plugin) =>\n              plugin.userMenuItems?.map((Item, index) => (\n                <Item key={`${plugin.id}-${index.toString()}`} />\n              ))\n            )}\n\n            <DropdownMenuSeparator />\n\n            <DropdownMenuItem\n              onClick={() =>\n                navigate({\n                  to: `${basePaths.auth}/${viewPaths.auth.signOut}`\n                })\n              }\n            >\n              <LogOut className=\"text-muted-foreground\" />\n\n              {localization.auth.signOut}\n            </DropdownMenuItem>\n          </>\n        ) : (\n          <>\n            {userLinks}\n\n            <DropdownMenuItem\n              onClick={() =>\n                navigate({\n                  to: `${basePaths.auth}/${viewPaths.auth.signIn}`\n                })\n              }\n            >\n              <LogIn className=\"text-muted-foreground\" />\n\n              {localization.auth.signIn}\n            </DropdownMenuItem>\n\n            <DropdownMenuItem\n              onClick={() =>\n                navigate({\n                  to: `${basePaths.auth}/${viewPaths.auth.signUp}`\n                })\n              }\n            >\n              <UserPlus2 className=\"text-muted-foreground\" />\n\n              {localization.auth.signUp}\n            </DropdownMenuItem>\n\n            {plugins.flatMap((plugin) =>\n              plugin.userMenuItems?.map((Item, index) => (\n                <Item key={`${plugin.id}-${index.toString()}`} />\n              ))\n            )}\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/user/user-button.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/user/user-view.tsx",
      "content": "\"use client\"\n\nimport type { UsernameAuthClient } from \"@better-auth-ui/core/plugins/username\"\nimport { useAuth, useSession } from \"@better-auth-ui/react\"\nimport type { User } from \"better-auth\"\n\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { cn } from \"cn\"\nimport { UserAvatar } from \"./user-avatar\"\n\nexport type UserViewProps = {\n  className?: string\n  isPending?: boolean\n  /**\n   * When true, the subtitle line (email when name/username is shown) is hidden.\n   * @default false\n   */\n  hideSubtitle?: boolean\n  /** @remarks `User` */\n  user?: Partial<User> & {\n    username?: string | null\n    displayUsername?: string | null\n  }\n}\n\n/**\n * Render a compact user item with an avatar, a primary label (display username, name, or email), and an optional subtitle (email).\n *\n * @param isPending - If true and no `user` prop is provided, renders a loading skeleton instead of user details\n * @param className - Additional CSS classes applied to the outer container\n * @param hideSubtitle - When true, omits the muted subtitle row under the primary label\n * @param user - Optional user object to display; when omitted the current session user is used\n * @returns A React element showing the user's avatar with their identifying information\n */\nexport function UserView({\n  className,\n  isPending,\n  hideSubtitle = false,\n  user\n}: UserViewProps) {\n  const { authClient } = useAuth<UsernameAuthClient>()\n  const { data: session, isPending: sessionPending } = useSession(authClient, {\n    enabled: !user && !isPending\n  })\n\n  const resolvedUser = user ?? session?.user\n\n  if ((isPending || sessionPending) && !user) {\n    return (\n      <div className={cn(\"flex items-center gap-2 min-w-0\", className)}>\n        <UserAvatar isPending />\n\n        <div className=\"grid flex-1 gap-1 text-left text-sm\">\n          <Skeleton className=\"h-4 w-24\" />\n\n          {!hideSubtitle && <Skeleton className=\"h-3 w-32\" />}\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <div className={cn(\"flex items-center gap-2 min-w-0\", className)}>\n      <UserAvatar user={resolvedUser as User | undefined} />\n\n      <div className=\"grid min-w-0 flex-1 text-left text-sm leading-tight\">\n        <span className=\"truncate font-medium text-foreground\">\n          {resolvedUser?.displayUsername ||\n            resolvedUser?.name ||\n            resolvedUser?.email}\n        </span>\n\n        {!hideSubtitle &&\n          (resolvedUser?.displayUsername || resolvedUser?.name) && (\n            <span className=\"text-muted-foreground truncate text-xs\">\n              {resolvedUser?.email}\n            </span>\n          )}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/user/user-view.tsx"
    }
  ],
  "type": "registry:component"
}