Style Guide
Code standards and conventions for On Book Pro development.
Code Principles
1. Primitive Conformity
All UI elements should use design system primitives from src/components/common/:
Button— All clickable actionsInput,Select,Textarea— All form elementsModal,ModalHeader,ModalFooter— All dialogsCard— Content containers with consistent shadowsTabNavigation,SubtabSwitcher,SidebarTabs— All navigation
❌ Don't: Use raw <button>, <input>, or <select> elements
✅ Do: Import and use the primitive component
Button Icon Support (January 2026)
Buttons with icons should use the leftIcon or rightIcon props:
// ✅ Good: Use icon props
<Button leftIcon="add" onClick={handleAdd}>Add Item</Button>
<Button rightIcon="arrow_forward">Continue</Button>
// ❌ Bad: Manual icon composition
<Button><Icon name="add" /> Add Item</Button>Decorator Patterns
FloatingToolbar automatically decorates child buttons with floating-specific styles:
// Child buttons get pill styling + shadows automatically
<FloatingToolbar position="bottom-right">
<Button leftIcon="edit" onClick={handleEdit}>Edit</Button>
<Button leftIcon="delete" variant="danger">Delete</Button>
</FloatingToolbar>2. Branded Types
Use nominal types for semantic ID safety:
// ❌ Don't
function getNote(id: string) { ... }
// ✅ Do
import { NoteId } from '@/types/branded';
function getNote(id: NoteId) { ... }3. File Organization
Features: Domain-specific logic lives in src/features/<feature>/
- Each feature is self-contained
- Export a single public API from
index.ts
Components: Reusable UI primitives live in src/components/common/
- Fully generic (no domain logic)
- Compose from Tailwind utilities
4. Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Components | PascalCase | BlockingTracker.tsx |
| Hooks | camelCase with use prefix | useBlockingState.ts |
| Types | PascalCase | BlockingEvent |
| Constants | SCREAMING_SNAKE_CASE | MAX_UPLOAD_SIZE |
| Functions | camelCase | calculateDistance() |
5. TypeScript Standards
- Strict mode enabled: No implicit
any - Explicit return types for public functions
- Branded types for IDs and timestamps
- Zod schemas for external data validation
6. State Management
Use Zustand slices with Immer:
// ✅ Good: Mutation via Immer
set((state) => {
state.notes[id].content = newContent;
});
// ❌ Bad: Manual spreading
set({ notes: { ...state.notes, [id]: { ...state.notes[id], content: newContent }}});7. Accessibility
- All interactive elements must be keyboard accessible
- Images require
altattributes - Color contrast ratio ≥ 4.5:1
- Semantic HTML (
<nav>,<main>,<section>)
8. Animation Conventions
Use the motion package (Framer Motion) for all enter/exit animations, layout transitions, and spring physics. Do not add new CSS @keyframes for UI transitions.
// ✅ Good: Use useMotionConfig presets
const { overlayTransition } = useMotionConfig();
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={overlayTransition} />
// ❌ Bad: Raw CSS keyframes for enter/exit
<div className="animate-fade-in" />When to use which mechanism:
| Mechanism | Use Case |
|---|---|
AnimatePresence + motion.div | Components that mount/unmount (modals, toasts, dropdowns) |
layoutId | Shared element transitions between two states (Barry FAB → panel) |
layout prop | Auto-animating size/position changes (sidebar collapse, accordion) |
useMotionConfig presets | Always — never hardcode spring/tween values |
Exit animations: Keep exits fast (≤ 200ms). If a component uses layoutId, exit its children instantly to avoid competing animations.
Git Workflow
Commit Messages
Follow conventional commits:
feat: add blocking path visibility toggle
fix: resolve sync race condition in presence
docs: update setup guide with Node 20 requirementBranch Strategy
main— Productionrefactor/react-migration— Active developmentfeature/your-feature— Feature branches
For more technical patterns, see the Developer Documentation.