@cosplay-ai/ui
v0.7.2
Published
Shared UI components, shell primitives, and theming contract for [Cosplay](https://github.com/aaronk9-web/cosplay) and `cosplay-cloud` portal.
Readme
@cosplay-ai/ui
Shared UI components, shell primitives, and theming contract for Cosplay and cosplay-cloud portal.
Install
npm install @cosplay-ai/uiThe package is published to the public npm registry (
registry.npmjs.org). No.npmrcconfiguration is needed —npm install @cosplay-ai/uiworks out of the box.
Peer dependency matrix
The package requires the following peer dependencies to be provided by the consuming application. Pin to at least the minimum version shown.
| Peer dependency | Required version |
| --- | --- |
| react | ^19 |
| react-dom | ^19 |
| next | ^15 |
| tailwindcss | ^3.4 |
| @radix-ui/react-collapsible | ^1.1 |
| @radix-ui/react-dialog | ^1.1 |
| @radix-ui/react-dropdown-menu | ^2.1 |
| @radix-ui/react-label | ^2.1 |
| @radix-ui/react-scroll-area | ^1.2 |
| @radix-ui/react-select | ^2.2 |
| @radix-ui/react-separator | ^1.1 |
| @radix-ui/react-slot | ^1.2 |
| @radix-ui/react-tabs | ^1.1 |
| @radix-ui/react-tooltip | ^1.2 |
| lucide-react | ^1.7 |
| next-themes | ^0.4 |
| sonner | ^2 |
| react-markdown | ^10 |
| remark-gfm | ^4 |
Install them alongside @cosplay-ai/ui:
npm install react react-dom next tailwindcss \
@radix-ui/react-collapsible @radix-ui/react-dialog \
@radix-ui/react-dropdown-menu @radix-ui/react-label \
@radix-ui/react-scroll-area @radix-ui/react-select \
@radix-ui/react-separator @radix-ui/react-slot \
@radix-ui/react-tabs @radix-ui/react-tooltip \
lucide-react next-themes sonner \
react-markdown remark-gfmTailwind configuration (required)
You must add the @cosplay-ai/ui source glob to your Tailwind content array. Without it, Tailwind will tree-shake every utility class used inside the package and your application will ship with broken or unstyled UI.
// tailwind.config.js
module.exports = {
presets: [require("@cosplay-ai/ui/tailwind-preset")],
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
// Required: scan package output so Tailwind does not purge package classes
"./node_modules/@cosplay-ai/ui/**/*.{js,mjs}",
],
theme: {
extend: {
// Add portal-specific tokens here (brand colors, custom radius, etc.)
},
},
};The preset (@cosplay-ai/ui/tailwind-preset) exports the full theme.extend block (HSL-variable-driven palette, sidebar tokens, status colors, animation, typography). Consumers that need to override brand tokens do so in their own theme.extend after the preset — Tailwind merges them in order.
Import entrypoints
| Import path | Contents |
| --- | --- |
| @cosplay-ai/ui | Server-safe barrel — formatters, types, theme CSS re-export. Safe to import from React Server Components. |
| @cosplay-ai/ui/client | Client-component barrel — every component marked "use client". Import from client components and layouts. |
| @cosplay-ai/ui/styles | Default theme CSS (theme.css) — exposes the :root and html.light CSS custom property blocks. |
| @cosplay-ai/ui/primitives | Primitive theme CSS (theme-primitives.css) — layout and animation primitives. |
| @cosplay-ai/ui/tailwind-preset | Tailwind preset — the theme.extend block. Required in tailwind.config.js. |
Usage example
1. Import global styles
In your application's root layout or global CSS file, import the package stylesheet before your own overrides:
/* app/globals.css */
@import "@cosplay-ai/ui/styles"; /* default theme tokens (:root + html.light) */
@import "@cosplay-ai/ui/primitives"; /* layout + animation primitives */
/* Override brand tokens for this application */
:root {
--primary: 226 70% 55.5%;
--radius: 0.5rem;
}2. Wrap your layout with AppShell
// app/layout.tsx
import { ThemeProvider } from "@cosplay-ai/ui/client";
import { Toaster } from "sonner";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="dark">
<Toaster />
{children}
</ThemeProvider>
</body>
</html>
);
}3. Use components
// components/my-page.tsx
"use client";
import { Button } from "@cosplay-ai/ui/client";
import { StatusBadge, PriorityPicker } from "@cosplay-ai/ui/client";
export function MyPage() {
return (
<div className="p-6 space-y-4">
<StatusBadge status="in_progress" />
<PriorityPicker value="P1" onChange={() => {}} />
<Button variant="default">Run workflow</Button>
</div>
);
}4. AppShell with nav config
// app/(shell)/layout.tsx
"use client";
import {
AppShell,
AppSidebar,
ProjectProvider,
} from "@cosplay-ai/ui/client";
import { Home, Settings } from "lucide-react";
import type { NavGroup } from "@cosplay-ai/ui/client";
const navGroups: NavGroup[] = [
{
label: "Main",
items: [
{ href: "/", label: "Home", icon: <Home size={16} /> },
{ href: "/settings", label: "Settings", icon: <Settings size={16} /> },
],
},
];
export default function ShellLayout({ children }: { children: React.ReactNode }) {
return (
<ProjectProvider currentSlug={null} currentProjectName={null}>
<AppShell
sidebar={
<AppSidebar
brand={{ logo: <span>My App</span>, label: "MY APP", href: "/" }}
navGroups={navGroups}
/>
}
>
{children}
</AppShell>
</ProjectProvider>
);
}Theming
The package ships a three-layer theming contract so consumers can brand the UI without forking component source.
Layer 1 — CSS custom properties
Override any token in @cosplay-ai/ui/styles in your own stylesheet (loaded after the package's import):
/* your app/globals.css — after @import "@cosplay-ai/ui/styles" */
:root {
--primary: 262 83% 58%; /* purple brand */
--primary-foreground: 0 0% 100%;
--radius: 0.75rem;
--sidebar-background: 262 30% 14%;
}
html.light {
--primary: 262 83% 44%;
}Every Tailwind utility class (bg-primary, text-foreground, border-border, etc.) resolves through these tokens — you never touch compiled Tailwind output.
Layer 2 — Tailwind preset
The preset exposes the full theme.extend block from the cosplay dashboard. Extend it with your own tokens:
// tailwind.config.js
module.exports = {
presets: [require("@cosplay-ai/ui/tailwind-preset")],
content: [
"./app/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./node_modules/@cosplay-ai/ui/**/*.{js,mjs}", // <-- required
],
theme: {
extend: {
colors: {
brand: { 500: "hsl(var(--primary))" },
},
},
},
};Layer 3 — Slot props
When you need a different shape (not just a different color), use component slot props: brand, navGroups, footerItems, themeToggle, projectPicker. See the AppShell example above.
Client/server boundaries
Planned barrel split (ADR — task_1148)
The package exposes three export entrypoints with strict RSC compatibility rules:
| Entrypoint | Contents | "use client" rule |
| --- | --- | --- |
| @cosplay-ai/ui | Server-safe barrel — pure formatters (cn, formatDate, formatDuration, …), presentational type definitions, and CSS re-exports. Safe to import from React Server Components. | No directive — the barrel file and every constituent file must be free of hooks and browser APIs. |
| @cosplay-ai/ui/client | Client-only barrel — every React component that uses hooks or browser APIs: all 26 shadcn/Radix primitives, page bodies, workflow composites, shell components, landing components, state providers, and client hooks. | Must have "use client" at line 1 of both the barrel file (src/client.ts) and every constituent .tsx/.ts file so tree-shakers preserve the directive when files are bundled individually. |
| @cosplay-ai/ui/styles | CSS-only — theme.css (:root and html.light custom property blocks). | N/A — CSS files have no directive concept. |
| @cosplay-ai/ui/primitives | CSS-only — theme-primitives.css (layout and animation primitives). | N/A |
Why each constituent file needs its own directive
The "use client" directive on the barrel (src/client.ts) is not sufficient on its own. When a bundler (Next.js App Router, Webpack, Turbopack) processes individual files from the package, it re-evaluates the directive at the file level. A file without "use client" that happens to be re-exported from a barrel carrying the directive may still be treated as server-safe in some bundler configurations, causing a runtime error if the file contains useState or other client-only APIs. The fix is belt-and-suspenders: add "use client" to every file in the @cosplay-ai/ui/client bucket.
Current audit status
An audit checklist at .cosplay/artifacts/cto/shared-ui-client-audit.md enumerates every file slated for extraction with its current directive status. As of the UI-12 audit (task_1148), 29 dashboard source files are missing the "use client" directive and must have it added before they are extracted into packages/ui/src/. These additions are safe to land in-place in the dashboard (no runtime behavior change — Next.js already treats them as client components via their import chain).
Import rules for consumers
Components that use React hooks or browser APIs are exported exclusively from @cosplay-ai/ui/client (the "use client" barrel). Do not import from @cosplay-ai/ui/client inside React Server Components — Next.js will error at build time.
If you need a type from the client barrel inside a server file, use a type-only import:
import type { NavGroup } from "@cosplay-ai/ui/client";Server-safe utilities (formatters, pure types) are available from the default entrypoint and are safe to import anywhere:
import { cn, formatDate, formatDuration } from "@cosplay-ai/ui";Versioning + Migration
Pin discipline
The dashboard and every other consumer must exact-pin @cosplay-ai/ui — no caret (^), no tilde (~). Every version bump is intentional: you review the release notes, update the pin, run tests, then merge.
// dashboard/package.json — correct
"@cosplay-ai/ui": "0.1.0"
// dashboard/package.json — incorrect (auto-upgrades silently)
"@cosplay-ai/ui": "^0.1.0"This mirrors the pattern established by @cosplay-ai/protocol, the reference implementation for Cosplay's shared package discipline. Treat @cosplay-ai/protocol as the authoritative example of how a shared package in this monorepo is versioned, pinned, and released.
Semantic versioning during 0.x
@cosplay-ai/ui starts at 0.x while the public API stabilises:
- Patch releases (
0.1.0 → 0.1.1) — additive only: new exports, bug fixes, internal refactors, documentation updates. Consumers can apply these safely. - Minor releases (
0.1.0 → 0.2.0) — may contain breaking changes (renamed exports, changed prop types, removed components). Update the consumer pin deliberately and review the migration notes. - 1.0.0 — cut once both the Cosplay dashboard and
cosplay-cloudportal build clean from the same release for two consecutive weekly cycles. From 1.0.0 onward, standard SemVer applies: breaking changes are major-only.
Release cadence
- Weekly patch releases during the 0.x convergence phase — predictable cadence so consumers can stay current without large accumulation of changes.
- Per-PR minor releases for breaking changes during 0.x — each breaking change ships immediately so the diff is small and reviewable.
- Post-1.0: monthly minors, patches as needed.
Migration guides (1.x → 2.x and beyond)
Each future major release ships a MIGRATION-vX.md file at the root of the package (e.g. packages/ui/MIGRATION-v2.md) following the Stripe API versioning playbook: a numbered checklist of every breaking change with before/after code snippets, so consumers can step through the rollover systematically.
No MIGRATION-v*.md file exists yet — there are no breaking changes pending. This is the stub for when the first major bump happens.
Worked example: bumping the pin (dependabot-equivalent flow)
- A new patch or minor release of
@cosplay-ai/uiis published (e.g.0.1.1). - Open
dashboard/package.jsonand update the pin:- "@cosplay-ai/ui": "0.1.0", + "@cosplay-ai/ui": "0.1.1", - Run
npm installat the repo root to update the lockfile. - If it is a minor bump (e.g.
0.1.0 → 0.2.0), read the release notes and anyMIGRATION-v*.mdbefore proceeding. - Run the dashboard test suite:
npx tsc --noEmit --project dashboard/tsconfig.json npx vitest run npm run build --workspace=cosplay-dashboard - Fix any type errors or import renames surfaced by the above.
- Commit the
package.json+package-lock.jsonchange in a single commit:chore: bump @cosplay-ai/ui to 0.1.1. - Open a PR, review the diff, merge.
License
MIT — see LICENSE.
