Dark mode with Tailwind and localStorage persistence

Dark mode improves usability in low-light conditions and reduces eye strain. I implement theme switching with Tailwind's dark: variant and CSS custom properties for colors. The theme preference persists in localStorage and syncs across tabs with stora

React Hook Form with Zod validation

Combining React Hook Form with Zod provides type-safe form validation with a schema-first approach. Zod schemas define the shape and rules for form data, and the zodResolver bridges them to React Hook Form. TypeScript infers form types from schemas au

SEO optimization with React Helmet

SPAs struggle with SEO because content loads via JavaScript after the initial HTML. React Helmet manages document head tags like title, meta descriptions, and Open Graph tags per route. Each page component declares its own metadata, and Helmet ensures

Virtual scrolling for large lists with react-window

Rendering thousands of list items kills performance. Virtual scrolling renders only visible items plus a buffer, dramatically reducing DOM nodes. The react-window library provides FixedSizeList and VariableSizeList components that handle viewport calc

Markdown rendering with react-markdown

Rich text content from Rails often comes as Markdown. The react-markdown library renders Markdown to React components with customizable styling and sanitization. I configure it to use syntax highlighting via react-syntax-highlighter for code blocks, c

React Testing Library for component tests

Testing Library encourages testing components from the user's perspective rather than implementation details. I query elements by accessible labels, text content, or roles—not by CSS classes or test IDs. User interactions use userEvent to simulate rea

Zustand for lightweight state management

Zustand provides a minimalist alternative to Redux with less boilerplate and better TypeScript support. I create stores with create that hold state and actions. Unlike Context, Zustand doesn't cause unnecessary re-renders—components only update when t

Lazy loading routes with React.lazy and Suspense

Code splitting routes reduces initial bundle size and improves load times. React's lazy function dynamically imports components when routes are accessed. Wrapped in Suspense, lazy components show fallback UI while loading. I group related routes into

Compound components pattern for flexible APIs

Compound components create flexible, composable APIs by sharing state between parent and child components via context. Instead of passing dozens of props, child components access shared state through context. A Select component might have Select.Trigg

Image upload with preview and Rails Direct Upload

Direct uploads to cloud storage bypass the Rails server, improving performance and reducing server load. I use ActiveStorage's Direct Upload feature to get presigned URLs from Rails, then upload files directly to S3 from the browser. The React compone

Toast notifications system

Toast notifications provide non-intrusive feedback for user actions. I create a toast context that manages a queue of notifications, auto-dismisses them after a timeout, and supports different types (success, error, info). The context exposes methods

Modal dialogs with focus trapping and accessibility

Accessible modals require focus management, keyboard navigation, and ARIA attributes. I build a reusable Modal component that traps focus inside when open, moves focus to the first interactive element, and returns it to the trigger on close. The Escap