From 2e7770f2dc11b380bf38321fc391fa06626d7bbf Mon Sep 17 00:00:00 2001 From: Madeleine Charity Date: Mon, 23 Feb 2026 11:19:49 -0500 Subject: [PATCH] Add Privy design skill document for Claude Comprehensive markdown reference extracted from privy-next-starter that Claude can consume as design system context. Includes copy-pasteable templates (globals.css, tailwind.config.js, layout.tsx), exact color hex values, component patterns with JSX snippets, and layout recipes. Co-Authored-By: Claude Opus 4.6 Committed-By-Agent: claude --- examples/PRIVY_DESIGN_SKILL.md | 696 +++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 examples/PRIVY_DESIGN_SKILL.md diff --git a/examples/PRIVY_DESIGN_SKILL.md b/examples/PRIVY_DESIGN_SKILL.md new file mode 100644 index 0000000..a49d72e --- /dev/null +++ b/examples/PRIVY_DESIGN_SKILL.md @@ -0,0 +1,696 @@ +# Privy Design Skill + +## 1. Overview & Usage + +Include this document as context in your prompt alongside feature docs. Tell Claude: "Use this Privy design skill for the UI." The result will be a demo app that matches Privy's visual identity — colors, fonts, component patterns, and layout — without manually specifying design tokens each time. + +All values are extracted from [privy-next-starter](https://github.com/privy-io/examples/tree/main/privy-next-starter), the canonical Privy example app. + +--- + +## 2. Tech Stack + +- **Framework:** Next.js 15+ App Router, TypeScript +- **Styling:** Tailwind CSS v4 (with `@tailwindcss/postcss`) +- **Auth:** `@privy-io/react-auth` ^3.12.0 +- **Icons:** `@heroicons/react` ^2.2.0 +- **Notifications:** `react-toastify` ^11.0.5 +- **Package manager:** `pnpm` +- **Dev server:** Turbopack (`next dev --turbopack`) + +--- + +## 3. Color System + +### Brand + +| Token | Hex | Usage | +|-------|-----|-------| +| Primary | `#5B4FFF` | Buttons, links, borders, focus rings | +| Primary hover | `#4A3EE6` | `.button-primary` hover/focus, `.button-outline` active | +| Primary active | `#3F35D9` | `.button-primary` active state | +| Logo/dark | `#010110` | Dark logo fill | + +### Neutrals + +| Token | Hex | Usage | +|-------|-----|-------| +| Background | `#ffffff` | Page background (CSS var `--background`) | +| Foreground | `#171717` | Body text color (CSS var `--foreground`) | +| Text dark | `#040217` | `.button` text color | +| Borders | `#E2E3F0` | `.button` border, header border, sidebar border | +| Light bg | `#E0E7FF66` | Authenticated page background (with alpha) | +| Code/pre bg | `#F1F2F9` | JSON display, code blocks | +| Badge/highlight bg | `#E0E7FF` | Section file path badges | +| Loader fill | `#E1E4FF` | Fullscreen loader SVG fill | + +### Status + +Success, warning, and error states are handled via `react-toastify` toast notifications — no custom status colors needed. + +--- + +## 4. Typography + +### Fonts + +| Font | File | CSS Variable | Usage | +|------|------|-------------|-------| +| **Inter** | `InterVariable.ttf` | `--font-inter` | Body text, UI elements | +| **ABC Favorit Medium** | `ABCFavorit-Medium.woff2` | `--font-abc-favorit` | Hero headings, branded pill badges | + +- **Fallback stack:** Arial, Helvetica, sans-serif +- Body uses `font-family: var(--font-inter)` on `` +- ABC Favorit is used for: hero headings (`font-abc-favorit`), pill badges on the landing page + +### Font Loading Pattern (layout.tsx) + +```tsx +import localFont from "next/font/local"; + +const inter = localFont({ + src: "../../public/fonts/InterVariable.ttf", + variable: "--font-inter", + display: "swap", +}); + +const abcFavorit = localFont({ + src: "../../public/fonts/ABCFavorit-Medium.woff2", + variable: "--font-abc-favorit", + display: "swap", +}); + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} +``` + +--- + +## 5. globals.css Template + +Copy-pasteable. Drop this into `src/app/globals.css`: + +```css +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-inter); + --font-abc-favorit: var(--font-abc-favorit); +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--font-inter), Arial, Helvetica, sans-serif; +} + +@layer components { + .button-primary { + @apply bg-[#5B4FFF] hover:bg-[#4A3EE6] focus:bg-[#4A3EE6] active:bg-[#3F35D9] + disabled:bg-gray-300 disabled:cursor-not-allowed + text-white font-medium + px-4 py-2 rounded-md + border border-transparent + transition-colors duration-200 ease-in-out + focus:outline-none focus:ring-2 focus:ring-[#5B4FFF] focus:ring-offset-2 + inline-flex items-center justify-center + text-sm leading-5 cursor-pointer; + } + + .button-outline { + @apply flex flex-row items-center + px-4 py-2 gap-1 cursor-pointer + h-9 rounded-full + bg-transparent border border-[#5B4FFF] + text-[#5B4FFF] font-medium text-sm leading-tight + hover:bg-[#5B4FFF] hover:text-white + focus:bg-[#5B4FFF] focus:text-white + active:bg-[#4A3EE6] + focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 + transition-all duration-200 ease-in-out + disabled:opacity-50 disabled:cursor-not-allowed; + } + + .button { + @apply flex flex-row items-center + px-3 py-2 gap-3 cursor-pointer + bg-white border border-[#E2E3F0] rounded-xl + text-[#040217] font-normal text-base leading-6 + hover:bg-gray-50 hover:border-gray-300 + focus:bg-gray-50 focus:border-gray-300 + active:bg-gray-100 + focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-offset-2 + transition-all duration-200 ease-in-out + disabled:opacity-50 disabled:cursor-not-allowed + box-border; + } + + .text-primary { + @apply text-[#5B4FFF]; + } + .border-primary { + @apply border-[#5B4FFF]; + } + + .loader-overlay { + @apply absolute left-0 top-0 z-10 flex h-full w-full items-center justify-center bg-white/30; + animation: fade-in 150ms ease-in forwards; + } +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +``` + +--- + +## 6. tailwind.config.js Template + +Copy-pasteable. Drop this into `tailwind.config.js`: + +```js +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + fontFamily: { + sans: ["var(--font-inter)", "Arial", "Helvetica", "sans-serif"], + "abc-favorit": ["var(--font-abc-favorit)", "Arial", "Helvetica", "sans-serif"], + }, + }, + }, + plugins: [], +}; +``` + +--- + +## 7. Layout Patterns + +### Header (`components/ui/header.tsx`) + +- **Height:** 60px, fixed position, `z-50` +- **Authenticated state:** `bg-white border-b border-[#E2E3F0]` with dark logo +- **Unauthenticated state:** `bg-transparent border-none` with light logo, overlays on hero +- **Left side:** Logo image + pill badge (`rounded-[11px] border border-primary text-primary` with `text-[0.75rem]`) +- **Right side:** Docs link (`text-primary` + `ArrowUpRightIcon`) + Dashboard CTA (`button-primary rounded-full`, `hidden md:block`) + +```tsx +import { ArrowRightIcon, ArrowUpRightIcon } from "@heroicons/react/16/solid"; +import Image from "next/image"; + +interface HeaderProps { + authenticated: boolean; +} + +export function Header({ authenticated }: HeaderProps) { + return ( +
+
+ Privy Logo + + {authenticated && ( +
+ Next.js Demo +
+ )} +
+ +
+ + Docs + + + +
+
+ ); +} +``` + +### Hero / Landing (Unauthenticated) + +- Full-screen with `BG.svg` background image (`fill`, `objectFit: "cover"`) +- Centered column layout, `z-10` +- Pill badge: `rounded-[20px] border border-white px-6 text-lg text-white font-abc-favorit` +- Heading: `text-7xl font-medium font-abc-favorit leading-[81.60px] text-white` +- Subtitle: `text-xl font-normal text-white leading-loose` +- CTA button: `bg-white text-brand-off-black rounded-full px-4 py-2 hover:bg-gray-100` — scales up at `lg:` breakpoint + +```tsx +
+ Background +
+
+ Next.js Demo +
+
+ Starter repo +
+
+ Get started developing with Privy using our Next.js starter repo +
+ +
+
+``` + +### Authenticated Layout + +- Two-column: scrollable left content + fixed right sidebar +- Outer wrapper: `bg-[#E0E7FF66] md:max-h-[100vh] md:overflow-hidden` +- Left column: `flex-grow overflow-y-auto h-full p-4 pl-8` +- Right column: `UserObject` panel — `w-full md:w-[400px] bg-white border-l border-[#E2E3F0]` +- Content area offset by header: `pt-[60px]` + +```tsx +
+
+ +
+ {/* Section components go here */} +
+
+ +
+``` + +### Footer + +The starter does not include a footer. If adding one, keep it minimal and consistent with the neutral border/background system. + +--- + +## 8. Component Patterns + +### Buttons (3 variants from globals.css) + +| Class | Style | Shape | States | +|-------|-------|-------|--------| +| `.button-primary` | Solid `#5B4FFF`, white text | `rounded-md` | hover `#4A3EE6`, active `#3F35D9`, focus ring `#5B4FFF`, disabled `bg-gray-300` | +| `.button-outline` | Transparent, `#5B4FFF` border + text | `rounded-full` | hover/focus inverts to solid `#5B4FFF` + white text, active `#4A3EE6`, disabled `opacity-50` | +| `.button` | White bg, `#E2E3F0` border | `rounded-xl` | hover `bg-gray-50`, active `bg-gray-100`, disabled `opacity-50` | + +All buttons include `transition-colors` or `transition-all` at `duration-200 ease-in-out` and `disabled:cursor-not-allowed`. + +### Section (`components/reusables/section.tsx`) + +Reusable content section with title, file path badge, description, and action buttons: + +```tsx +interface IAction { + name: string; + function: () => void; + disabled?: boolean; +} + +interface ISection { + name: string; + description?: string; + filepath?: string; + actions: IAction[]; + children?: React.ReactNode; +} + +const Section = ({ name, description, filepath, actions, children }: ISection) => { + return ( +
+
+

{name}

+

+ @{filepath} +

+
+

{description}

+ + {children &&
{children}
} + +
+ {actions.map((action, index) => ( + + ))} +
+
+ ); +}; +``` + +### Badge (`components/ui/badge.tsx`) + +Variants: `default`, `success`, `warning`, `destructive`, `outline`. Uses a CSS class mapping (not CVA). Class joining via array `.join(" ")`: + +```tsx +type BadgeVariant = "default" | "success" | "warning" | "destructive" | "outline"; + +type BadgeProps = React.HTMLAttributes & { + variant?: BadgeVariant; +}; + +const variantToClasses: Record = { + default: "badge", + success: "badge badge-success", + warning: "badge badge-warning", + destructive: "badge badge-destructive", + outline: "badge badge-outline", +}; + +export function Badge({ variant = "default", className, ...props }: BadgeProps) { + return ( + + ); +} +``` + +### UserObject Panel (`components/sections/user-object.tsx`) + +Fixed-width right sidebar showing user JSON: + +```tsx +const UserObject = () => { + const { user } = usePrivy(); + return ( +
+

User object

+
+        {JSON.stringify(user, null, 2)}
+      
+
+ ); +}; +``` + +### FullScreen Loader (`components/ui/fullscreen-loader.tsx`) + +Uses `.loader-overlay` wrapper with an animated Privy blob SVG (72x72px, fill `#E1E4FF`): + +```tsx +export const FullScreenLoader = () => { + return ( +
+ + + + + + +
+ ); +}; +``` + +### Privy Logo (`components/ui/privy-logo.tsx`) + +SVG component with `useLightLogo` boolean prop. Light: `fill="#ffffff"`, Dark: `fill="#010110"`. ViewBox: `0 0 188 42`. + +```tsx +export function PrivyLogo( + props: { useLightLogo?: boolean } & React.SVGProps +) { + const fill = props.useLightLogo ? "#ffffff" : "#010110"; + return ( + + {/* Full SVG paths — see privy-next-starter/src/components/ui/privy-logo.tsx */} + + + + + + + + + ); +} +``` + +### Toast Notifications + +`react-toastify` with `` at top-center, positioned below the header: + +```tsx + +``` + +--- + +## 9. Icon System + +- **Package:** `@heroicons/react/16/solid` (small, filled icons) +- **Size:** `h-4 w-4` with `strokeWidth={2}` +- **Icons used:** + - `ArrowLeftIcon` — back navigation, logout + - `ArrowRightIcon` — forward CTA (dashboard button) + - `ArrowUpRightIcon` — external links (docs) + +--- + +## 10. Responsive Patterns + +- **Approach:** Mobile-first Tailwind +- **`md:` breakpoint:** Two-column layout, header CTA visibility +- **`lg:` breakpoint:** Button size scaling on hero + +| Element | Mobile | `md:` | `lg:` | +|---------|--------|-------|-------| +| Auth layout | `flex-col` | `flex-row` | — | +| Header dashboard button | `hidden` | `block` | — | +| Hero CTA | `px-4 py-2` | — | `px-8 py-4 text-xl` | +| Section header | `flex-col` | `flex-row items-center` | — | +| Container | Full-width with padding | — | — | + +No explicit `max-width` constraint on content areas. + +--- + +## 11. Dependencies + +### package.json (core dependencies) + +```json +{ + "dependencies": { + "@heroicons/react": "^2.2.0", + "@privy-io/react-auth": "^3.12.0", + "next": "15.5.7", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-toastify": "^11.0.5" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "tailwindcss": "^4", + "typescript": "^5" + } +} +``` + +Additional dependencies in the starter for Solana/Ethereum wallet functionality: `viem`, `@solana/kit`, `@solana-program/*`, `bs58`. Include these only if your demo needs on-chain interactions. + +--- + +## 12. Provider Setup Pattern + +```tsx +"use client"; + +import { PrivyProvider } from "@privy-io/react-auth"; +import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana"; + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +**Environment variable:** `NEXT_PUBLIC_PRIVY_APP_ID` — required, set in `.env`. + +--- + +## 13. Do's and Don'ts + +### Do + +- Use the 3 button classes from globals.css: `.button-primary`, `.button-outline`, `.button` +- Use `#5B4FFF` as the primary brand color everywhere +- Use Inter for body text, ABC Favorit for display headings and branded badges +- Use `#E2E3F0` for borders, `#E0E7FF` for highlight backgrounds +- Use `react-toastify` for notifications, `@heroicons/react` for icons +- Use Tailwind utility classes for layout and spacing +- Use `pnpm` as the package manager +- Use the `font-abc-favorit` Tailwind class for branded display text + +### Don't + +- Use shadcn/ui, @radix-ui, class-variance-authority, clsx, or tailwind-merge +- Add dark mode (not part of the design system) +- Use heavy shadows or complex gradients +- Create custom component abstractions — keep it simple with Tailwind + the 3 button classes +- Use `npm` or `yarn` — use `pnpm` + +--- + +## Source Reference + +All values extracted from [privy-next-starter](https://github.com/privy-io/examples/tree/main/privy-next-starter) at commit `38955b0`: + +| File | What's extracted | +|------|-----------------| +| `src/app/globals.css` | CSS variables, button classes, animations | +| `src/app/layout.tsx` | Font loading, body setup | +| `src/app/page.tsx` | Hero page, auth layout, toast config | +| `tailwind.config.js` | Font families, content paths | +| `package.json` | Dependencies | +| `src/components/ui/header.tsx` | Header component | +| `src/components/ui/badge.tsx` | Badge variants | +| `src/components/ui/fullscreen-loader.tsx` | Loader SVG | +| `src/components/ui/privy-logo.tsx` | Logo SVG with light/dark | +| `src/components/reusables/section.tsx` | Section layout pattern | +| `src/components/sections/user-object.tsx` | JSON sidebar panel | +| `src/providers/providers.tsx` | PrivyProvider config |