diff --git a/apps/examples/.gitignore b/apps/examples/.gitignore
new file mode 100644
index 000000000..cccfd3167
--- /dev/null
+++ b/apps/examples/.gitignore
@@ -0,0 +1,44 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# fumadocs
+.source
\ No newline at end of file
diff --git a/apps/examples/README.md b/apps/examples/README.md
new file mode 100644
index 000000000..1a873faca
--- /dev/null
+++ b/apps/examples/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun run dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/apps/examples/app/calcom-shell/page.tsx b/apps/examples/app/calcom-shell/page.tsx
new file mode 100644
index 000000000..0676a333c
--- /dev/null
+++ b/apps/examples/app/calcom-shell/page.tsx
@@ -0,0 +1,18 @@
+import { AppSidebar } from "@/components/app-sidebar";
+import { EventTypes } from "@/components/event-types";
+import { MobileFooter } from "@/components/mobile-footer";
+import { MobileHeader } from "@/components/mobile-header";
+import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
+
+export default function Page() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/examples/app/favicon.ico b/apps/examples/app/favicon.ico
new file mode 100644
index 000000000..b0a74387b
Binary files /dev/null and b/apps/examples/app/favicon.ico differ
diff --git a/apps/examples/app/globals.css b/apps/examples/app/globals.css
new file mode 100644
index 000000000..2867b8cf3
--- /dev/null
+++ b/apps/examples/app/globals.css
@@ -0,0 +1,29 @@
+@import "@coss/ui/globals.css";
+
+:root {
+ --sidebar-foreground: color-mix(
+ in srgb,
+ var(--color-zinc-800) 80%,
+ var(--sidebar)
+ );
+ --text-xs: 0.8125rem;
+ --text-xs--line-height: 1rem;
+ --text-sm: 0.9375rem;
+ --text-sm--line-height: 1.25rem;
+ --text-base: 1.0625rem;
+ --text-base--line-height: 1.5rem;
+ --text-lg: 1.1875rem;
+ --text-lg--line-height: 1.75rem;
+ --font-weight-normal: 350;
+ --font-weight-medium: 450;
+ --font-weight-semibold: 550;
+ --font-weight-bold: 650;
+}
+
+.dark {
+ --sidebar-foreground: color-mix(
+ in srgb,
+ var(--color-zinc-200) 80%,
+ var(--sidebar)
+ );
+}
diff --git a/apps/examples/app/layout.tsx b/apps/examples/app/layout.tsx
new file mode 100644
index 000000000..b846f7819
--- /dev/null
+++ b/apps/examples/app/layout.tsx
@@ -0,0 +1,30 @@
+import "./globals.css";
+
+import { ToastProvider } from "@coss/ui/components/toast";
+import { fontHeading, fontSans } from "@coss/ui/fonts";
+import { ThemeProvider } from "@coss/ui/shared/theme-provider";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ description: "coss.com - the everything but AI company",
+ metadataBase: new URL("https://coss.com"),
+ title: "coss.com",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/apps/examples/components/app-sidebar.tsx b/apps/examples/components/app-sidebar.tsx
new file mode 100644
index 000000000..a07a9dfaa
--- /dev/null
+++ b/apps/examples/components/app-sidebar.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import type * as React from "react";
+import { HeaderActions } from "@/components/header-actions";
+import { Logo } from "@/components/logo";
+import { NavMain } from "@/components/nav-main";
+import { NavSecondary } from "@/components/nav-secondary";
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarHeader,
+ SidebarMenuButton,
+} from "@/components/ui/sidebar";
+import { navFooterItems, navMainItems } from "@/lib/navigation-data";
+
+export function AppSidebar({
+ variant,
+ ...props
+}: React.ComponentProps & {
+ variant?: never;
+}) {
+ return (
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ © 2025 Cal.com, Inc. v.5.9.6-h-2701b4d
+
+
+
+ );
+}
diff --git a/apps/examples/components/event-types.tsx b/apps/examples/components/event-types.tsx
new file mode 100644
index 000000000..985355511
--- /dev/null
+++ b/apps/examples/components/event-types.tsx
@@ -0,0 +1,336 @@
+"use client";
+
+import { Badge } from "@coss/ui/components/badge";
+import { Button } from "@coss/ui/components/button";
+import { Card, CardPanel } from "@coss/ui/components/card";
+import { Group, GroupSeparator } from "@coss/ui/components/group";
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+} from "@coss/ui/components/input-group";
+import {
+ Menu,
+ MenuCheckboxItem,
+ MenuItem,
+ MenuPopup,
+ MenuSeparator,
+ MenuTrigger,
+} from "@coss/ui/components/menu";
+import { Switch } from "@coss/ui/components/switch";
+import {
+ Tooltip,
+ TooltipCreateHandle,
+ TooltipPopup,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@coss/ui/components/tooltip";
+import {
+ ClockIcon,
+ EllipsisIcon,
+ EyeIcon,
+ Link2Icon,
+ PlusIcon,
+ SearchIcon,
+} from "lucide-react";
+import { useState } from "react";
+
+const tooltipHandle = TooltipCreateHandle();
+
+const eventTypes = [
+ {
+ duration: "15m",
+ enabled: true,
+ hidden: false,
+ id: 1,
+ path: "/pasquale/15min",
+ title: "15 Min Meeting",
+ },
+ {
+ duration: "30m",
+ enabled: true,
+ hidden: false,
+ id: 2,
+ path: "/pasquale/30min",
+ title: "30 Min Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 3,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 4,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 5,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 6,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 7,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 8,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 9,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+ {
+ duration: "15m",
+ enabled: false,
+ hidden: true,
+ id: 10,
+ path: "/pasquale/secret",
+ title: "Secret Meeting",
+ },
+];
+
+export function EventTypes() {
+ const [hiddenStates, setHiddenStates] = useState>(
+ Object.fromEntries(eventTypes.map((et) => [et.id, et.hidden])),
+ );
+
+ const handleHiddenToggle = (id: number, checked: boolean) => {
+ setHiddenStates((prev) => ({
+ ...prev,
+ [id]: checked,
+ }));
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
Event Types
+
+ Create events to share for people to book on your calendar.
+
+
+
+
+
+ {/* Search */}
+
+
+
+
+
+
+
+
+
+
+ {eventTypes.map((eventType) => {
+ const isHidden = hiddenStates[eventType.id];
+ return (
+
+
+
+ {/* Content */}
+
+
+
+
+
+ {eventType.duration}
+
+
+
+ {eventType.path}
+
+
+ {/* Actions */}
+
+
+ {isHidden ? (
+
+ Hidden
+
+ ) : null}
+
+
+ handleHiddenToggle(eventType.id, !checked)
+ }
+ />
+ }
+ />
+
+ {isHidden ? "Show on profile" : "Hide from profile"}
+
+
+
+ {/* Desktop: Group of buttons */}
+
+ "Preview"}
+ render={
+
+ }
+ />
+
+ "Copy link"}
+ render={
+
+ }
+ />
+
+
+
+ {/* Mobile: Single menu button with all actions */}
+
+
+
+
+
+ );
+ })}
+
+
+ {/* No more results */}
+
+ No more results
+
+
+
+ {({ payload: Payload }) => (
+ {Payload !== undefined && }
+ )}
+
+
+ );
+}
diff --git a/apps/examples/components/header-actions.tsx b/apps/examples/components/header-actions.tsx
new file mode 100644
index 000000000..484c83eb5
--- /dev/null
+++ b/apps/examples/components/header-actions.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "@coss/ui/components/avatar";
+import { SearchIcon } from "lucide-react";
+import Link from "next/link";
+import { SidebarMenuButton } from "@/components/ui/sidebar";
+import { UserMenu } from "@/components/user-menu";
+
+export function HeaderActions() {
+ return (
+
+
+
+
+
+
+
+ CC
+
+
+ }
+ />
+
+
+ );
+}
diff --git a/apps/examples/components/logo.tsx b/apps/examples/components/logo.tsx
new file mode 100644
index 000000000..f38187b8b
--- /dev/null
+++ b/apps/examples/components/logo.tsx
@@ -0,0 +1,11 @@
+import Link from "next/link";
+
+export function Logo({ ...props }) {
+ return (
+
+
+ Cal.com
+
+
+ );
+}
diff --git a/apps/examples/components/mobile-footer.tsx b/apps/examples/components/mobile-footer.tsx
new file mode 100644
index 000000000..679042bb8
--- /dev/null
+++ b/apps/examples/components/mobile-footer.tsx
@@ -0,0 +1,86 @@
+"use client";
+
+import { Button } from "@coss/ui/components/button";
+import {
+ Menu,
+ MenuGroup,
+ MenuItem,
+ MenuPopup,
+ MenuSeparator,
+ MenuTrigger,
+} from "@coss/ui/components/menu";
+import { cn } from "@coss/ui/lib/utils";
+import { EllipsisIcon, PlusIcon } from "lucide-react";
+import Link from "next/link";
+import { useScrollHide } from "@/hooks/use-scroll-hide";
+import { navFooterItems, navMainItems } from "@/lib/navigation-data";
+
+const primaryNavItems = navMainItems.slice(0, 3);
+const remainingMainItems = navMainItems.slice(3);
+
+export function MobileFooter() {
+ const isHidden = useScrollHide();
+
+ return (
+
+ );
+}
diff --git a/apps/examples/components/mobile-header.tsx b/apps/examples/components/mobile-header.tsx
new file mode 100644
index 000000000..be04473db
--- /dev/null
+++ b/apps/examples/components/mobile-header.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { cn } from "@coss/ui/lib/utils";
+import { HeaderActions } from "@/components/header-actions";
+import { Logo } from "@/components/logo";
+import { useScrollHide } from "@/hooks/use-scroll-hide";
+
+export function MobileHeader() {
+ const isHidden = useScrollHide();
+
+ return (
+
+ );
+}
diff --git a/apps/examples/components/nav-main.tsx b/apps/examples/components/nav-main.tsx
new file mode 100644
index 000000000..6b649684a
--- /dev/null
+++ b/apps/examples/components/nav-main.tsx
@@ -0,0 +1,209 @@
+"use client";
+
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@coss/ui/components/collapsible";
+import {
+ Menu,
+ MenuGroup,
+ MenuGroupLabel,
+ MenuItem,
+ MenuPopup,
+ MenuTrigger,
+} from "@coss/ui/components/menu";
+import { TooltipTrigger } from "@coss/ui/components/tooltip";
+import { ChevronRightIcon, type LucideIcon } from "lucide-react";
+import Link from "next/link";
+import { useEffect, useRef } from "react";
+import {
+ SidebarGroup,
+ SidebarMenu,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ sidebarTooltipHandle,
+ useSidebarMenuOpen,
+} from "@/components/ui/sidebar";
+import { useIsBetweenMdAndLg } from "@/hooks/use-mobile";
+
+type BaseNavItem = {
+ title: string;
+ url: string;
+ icon: LucideIcon;
+ isActive?: boolean;
+ badge?: string;
+};
+
+type NavSubItem = {
+ title: string;
+ url: string;
+};
+
+type NavItemWithChildren = BaseNavItem & { items: NavSubItem[] };
+type NavItemLeaf = BaseNavItem & { items?: undefined };
+type NavItem = NavItemLeaf | NavItemWithChildren;
+
+function hasSubItems(item: NavItem): item is NavItemWithChildren {
+ return Array.isArray(item.items) && item.items.length > 0;
+}
+
+function NavItemWithSubmenu({ item }: { item: NavItemWithChildren }) {
+ const isBetweenMdAndLg = useIsBetweenMdAndLg();
+ const { registerMenu } = useSidebarMenuOpen();
+ const unregisterRef = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ return () => {
+ if (unregisterRef.current) {
+ unregisterRef.current();
+ }
+ };
+ }, []);
+
+ const TooltipContent = () => item.title;
+
+ return (
+
+ {/* Menu version for collapsed sidebar (md-lg breakpoint) */}
+
+
+ {/* Collapsible version for expanded sidebar */}
+ }
+ >
+
+ }
+ >
+
+
+ {item.title}
+
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+
+
+
+ {item.items.map((subItem) => (
+
+
+ {subItem.title}
+
+ }
+ />
+
+ ))}
+
+
+
+
+ );
+}
+
+function NavItemSimple({ item }: { item: NavItemLeaf }) {
+ const isBetweenMdAndLg = useIsBetweenMdAndLg();
+
+ return (
+
+
+
+ {item.title}
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+ }
+ tooltip={isBetweenMdAndLg ? item.title : undefined}
+ />
+
+ );
+}
+
+export function NavMain({ items }: { items: NavItem[] }) {
+ return (
+
+
+ {items.map((item) =>
+ hasSubItems(item) ? (
+
+ ) : (
+
+ ),
+ )}
+
+
+ );
+}
diff --git a/apps/examples/components/nav-secondary.tsx b/apps/examples/components/nav-secondary.tsx
new file mode 100644
index 000000000..41d29e3e4
--- /dev/null
+++ b/apps/examples/components/nav-secondary.tsx
@@ -0,0 +1,48 @@
+"use client";
+
+import type { LucideIcon } from "lucide-react";
+import type * as React from "react";
+
+import {
+ SidebarGroup,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+} from "@/components/ui/sidebar";
+import { useIsBetweenMdAndLg } from "@/hooks/use-mobile";
+
+export function NavSecondary({
+ items,
+ ...props
+}: {
+ items: {
+ title: string;
+ url: string;
+ icon: LucideIcon;
+ }[];
+} & React.ComponentPropsWithoutRef) {
+ const isBetweenMdAndLg = useIsBetweenMdAndLg();
+
+ return (
+
+
+ {items.map((item) => (
+
+
+
+
+ {item.title}
+
+
+ }
+ tooltip={isBetweenMdAndLg ? item.title : undefined}
+ />
+
+ ))}
+
+
+ );
+}
diff --git a/apps/examples/components/ui/sidebar.tsx b/apps/examples/components/ui/sidebar.tsx
new file mode 100644
index 000000000..fa9871d45
--- /dev/null
+++ b/apps/examples/components/ui/sidebar.tsx
@@ -0,0 +1,475 @@
+"use client";
+
+import { mergeProps } from "@base-ui-components/react/merge-props";
+import { useRender } from "@base-ui-components/react/use-render";
+import { ScrollArea } from "@coss/ui/components/scroll-area";
+import { Separator } from "@coss/ui/components/separator";
+import { Skeleton } from "@coss/ui/components/skeleton";
+import {
+ Tooltip,
+ TooltipCreateHandle,
+ TooltipPopup,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@coss/ui/components/tooltip";
+import { cn } from "@coss/ui/lib/utils";
+import * as React from "react";
+import { useIsBetweenMdAndLg, useIsMobile } from "@/hooks/use-mobile";
+
+type SidebarTooltipHandle = ReturnType<
+ typeof TooltipCreateHandle
+>;
+
+export const sidebarTooltipHandle: SidebarTooltipHandle =
+ TooltipCreateHandle();
+
+const SidebarMenuOpenContext = React.createContext<{
+ openMenuCount: number;
+ registerMenu: () => () => void;
+}>({
+ openMenuCount: 0,
+ registerMenu: () => () => {},
+});
+
+function SidebarProvider({
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div">) {
+ const [openMenuCount, setOpenMenuCount] = React.useState(0);
+
+ const registerMenu = React.useCallback(() => {
+ setOpenMenuCount((prev) => prev + 1);
+ return () => {
+ setOpenMenuCount((prev) => Math.max(0, prev - 1));
+ };
+ }, []);
+
+ return (
+
+
+
+ {children}
+
+ {({ payload: Payload }) => (
+ 0 ? "hidden" : undefined}
+ side="right"
+ >
+ {Payload !== undefined && }
+
+ )}
+
+
+
+
+ );
+}
+
+export function useSidebarMenuOpen() {
+ return React.useContext(SidebarMenuOpenContext);
+}
+
+function Sidebar({
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarInset({
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"main">) {
+ return (
+
+ {children}
+
+ );
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+
+
+ );
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarGroupLabel({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"div">) {
+ const defaultProps = {
+ className: cn(
+ "flex h-8 shrink-0 items-center rounded-lg px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ "md:max-lg:-mt-8 md:max-lg:opacity-0",
+ className,
+ ),
+ "data-sidebar": "group-label",
+ "data-slot": "sidebar-group-label",
+ };
+
+ return useRender({
+ defaultTagName: "div",
+ props: mergeProps(defaultProps, props),
+ render,
+ });
+}
+
+function SidebarGroupAction({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"button">) {
+ const defaultProps = {
+ className: cn(
+ "absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-lg p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0",
+ "after:-inset-2 after:absolute md:after:hidden",
+ "md:max-lg:hidden",
+ className,
+ ),
+ "data-sidebar": "group-action",
+ "data-slot": "sidebar-group-action",
+ };
+
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps(defaultProps, props),
+ render,
+ });
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function SidebarMenuButton({
+ isActive = false,
+ tooltip,
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"button"> & {
+ isActive?: boolean;
+ tooltip?: string | React.ComponentType;
+}) {
+ const isMobile = useIsMobile();
+ const isBetweenMdAndLg = useIsBetweenMdAndLg();
+ const state = isBetweenMdAndLg ? "collapsed" : "expanded";
+ const showTooltip = state === "collapsed" && !isMobile;
+
+ const defaultProps = {
+ className: cn(
+ "peer/menu-button cursor-pointer flex w-full items-center gap-2 overflow-hidden rounded-lg p-2 text-left text-sm outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pe-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground data-pressed:bg-sidebar-accent data-pressed:text-sidebar-accent-foreground max-lg:size-10 max-lg:p-0 max-lg:justify-center h-8 [&>span:last-child]:truncate md:[&>svg:not([class*='size-'])]:size-4 [&>svg:not([class*='size-'])]:size-5 [&>svg]:shrink-0",
+ className,
+ ),
+ "data-active": isActive,
+ "data-sidebar": "menu-button",
+ "data-slot": "sidebar-menu-button",
+ };
+
+ const buttonProps = mergeProps<"button">(defaultProps, props);
+
+ const buttonElement = useRender({
+ defaultTagName: "button",
+ props: buttonProps,
+ render,
+ });
+
+ if (!tooltip || !showTooltip) {
+ return buttonElement;
+ }
+
+ // Convert string tooltip to a component
+ const TooltipContent = typeof tooltip === "string" ? () => tooltip : tooltip;
+
+ return (
+ >}
+ />
+ );
+}
+
+function SidebarMenuAction({
+ className,
+ showOnHover = false,
+ render,
+ ...props
+}: useRender.ComponentProps<"button"> & {
+ showOnHover?: boolean;
+}) {
+ const defaultProps = {
+ className: cn(
+ "absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-lg p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0",
+ "after:-inset-2 after:absolute md:after:hidden",
+ "md:max-lg:hidden",
+ showOnHover &&
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ className,
+ ),
+ "data-sidebar": "menu-action",
+ "data-slot": "sidebar-menu-action",
+ };
+
+ return useRender({
+ defaultTagName: "button",
+ props: mergeProps<"button">(defaultProps, props),
+ render,
+ });
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean;
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ }, []);
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ );
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubButton({
+ isActive = false,
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"a"> & {
+ isActive?: boolean;
+}) {
+ const defaultProps = {
+ className: cn(
+ "-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-lg px-2 text-sm text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ "md:max-lg:hidden",
+ className,
+ ),
+ "data-active": isActive,
+ "data-sidebar": "menu-sub-button",
+ "data-slot": "sidebar-menu-sub-button",
+ };
+
+ return useRender({
+ defaultTagName: "a",
+ props: mergeProps<"a">(defaultProps, props),
+ render,
+ });
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarSeparator,
+};
diff --git a/apps/examples/components/user-menu.tsx b/apps/examples/components/user-menu.tsx
new file mode 100644
index 000000000..c1ba29213
--- /dev/null
+++ b/apps/examples/components/user-menu.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "@coss/ui/components/avatar";
+import {
+ Menu,
+ MenuGroup,
+ MenuGroupLabel,
+ MenuItem,
+ MenuPopup,
+ MenuSeparator,
+ MenuTrigger,
+} from "@coss/ui/components/menu";
+import {
+ GaugeIcon,
+ LogOutIcon,
+ MessageCircleQuestionMarkIcon,
+ MilestoneIcon,
+ MonitorDownIcon,
+ MoonStarIcon,
+ SettingsIcon,
+ UserRoundIcon,
+} from "lucide-react";
+import { SidebarMenuButton } from "@/components/ui/sidebar";
+import { useIsBetweenMdAndLg } from "@/hooks/use-mobile";
+
+interface UserMenuProps {
+ variant?: "sidebar" | "mobile";
+}
+
+export function UserMenu({ variant = "sidebar" }: UserMenuProps) {
+ const isBetweenMdAndLg = useIsBetweenMdAndLg();
+
+ return (
+
+ );
+}
diff --git a/apps/examples/hooks/use-mobile.ts b/apps/examples/hooks/use-mobile.ts
new file mode 100644
index 000000000..afb93eb08
--- /dev/null
+++ b/apps/examples/hooks/use-mobile.ts
@@ -0,0 +1,48 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768; // Tailwind md breakpoint
+const LG_BREAKPOINT = 1024; // Tailwind lg breakpoint
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(
+ undefined,
+ );
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isMobile;
+}
+
+export function useIsBetweenMdAndLg() {
+ const [isBetween, setIsBetween] = React.useState(
+ undefined,
+ );
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(
+ `(min-width: ${MOBILE_BREAKPOINT}px) and (max-width: ${LG_BREAKPOINT - 1}px)`,
+ );
+ const onChange = () => {
+ setIsBetween(
+ window.innerWidth >= MOBILE_BREAKPOINT &&
+ window.innerWidth < LG_BREAKPOINT,
+ );
+ };
+ mql.addEventListener("change", onChange);
+ setIsBetween(
+ window.innerWidth >= MOBILE_BREAKPOINT &&
+ window.innerWidth < LG_BREAKPOINT,
+ );
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isBetween;
+}
diff --git a/apps/examples/hooks/use-scroll-hide.ts b/apps/examples/hooks/use-scroll-hide.ts
new file mode 100644
index 000000000..d04b27130
--- /dev/null
+++ b/apps/examples/hooks/use-scroll-hide.ts
@@ -0,0 +1,38 @@
+import * as React from "react";
+
+const DEFAULT_SCROLL_THRESHOLD = 48;
+
+export function useScrollHide(threshold = DEFAULT_SCROLL_THRESHOLD) {
+ const [isHidden, setIsHidden] = React.useState(false);
+ const lastScrollY = React.useRef(0);
+
+ React.useEffect(() => {
+ const handleScroll = () => {
+ const currentY = window.scrollY;
+ const delta = currentY - lastScrollY.current;
+
+ if (currentY <= 0) {
+ setIsHidden(false);
+ lastScrollY.current = currentY;
+ return;
+ }
+
+ if (Math.abs(delta) < threshold) {
+ return;
+ }
+
+ if (delta > 0) {
+ setIsHidden(true);
+ } else {
+ setIsHidden(false);
+ }
+
+ lastScrollY.current = currentY;
+ };
+
+ window.addEventListener("scroll", handleScroll, { passive: true });
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [threshold]);
+
+ return isHidden;
+}
diff --git a/apps/examples/lib/navigation-data.ts b/apps/examples/lib/navigation-data.ts
new file mode 100644
index 000000000..83903e362
--- /dev/null
+++ b/apps/examples/lib/navigation-data.ts
@@ -0,0 +1,145 @@
+import {
+ ActivityIcon,
+ CalendarIcon,
+ ClockFadingIcon,
+ ContactRoundIcon,
+ CopyIcon,
+ ExternalLinkIcon,
+ GiftIcon,
+ Grid2x2Plus,
+ Link2Icon,
+ type LucideIcon,
+ RouteIcon,
+ SettingsIcon,
+ UsersRoundIcon,
+ WorkflowIcon,
+} from "lucide-react";
+
+export interface NavItem {
+ title: string;
+ url: string;
+ icon: LucideIcon;
+ isActive?: boolean;
+ badge?: string;
+ items?: {
+ title: string;
+ url: string;
+ }[];
+}
+
+export interface User {
+ avatar: string;
+ email: string;
+ name: string;
+}
+
+export const navMainItems: NavItem[] = [
+ {
+ icon: Link2Icon,
+ isActive: true,
+ title: "Event Types",
+ url: "#",
+ },
+ {
+ icon: CalendarIcon,
+ items: [
+ {
+ title: "Upcoming",
+ url: "#",
+ },
+ {
+ title: "Unconfirmed",
+ url: "#",
+ },
+ {
+ title: "Recurring",
+ url: "#",
+ },
+ {
+ title: "Past",
+ url: "#",
+ },
+ {
+ title: "Canceled",
+ url: "#",
+ },
+ ],
+ title: "Bookings",
+ url: "#",
+ },
+ {
+ icon: ClockFadingIcon,
+ title: "Availability",
+ url: "#",
+ },
+ {
+ icon: ContactRoundIcon,
+ title: "Members",
+ url: "#",
+ },
+ {
+ icon: UsersRoundIcon,
+ title: "Teams",
+ url: "#",
+ },
+ {
+ icon: Grid2x2Plus,
+ items: [
+ {
+ title: "App Store",
+ url: "#",
+ },
+ {
+ title: "Installed Apps",
+ url: "#",
+ },
+ ],
+ title: "Apps",
+ url: "#",
+ },
+ {
+ icon: RouteIcon,
+ title: "Routing",
+ url: "#",
+ },
+ {
+ badge: "Cal.ai",
+ icon: WorkflowIcon,
+ title: "Workflows",
+ url: "#",
+ },
+ {
+ icon: ActivityIcon,
+ title: "Insights",
+ url: "#",
+ },
+];
+
+export const navFooterItems: NavItem[] = [
+ {
+ icon: ExternalLinkIcon,
+ title: "View public page",
+ url: "#",
+ },
+ {
+ icon: CopyIcon,
+ title: "Copy public page link",
+ url: "#",
+ },
+ {
+ icon: GiftIcon,
+ title: "Refer and earn",
+ url: "#",
+ },
+ {
+ icon: SettingsIcon,
+ title: "Settings",
+ url: "#",
+ },
+];
+
+export const user: User = {
+ avatar: "",
+ email: "pasqua@example.com",
+ name: "Pasquale",
+};
diff --git a/apps/examples/next.config.ts b/apps/examples/next.config.ts
new file mode 100644
index 000000000..5dec87516
--- /dev/null
+++ b/apps/examples/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ transpilePackages: ["@coss/ui"],
+};
+
+export default nextConfig;
diff --git a/apps/examples/package.json b/apps/examples/package.json
new file mode 100644
index 000000000..555bd5857
--- /dev/null
+++ b/apps/examples/package.json
@@ -0,0 +1,33 @@
+{
+ "dependencies": {
+ "@base-ui-components/react": "^1.0.0-rc.0",
+ "@coss/ui": "workspace:*",
+ "@hugeicons/core-free-icons": "^2.0.0",
+ "@hugeicons/react": "^1.1.1",
+ "lucide-react": "^0.555.0",
+ "next": "16.0.9",
+ "react": "19.2.3",
+ "react-dom": "19.2.3"
+ },
+ "devDependencies": {
+ "@coss/typescript-config": "workspace:*",
+ "@tailwindcss/postcss": "^4.1.17",
+ "@types/node": "^24.10.1",
+ "@types/react": "19.2.6",
+ "@types/react-dom": "19.2.3",
+ "tailwindcss": "^4.1.17",
+ "typescript": "^5.9.3"
+ },
+ "license": "AGPL-3.0-or-later",
+ "name": "examples",
+ "private": true,
+ "scripts": {
+ "build": "next build",
+ "clean": "rm -rf node_modules && rm -rf .turbo && rm -rf .next",
+ "dev": "next dev",
+ "lint": "biome lint .",
+ "start": "next start",
+ "typecheck": "tsc --noEmit"
+ },
+ "version": "0.1.0"
+}
diff --git a/apps/examples/postcss.config.mjs b/apps/examples/postcss.config.mjs
new file mode 100644
index 000000000..af6087cdf
--- /dev/null
+++ b/apps/examples/postcss.config.mjs
@@ -0,0 +1,3 @@
+import { postcssConfig } from "@coss/ui/postcss.config";
+
+export default postcssConfig;
diff --git a/apps/examples/tsconfig.json b/apps/examples/tsconfig.json
new file mode 100644
index 000000000..e84f6335c
--- /dev/null
+++ b/apps/examples/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./*"]
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "exclude": ["node_modules"],
+ "extends": "@coss/typescript-config/nextjs.json",
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "next-env.d.ts",
+ "next.config.ts",
+ ".next/types/**/*.ts"
+ ]
+}
diff --git a/bun.lock b/bun.lock
index 4f15dd371..5eadee9a2 100644
--- a/bun.lock
+++ b/bun.lock
@@ -11,6 +11,29 @@
"typescript": "^5.9.3",
},
},
+ "apps/examples": {
+ "name": "examples",
+ "version": "0.1.0",
+ "dependencies": {
+ "@base-ui-components/react": "^1.0.0-rc.0",
+ "@coss/ui": "workspace:*",
+ "@hugeicons/core-free-icons": "^2.0.0",
+ "@hugeicons/react": "^1.1.1",
+ "lucide-react": "^0.555.0",
+ "next": "16.0.9",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ },
+ "devDependencies": {
+ "@coss/typescript-config": "workspace:*",
+ "@tailwindcss/postcss": "^4.1.17",
+ "@types/node": "^24.10.1",
+ "@types/react": "19.2.6",
+ "@types/react-dom": "19.2.3",
+ "tailwindcss": "^4.1.17",
+ "typescript": "^5.9.3",
+ },
+ },
"apps/origin": {
"name": "origin",
"version": "0.1.0",
@@ -236,6 +259,10 @@
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
+ "@base-ui-components/react": ["@base-ui-components/react@1.0.0-rc.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui-components/utils": "0.2.2", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-9lhUFbJcbXvc9KulLev1WTFxS/alJRBWDH/ibKSQaNvmDwMFS2gKp1sTeeldYSfKuS/KC1w2MZutc0wHu2hRHQ=="],
+
+ "@base-ui-components/utils": ["@base-ui-components/utils@0.2.2", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-rNJCD6TFy3OSRDKVHJDzLpxO3esTV1/drRtWNUpe7rCpPN9HZVHUCuP+6rdDYDGWfXnQHbqi05xOyRP2iZAlkw=="],
+
"@base-ui/react": ["@base-ui/react@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.3", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.3.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg=="],
"@base-ui/utils": ["@base-ui/utils@0.2.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ=="],
@@ -1236,6 +1263,8 @@
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
+ "examples": ["examples@workspace:apps/examples"],
+
"execa": ["execa@7.2.0", "", { "dependencies": { "cross-spawn": "7.0.6", "get-stream": "6.0.1", "human-signals": "4.3.1", "is-stream": "3.0.0", "merge-stream": "2.0.0", "npm-run-path": "5.3.0", "onetime": "6.0.0", "signal-exit": "3.0.7", "strip-final-newline": "3.0.0" } }, "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="],
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "2.0.0", "body-parser": "2.2.0", "content-disposition": "1.0.0", "content-type": "1.0.5", "cookie": "0.7.2", "cookie-signature": "1.2.2", "debug": "4.4.1", "encodeurl": "2.0.0", "escape-html": "1.0.3", "etag": "1.8.1", "finalhandler": "2.1.0", "fresh": "2.0.0", "http-errors": "2.0.0", "merge-descriptors": "2.0.0", "mime-types": "3.0.1", "on-finished": "2.4.1", "once": "1.4.0", "parseurl": "1.3.3", "proxy-addr": "2.0.7", "qs": "6.14.0", "range-parser": "1.2.1", "router": "2.2.0", "send": "1.2.0", "serve-static": "2.2.0", "statuses": "2.0.2", "type-is": "2.0.1", "vary": "1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 52ac8bfb7..80f53d68d 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -26,6 +26,7 @@
},
"exports": {
"./components/*": "./src/components/*.tsx",
+ "./fonts": "./src/fonts/index.ts",
"./globals.css": "./src/styles/globals.css",
"./hooks/*": "./src/hooks/*.ts",
"./lib/*": "./src/lib/*.ts",
diff --git a/packages/ui/src/fonts/CalSans-Regular.woff2 b/packages/ui/src/fonts/CalSans-Regular.woff2
new file mode 100644
index 000000000..6c83a2df3
Binary files /dev/null and b/packages/ui/src/fonts/CalSans-Regular.woff2 differ
diff --git a/packages/ui/src/fonts/CalSansUI[MODE,wght].woff2 b/packages/ui/src/fonts/CalSansUI[MODE,wght].woff2
new file mode 100644
index 000000000..d353c5893
Binary files /dev/null and b/packages/ui/src/fonts/CalSansUI[MODE,wght].woff2 differ
diff --git a/packages/ui/src/fonts/README.md b/packages/ui/src/fonts/README.md
new file mode 100644
index 000000000..6e03ca1bc
--- /dev/null
+++ b/packages/ui/src/fonts/README.md
@@ -0,0 +1,49 @@
+# Shared Fonts
+
+This directory contains shared font files and configurations used across all apps in the monorepo.
+
+## Usage
+
+Import fonts directly from the shared UI package:
+
+```tsx
+import { fontSans, fontHeading } from "@coss/ui/fonts";
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+```
+
+## Available Fonts
+
+- `fontSans` - Cal Sans UI variable font (supports multiple weights and modes)
+- `fontHeading` - Cal Sans Regular font
+
+## Adding New Fonts
+
+1. Place the font file in this directory (`packages/ui/src/fonts/`)
+2. Add a new font configuration in `index.ts`:
+
+```typescript
+export const yourNewFont = localFont({
+ display: "swap",
+ src: "./YourFont.woff2",
+ variable: "--font-your-name",
+});
+```
+
+3. Use it in any app by importing from `@coss/ui/fonts`
+
+## Benefits of This Approach
+
+- Single source of truth for fonts
+- No fragile relative paths
+- Type-safe imports
+- Versioned with the UI package
+- Easy to update across all apps
diff --git a/packages/ui/src/fonts/index.ts b/packages/ui/src/fonts/index.ts
new file mode 100644
index 000000000..06ff61512
--- /dev/null
+++ b/packages/ui/src/fonts/index.ts
@@ -0,0 +1,13 @@
+import localFont from "next/font/local";
+
+export const fontSans = localFont({
+ display: "swap",
+ src: "./CalSansUI[MODE,wght].woff2",
+ variable: "--font-sans",
+});
+
+export const fontHeading = localFont({
+ display: "swap",
+ src: "./CalSans-Regular.woff2",
+ variable: "--font-heading",
+});