# Pitfalls Research **Domain:** UI polish overhaul -- Material Design 3, dark mode, accent colors added to existing Tailwind v4 + React wizard app **Researched:** 2026-03-31 **Confidence:** HIGH (based on codebase analysis + verified Tailwind v4 docs + community patterns) --- ## Critical Pitfalls ### Pitfall 1: Tailwind v4 Dark Mode Requires CSS-First Config, Not tailwind.config.js **What goes wrong:** Developers reach for `tailwind.config.js` with `darkMode: 'class'` which does not exist in Tailwind v4. The app currently has only `@import "tailwindcss";` in `index.css` with no config file at all. Using v3 dark mode patterns produces zero effect and wastes debugging time. **Why it happens:** Most tutorials and Stack Overflow answers still reference Tailwind v3 syntax. Tailwind v4 moved to a fully CSS-first configuration model. The `darkMode` config key is gone. **How to avoid:** Add the `@custom-variant` directive in `index.css` for class-based toggling: ```css @import "tailwindcss"; @custom-variant dark (&:where(.dark, .dark *)); ``` This enables manual toggle via a `.dark` class on ``. The `:where()` wrapper keeps specificity at zero, preventing cascade conflicts. Verified against [Tailwind v4 dark mode docs](https://tailwindcss.com/docs/dark-mode). **Warning signs:** - `dark:` prefixed classes have no visible effect - Dark mode only responds to OS preference, not the toggle button **Phase to address:** Phase 1 (Theme Foundation) -- this must be the very first CSS change before any `dark:` classes are added to components. --- ### Pitfall 2: Hardcoded Color Values Across 73 className Usages **What goes wrong:** The codebase has 73 `className=` usages with hardcoded Tailwind color classes (`text-gray-700`, `border-gray-300`, `focus:ring-blue-300`, `text-red-500`, `bg-gray-50`, etc.). Adding dark mode by appending a `dark:` counterpart to every single one creates unreadable className strings and guarantees missed spots -- invisible text, invisible borders, or unreadable error messages against dark backgrounds. **Why it happens:** When building light-mode-only, hardcoded color classes are natural. The cost is deferred until dark mode arrives. Developers add `dark:` to the visible components and miss the less-obvious ones (help text, error messages, placeholders, disabled states). **How to avoid:** Define semantic CSS custom properties (design tokens) mapped to Tailwind's `@theme` directive: ```css @theme { --color-surface: #ffffff; --color-on-surface: #1a1a1a; --color-primary: #2563eb; --color-error: #dc2626; } ``` Then use `bg-surface`, `text-on-surface` throughout components. Dark mode changes the token values once (on `.dark`), not every component. This is the Material Design 3 approach (surface, on-surface, primary, on-primary, etc.). **Warning signs:** - `dark:` classes appearing in JSX alongside light classes, creating 100+ character className strings - Text disappearing on dark backgrounds during manual testing - Error messages (`text-red-600`) becoming unreadable against dark backgrounds **Phase to address:** Phase 1 (Theme Foundation) defines tokens. Phase 2 (Component Overhaul) replaces hardcoded colors with token references. --- ### Pitfall 3: Dark Mode Color Contrast Failures (WCAG AA) **What goes wrong:** Text that passes 4.5:1 contrast in light mode fails in dark mode. The most common failures: gray help text on dark gray backgrounds, red error text on dark surfaces, blue links on dark blue-gray backgrounds. The current app uses `text-gray-500` for help text and `text-red-600` for errors -- both will fail against typical dark backgrounds. **Why it happens:** Developers assume inverting colors preserves contrast ratios. They do not. Concrete example from this codebase: `text-gray-500` (#6b7280) on `bg-white` (#ffffff) gives 4.6:1 contrast -- barely passing AA. The same `text-gray-500` on `bg-gray-900` (#111827) gives only 3.5:1 -- failing AA for normal text. **How to avoid:** Define separate color values per theme within the token system. In dark mode, help text must use a lighter gray (equivalent of `text-gray-400`), errors must use a lighter red (equivalent of `text-red-400`). Semantic tokens centralize these mappings so each value is defined once. Verify every text/background pair with the browser DevTools accessibility panel or WebAIM contrast checker against WCAG AA 4.5:1 minimum for normal text, 3:1 for large text. **Warning signs:** - Help text feels "hard to read" in dark mode during visual review - Browser DevTools accessibility audit flagging contrast ratios below 4.5:1 - Error states visually blending into background colors **Phase to address:** Phase 1 (Token Definition) for color values. Phase 2 (Component Overhaul) for application. Each component restyle must include a contrast verification before marking complete. --- ### Pitfall 4: Breaking 131 Test Selectors During Component Restyling **What goes wrong:** The test suite uses 131 occurrences of `getByText`, `getByRole`, `getByTestId`, `getByLabelText`, and `queryBy` selectors across 5 test files (App.test.tsx, StepIndicator.test.tsx, ReviewStep.test.tsx, RemoteConfigStep.test.tsx, BackendSelectionStep.test.tsx). Restyling components breaks tests by: changing visible text content, wrapping elements in new containers that alter DOM hierarchy, replacing native elements with styled equivalents (changing roles), or removing/renaming aria attributes. **Why it happens:** UI overhauls touch the same JSX that tests query. Specific examples from this codebase: - `getByText(/Backend/)` in StepIndicator.test.tsx breaks if the label text changes or gets wrapped in a `` that splits the text node - `getAllByRole('button')` breaks if buttons become styled `` tags or `
` elements - `screen.findByText('2', { selector: '[data-testid="step"]' })` breaks if data-testid attributes are renamed during refactoring **How to avoid:** 1. Run the full 159-test suite after every single component change, not in a batch at the end. 2. Restyle one component, verify tests, commit. Never batch-restyle all components then fix all tests. 3. When restructuring JSX, preserve text content and element roles. A `; } ``` Only the toggle button re-renders. No context, no provider, no cascade. Accent color works the same way -- set a CSS variable on ``, no React re-render. **Warning signs:** - Form inputs losing focus when toggling dark mode - Visible flicker across the entire wizard when toggling - React DevTools profiler showing all components re-rendering on theme change **Phase to address:** Phase 1 (Theme Foundation) -- architecture decision: CSS class approach, not React Context for theming. --- ### Pitfall 7: Form Accessibility Regressions During Restyling **What goes wrong:** The current FieldRenderer has proper `