` needs `role="button"`, `tabIndex={0}`, `onKeyDown`
- Handle Enter and Space keys
- **ARIA attributes** - `aria-selected`, `aria-current`, `aria-expanded`, `aria-pressed`
- **Never remove focus rings** - Must be color-specific and static
- **Icon-only buttons MUST have aria-label** - Required for screen readers
- **Toggle buttons MUST have aria-pressed** - Indicates current state
- **Collapsible controls MUST have aria-expanded** - Indicates expanded/collapsed state
- **Decorative icons MUST have aria-hidden="true"** - Prevents screen reader announcement
### Anti-Patterns
```tsx
// ❌ Clickable div without keyboard
// Missing onKeyDown!
// ❌ Clickable icon without button wrapper
// ❌ Icon-only button without aria-label
// Screen reader has no idea what this does!
// ❌ Toggle button without aria-pressed
// No indication of current state!
// ❌ Expandable control without aria-expanded
setExpanded(!expanded)}>
// Screen reader doesn't know if expanded or collapsed!
// ❌ Icon without aria-hidden
// Screen reader announces both "Delete" AND icon details!
```
### Good Examples
```tsx
// ✅ Full keyboard support on div
{
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handler();
}
}}
aria-selected={isSelected}
>
// ✅ Clickable icon wrapped in button
{
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handler();
}
}}
className="focus:outline-none focus:ring-2"
>
// ✅ Icon-only button with proper aria-label and aria-hidden
// ✅ Toggle button with aria-pressed
setViewMode("grid")}
aria-label="Grid view"
aria-pressed={viewMode === "grid"}
>
// ✅ Expandable control with aria-expanded
setSidebarExpanded(false)}
aria-label="Collapse sidebar"
aria-expanded={sidebarExpanded}
>
```
### Automated Scans
```bash
# Interactive divs without keyboard
grep -rn "onClick.*role=\"button\"" [path] --include="*.tsx"
# Then manually verify onKeyDown exists
# Icons with onClick (should be wrapped in button)
grep -rn "<[A-Z].*onClick={" [path] --include="*.tsx" | grep -v "
` with proper ARIA attributes
- Icon-only buttons: Add `aria-label="Descriptive action"`
- Toggle buttons: Add `aria-pressed={isActive}`
- Expandable controls: Add `aria-expanded={isExpanded}`
- Icons in labeled buttons: Add `aria-hidden="true"`
---
## 7. TYPESCRIPT & API CONTRACTS
### Rules
- **Async functions return Promise** - Not just `void` if awaited
- **All props must be used** - If prop in interface, must affect rendering
- **Color types consistent** - Use "green" not "emerald" across components (avoid emerald entirely)
- **Run `tsc --noEmit`** to catch type errors
- **Use `satisfies` for lookup objects** - Enforce type coverage on color variants
- **120 character line limit** - Split long class strings into arrays with `.join(" ")`
### Anti-Patterns
```tsx
// ❌ Async typed as void
setEnabled: (val: boolean) => void; // But implemented as async!
// ❌ Unused prop
interface CardProps { glowColor?: string }
return {/* glowColor never used! */}
// ❌ Color type mismatch
// PillNavigation: colorVariant?: "emerald"
// Select: color?: "green" // Should match!
// ❌ Long class strings (exceeds 120 chars)
const activeClasses = {
blue: "data-[state=active]:bg-blue-500/20 dark:data-[state=active]:bg-blue-400/20 data-[state=active]:text-blue-700 dark:data-[state=active]:text-blue-300 data-[state=active]:border data-[state=active]:border-blue-400/50",
};
// ❌ Lookup objects without type safety
const colorClasses = {
cyan: "bg-cyan-500",
blue: "bg-blue-500",
// What if we forget purple? No compile error!
};
```
### Good Examples
```tsx
// ✅ Correct async type
setEnabled: (val: boolean) => Promise
;
// ✅ All props used
interface CardProps { glowColor?: string }
const glow = glassCard.variants[glowColor];
return
// ✅ Consistent color types (always use "green", never "emerald")
type Color = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
// ✅ Split long class strings (under 120 chars per line)
const activeClasses = {
blue: [
"data-[state=active]:bg-blue-500/20 dark:data-[state=active]:bg-blue-400/20",
"data-[state=active]:text-blue-700 dark:data-[state=active]:text-blue-300",
"data-[state=active]:border data-[state=active]:border-blue-400/50",
].join(" "),
};
// ✅ Type-safe lookup objects with satisfies
type Color = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
const colorClasses = {
purple: "bg-purple-500",
blue: "bg-blue-500",
cyan: "bg-cyan-500",
green: "bg-green-500",
orange: "bg-orange-500",
pink: "bg-pink-500",
} satisfies Record; // TypeScript enforces all colors present!
```
### Automated Scans
```bash
# TypeScript errors
npx tsc --noEmit [path] 2>&1 | grep "error TS"
# Color type inconsistencies (must use "green" not "emerald")
grep -rn "emerald" [path] --include="*.tsx" --include="*.ts"
# Line length violations (over 120 chars)
grep -rn ".\{121,\}" [path] --include="*.tsx" | grep className
# Lookup objects without satisfies
grep -rn "const.*Classes = {" [path]/primitives --include="*.tsx" -A 5 | grep -v "satisfies"
# Unused props (manual)
grep -rn "interface.*Props" [path]/primitives --include="*.tsx" -A 10
# Then verify each prop name appears in return statement
```
**Fix Pattern**:
- Split long strings into arrays: `["class1", "class2"].join(" ")`
- Add `satisfies Record` to lookup objects
- Replace all "emerald" with "green" + update RGB to green-500 (34,197,94)
- Wire all props to rendering
---
## 8. FUNCTIONAL LOGIC
### Rules
- **Interactive UI must be functional** - Especially in demos/examples
- **State changes must affect rendering**
- Filter state → must filter data before .map()
- Sort state → must sort data
- Drag-drop → must have state + onDrop handler
- **Props must do what they advertise** - If edgePosition accepts "left", it must work
### Anti-Patterns
```tsx
// ❌ Filter that doesn't filter
const [filter, setFilter] = useState("all");
return {items.map(...)}
// items not filtered!
// ❌ Drag-drop without state
{[1,2,3].map(num => )} // Always snaps back!
// ❌ Prop that does nothing
edgePosition="left" // But only "top" is implemented!
```
### Good Examples
```tsx
// ✅ Working filter
const [filter, setFilter] = useState("all");
const filtered = useMemo(() =>
filter === "all" ? items : items.filter(i => i.type === filter),
[items, filter]);
return {filtered.map(...)}
// ✅ Working drag-drop
const [items, setItems] = useState([...]);
const handleDrop = (id, index) => { /* reorder logic */ };
return <>{items.map((item, i) => )}>
// ✅ All edge positions work
if (edgePosition === "top") { /* top impl */ }
if (edgePosition === "left") { /* left impl */ }
// etc for all accepted values
```
### Manual Checks
```bash
# Look for state that never affects rendering
# Pattern: setState called but variable not used in .filter/.sort/.map
# Check prop implementations
# Pattern: Interface accepts values but switch/if only handles subset
```
**Fix Pattern**: Wire state to data transformations (filter/sort/map), add missing implementations
---
## AUTOMATED SCAN REFERENCE
Run ALL these scans during review:
### Critical (Breaking)
- Dynamic classes: `grep -rn "className.*\`.*\${.*}\`\|bg-\${.*}\|ring-\${.*}" [path]`
- Non-responsive grids: `grep -rn "grid-cols-[2-9]" [path] | grep -v "md:\|lg:"`
- Unconstrained scroll: `grep -rn "overflow-x-auto" [path]` (verify w-full parent)
- Native HTML: `grep -rn "\|type=\"checkbox\"" [path]`
- Emerald usage: `grep -rn "emerald" [path] --include="*.tsx" --include="*.ts"` (must use "green")
### High Priority
- Missing keyboard: `grep -rn "onClick.*role=\"button\"" [path]` (verify onKeyDown)
- Clickable icons: `grep -rn "<[A-Z].*onClick={" [path] --include="*.tsx" | grep -v "&1 | grep "error TS"`
- Line length: `grep -rn ".\{121,\}" [path] --include="*.tsx" | grep className`
- Missing satisfies: `grep -rn "const.*Classes = {" [path]/primitives -A 5 | grep -v "satisfies"`
- Props unused: Manual check interfaces vs usage
---
## QUICK REFERENCE
### Breakpoints
- sm: 640px | md: 768px | lg: 1024px | xl: 1280px | 2xl: 1536px
### Color Variant Checklist (for primitives with colors)
Every color object MUST have:
- [ ] `checked` or `active` state classes
- [ ] `glow` effect
- [ ] `focusRing` - STATIC class like `"focus-visible:ring-cyan-500"`
- [ ] `hover` state
- [ ] All 6 colors: purple, blue, cyan, green, orange, pink
### Common Patterns
**Horizontal Scroll (Archon Standard)**
```tsx
{items.map(i =>
)}
```
**Responsive Grid**
```tsx
```
**Flex + Scroll Container**
```tsx
{/* min-w-0 REQUIRED */}
{/* scroll containers here */}
```
**Color Variants (Static Lookup)**
```tsx
const variants = {
cyan: {
checked: "data-[state=checked]:bg-cyan-500/20",
glow: "shadow-[0_0_15px_rgba(34,211,238,0.5)]",
focusRing: "focus-visible:ring-cyan-500", // STATIC!
hover: "hover:bg-cyan-500/10",
},
// ... repeat for all colors
};
```
**Keyboard Support**
```tsx
(e.key === "Enter" || e.key === " ") && handler()}
aria-selected={isSelected}
>
```
---
## SCORING VIOLATIONS
### Critical (-3 points each)
- Dynamic class construction
- Missing keyboard support on interactive
- Non-responsive grids causing horizontal scroll
- TypeScript errors
### High (-2 points each)
- Unconstrained scroll containers
- Props that do nothing
- Non-functional UI logic (filter/sort/drag-drop)
- Missing dark mode variants
### Medium (-1 point each)
- Native HTML form elements
- Hardcoded glassmorphism
- Missing text truncation
- Color type inconsistencies
**Grading Scale:**
- 0 critical violations: A (9-10/10)
- 1 critical: B (7-8/10)
- 2-3 critical: C (5-6/10)
- 4+ critical: F (1-4/10)
---
## ADDING NEW RULES
When code review finds an issue not caught by automated review:
1. **Identify which section** it belongs to (Tailwind? Layout? A11y?)
2. **Add to that section**:
- Rule (what to do)
- Anti-Pattern example
- Good example
- Automated scan (if possible)
3. **Add scan to AUTOMATED SCAN REFERENCE**
4. **Done** - Next review will catch it
**Goal**: Eventually eliminate manual code reviews entirely.