|
| 1 | +## Icons |
| 2 | + |
| 3 | +- MUST use `@untitledui/icons` for ALL icons — never use lucide-react, heroicons, or any other icon package |
| 4 | +- MUST import icons as React components: `import { SearchLg, Check } from "@untitledui/icons"` |
| 5 | +- NEVER use inline `<svg>` for standard icons — always use the Untitled UI component |
| 6 | +- Size icons via className: `<SearchLg className="w-4 h-4" />` |
| 7 | +- Only exception for inline SVG: custom illustrations, animated graphics, or the product logo |
| 8 | + |
| 9 | +## Colors |
| 10 | + |
| 11 | +- ALL color values MUST use oklch() format — never hex, rgb, or hsl |
| 12 | +- This applies to CSS variables, inline values, and any new tokens |
| 13 | +- Base design tokens are defined in `src/styles/globals.css` using oklch() |
| 14 | +- When adding a new color, convert it to oklch first |
| 15 | +- Use existing theme tokens (e.g., `text-foreground`, `bg-card`) before introducing new values |
| 16 | + |
| 17 | +## Component Rules |
| 18 | + |
| 19 | +- MUST use shadcn/ui components as the base — never rebuild primitives |
| 20 | +- MUST add `aria-label` to icon-only buttons |
| 21 | +- NEVER mix component primitive systems in the same surface |
| 22 | +- NEVER rebuild keyboard or focus behavior by hand |
| 23 | + |
| 24 | +## Accessibility |
| 25 | + |
| 26 | +- All interactive elements must be keyboard accessible |
| 27 | +- WCAG AA minimum: 4.5:1 contrast for body text, 3:1 for large text and UI components |
| 28 | +- Never use color alone to convey information — pair with icons or text |
| 29 | +- Every image needs `alt` text; decorative images get `alt=""` |
| 30 | +- Use semantic HTML elements (`nav`, `main`, `section`, `button`) — not divs with click handlers |
| 31 | +- MUST use `AlertDialog` for destructive/irreversible actions |
| 32 | + |
| 33 | +## Responsiveness |
| 34 | + |
| 35 | +- Mobile-first: base styles for mobile, `min-width` queries to layer complexity |
| 36 | +- NEVER use `h-screen` — use `h-dvh` instead |
| 37 | +- MUST respect `safe-area-inset` for fixed/sticky elements |
| 38 | +- Use `clamp()` for fluid spacing and font sizes where appropriate |
| 39 | +- Touch targets must be at least 44x44px on touch devices |
| 40 | + |
| 41 | +## Design Quality |
| 42 | + |
| 43 | +- NEVER use gradients unless explicitly requested |
| 44 | +- NEVER use glow effects as primary affordances |
| 45 | +- NEVER use purple or multicolor gradients |
| 46 | +- Use Tailwind default shadow scale — no arbitrary shadow values |
| 47 | +- Limit accent color to one per view |
| 48 | +- Use existing theme/Tailwind color tokens before introducing new hex values |
| 49 | +- `tabular-nums` for all numeric data displays |
| 50 | +- `text-balance` for headings, `text-pretty` for body text |
| 51 | +- Use `truncate` or `line-clamp` for dense UI |
| 52 | + |
| 53 | +## Animation |
| 54 | + |
| 55 | +- NEVER add animation unless explicitly requested |
| 56 | +- Animate only compositor props (`transform`, `opacity`) |
| 57 | +- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`) |
| 58 | +- Never exceed 200ms for interaction feedback |
| 59 | +- MUST respect `prefers-reduced-motion` |
| 60 | + |
| 61 | +## Resizable Panels |
| 62 | + |
| 63 | +- Use CSS-based drag handlers (mousedown → document mousemove/mouseup) — NOT `react-resizable-panels` |
| 64 | +- Each resizable panel needs: min width, max width, collapse threshold, default width constants |
| 65 | +- Store cleanup functions in a ref and call them on unmount to prevent listener leaks |
| 66 | +- Set `document.body.style.cursor = "col-resize"` and `document.body.style.userSelect = "none"` during drag |
| 67 | +- Drag handle: `w-px bg-border hover:bg-primary/50 cursor-col-resize` with `after:` pseudo-element for larger hit area |
| 68 | +- Auto-collapse: when dragged below threshold, set panel open state to false and render width 0 |
| 69 | +- Conditionally render panel content only when open to avoid layout of hidden elements |
| 70 | + |
| 71 | +## Tooltips |
| 72 | + |
| 73 | +- `TooltipProvider` wraps the entire app in `src/App.tsx` — do not add additional providers |
| 74 | +- MUST wrap every icon-only button in a `Tooltip` with a descriptive `TooltipContent` |
| 75 | +- Use `side="bottom"` for toolbar buttons, `side="top"` for bottom-positioned actions |
| 76 | + |
| 77 | +## shadcn Component Usage |
| 78 | + |
| 79 | +- NEVER override shadcn component styles with inline className when a variant or size prop exists |
| 80 | +- Use standard `variant` and `size` props: `<Button variant="outline" size="sm">` — not `className="text-[12px] gap-1.5"` |
| 81 | +- `TabsTrigger` and `ToggleGroupItem` should use default styles — override only `data-[state=on]` or `data-[state=active]` when needed |
| 82 | +- When multiple toggle/filter elements share a toolbar, use consistent active state styling across all of them |
0 commit comments