The codebase has many repeated Tailwind CSS patterns scattered across screens and components. This creates inconsistent UI (especially in dark mode) and violates the DRY principle. This plan introduces reusable base components and better organizes the components folder.
- Phase 1: Button Variants
- Phase 2: LinkButton Component
- Phase 3: ContentCard Component
- Phase 4: ListContainer Component
- Phase 5: SearchInput Component
- Phase 6: ModalButtons Component
- Phase 7: SegmentedControl Component
- Phase 8: PageTitle Component
- Phase 9: EmptyState Component
- Phase 10: Components Folder Reorganization
As you complete each phase, check it off above to mark it as done.
Goal: Extend the existing Button component to support multiple visual variants (primary, secondary, outline, danger) instead of having hardcoded button styles scattered throughout the codebase.
Problem: The current Button component only supports one style (primary blue). Many places use inline button styles:
ConfirmModal.tsx: Cancel (outline) and Delete (danger) buttons with inline stylesInputModal.tsx: Cancel (outline) and Save (primary) buttons with inline stylesVisibilityModal.tsx: Cancel (outline) and Save (primary) buttons with inline stylesDeckPreviewScreen.tsx/CoursePreviewScreen.tsx: "Log In" (primary) and "Sign Up" (outline) link buttonsDeckStatsScreen.tsx/AttemptHistoryScreen.tsx: "View attempt history" / "Back to stats" outline buttons with dark mode issues
Tasks:
- Add
variantprop toButton.tsxwith options:'primary' | 'secondary' | 'outline' | 'danger' - Add proper dark mode support to all variants
- Update all modals to use the new
Buttonvariants - Add
sizeprop:'sm' | 'md'for different contexts
Files to modify:
apps/react/src/components/Button.tsxapps/react/src/components/modals/ConfirmModal.tsxapps/react/src/components/modals/InputModal.tsxapps/react/src/components/modals/VisibilityModal.tsx
Acceptance criteria:
Buttonsupportsvariantprop with consistent styling- All button variants have proper dark mode support
- Modal buttons use the
Buttoncomponent instead of inline styles - Existing tests pass
Goal: Create a LinkButton component that renders a React Router Link styled like a button.
Problem: Multiple places style <Link> elements as buttons with repeated inline Tailwind classes:
DeckStatsScreen.tsx: "View attempt history" link (line 78)AttemptHistoryScreen.tsx: "Back to stats" link (line 156)DeckPreviewScreen.tsx: "Log In" and "Sign Up" links (lines 113-121)CoursePreviewScreen.tsx: "Log In" and "Sign Up" links (lines 102-110)
Tasks:
- Create
LinkButton.tsxthat wrapsLinkwith button styling - Support same variants as
Button(primary,secondary,outline,danger) - Include proper dark mode support (fixes the current dark mode issue)
- Replace inline styled
Linkelements withLinkButton
Files to create:
apps/react/src/components/LinkButton.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/DeckStatsScreen/DeckStatsScreen.tsxapps/react/src/screens/AttemptHistoryScreen/AttemptHistoryScreen.tsxapps/react/src/screens/DeckPreviewScreen.tsxapps/react/src/screens/CoursePreviewScreen.tsx
Acceptance criteria:
LinkButtonrenders correctly with all variants- Dark mode styling works correctly
- All inline styled
Linkbuttons are replaced - Existing tests pass
Goal: Create a ContentCard component for the repeated white card pattern used throughout preview screens.
Problem: The pattern bg-white rounded-lg shadow p-6 space-y-4 is repeated 8+ times:
DeckPreviewScreen.tsx: 4 instances (lines 70, 81, 109, 129)CoursePreviewScreen.tsx: 4 instances (lines 57, 68, 90, 96)
Tasks:
- Create
ContentCard.tsxwith props for spacing variants (space-y-3,space-y-4) - Support optional
centeredprop for text-center content - Add dark mode background support
- Replace repeated patterns in preview screens
Files to create:
apps/react/src/components/ContentCard.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/DeckPreviewScreen.tsxapps/react/src/screens/CoursePreviewScreen.tsx
Acceptance criteria:
ContentCardreplaces allbg-white rounded-lg shadow p-6patterns- Dark mode styling works
- Preview screens are simplified
- Existing tests pass
Goal: Create a ListContainer component for divided list layouts.
Problem: The pattern bg-white rounded-lg shadow divide-y divide-gray-100 is repeated:
CommunityScreen.tsx: 3 instances (lines 65, 150, 177) for top decks, deck results, and course results
Tasks:
- Create
ListContainer.tsxthat wraps children with the divided list styling - Support both clickable items (with hover) and static items
- Add dark mode support
Files to create:
apps/react/src/components/ListContainer.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/CommunityScreen.tsx
Acceptance criteria:
ListContainerreplaces repeated patterns- Hover states work correctly
- Dark mode styling works
- Existing tests pass
Goal: Create a reusable search input component.
Problem: The search input in CommunityScreen.tsx (line 117-122) uses a long inline style that could be reused and doesn't match the existing BaseInput styling.
Tasks:
- Create
SearchInput.tsxthat extendsBaseInputwith search-specific styling - Include optional search icon
- Ensure consistent styling with other inputs
Files to create:
apps/react/src/components/inputs/SearchInput.tsx
Files to modify:
apps/react/src/components/inputs/index.tsapps/react/src/screens/CommunityScreen.tsx
Acceptance criteria:
SearchInputis used inCommunityScreen- Styling is consistent with other form inputs
- Dark mode works
- Existing tests pass
Goal: Extract the modal footer button layout into a reusable component.
Problem: All modals (ConfirmModal, InputModal, VisibilityModal) repeat the same footer pattern:
<div className="mt-8 sm:flex sm:pl-4 gap-3 justify-end bg-gray-50 px-6 pt-3 pb-4 border-t border-gray-100">Tasks:
- Create
ModalButtons.tsxcomponent that handles the footer layout - Accept
onCancel,onConfirm,confirmText,cancelText,confirmVariantprops - Use
Buttoncomponent internally with appropriate variants - Update all modals to use
ModalButtons
Files to create:
apps/react/src/components/modals/ModalButtons.tsx
Files to modify:
apps/react/src/components/modals/ConfirmModal.tsxapps/react/src/components/modals/InputModal.tsxapps/react/src/components/modals/VisibilityModal.tsx
Acceptance criteria:
ModalButtonsreduces code duplication in modals- All three modals use the new component
- Button variants (danger for delete, primary for save) are correct
- Existing tests pass
Goal: Create a proper segmented control wrapper component.
Problem: The SegmentButton component exists but the container styling is inline:
CommunityScreen.tsx:<div className="flex gap-2 p-1 bg-gray-100 rounded-lg">(line 103)AttemptHistoryScreen.tsx: Toggle between "Text" and "Sheet music" views uses different inline styles (line 129)
Tasks:
- Create
SegmentedControl.tsxwrapper that handles the container styling - Update
SegmentButtonto work seamlessly withinSegmentedControl - Support dark mode with proper background colors
- Replace inline implementations
Files to create:
apps/react/src/components/SegmentedControl.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/CommunityScreen.tsxapps/react/src/screens/AttemptHistoryScreen/AttemptHistoryScreen.tsx
Acceptance criteria:
SegmentedControlwrapsSegmentButtonchildren- Consistent styling across all usages
- Dark mode support
- Existing tests pass
Goal: Create a consistent page title component.
Problem: Page titles have inconsistent styling:
AttemptHistoryScreen.tsx:<h1 className="text-xl font-semibold">(line 127)DeckPreviewScreen.tsx:<h2 className="text-xl font-semibold text-gray-900">(line 71)CoursePreviewScreen.tsx:<h2 className="text-xl font-semibold text-gray-900">(line 58)
Tasks:
- Create
PageTitle.tsxwithh1,h2,h3variants - Include consistent text color with dark mode support
- Replace inline title styles
Files to create:
apps/react/src/components/PageTitle.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/AttemptHistoryScreen/AttemptHistoryScreen.tsxapps/react/src/screens/DeckPreviewScreen.tsxapps/react/src/screens/CoursePreviewScreen.tsx
Acceptance criteria:
PageTitleprovides consistent heading styles- Dark mode text colors are correct
- Preview screens and attempt history use the component
- Existing tests pass
Goal: Create a consistent empty state message component.
Problem: Empty state messages are styled inconsistently:
CommunityScreen.tsx:<p className="text-center text-gray-500 py-8">No decks found</p>(lines 146, 173)AttemptHistoryScreen.tsx:<div className="rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-700">No attempts yet.</div>(line 72)StudyScreenEmptyState.tsx: Uses different styling altogether
Tasks:
- Create
EmptyState.tsxcomponent with consistent styling - Support optional icon and action button
- Include dark mode support
- Replace inline empty state messages
Files to create:
apps/react/src/components/EmptyState.tsx
Files to modify:
apps/react/src/components/index.tsapps/react/src/screens/CommunityScreen.tsxapps/react/src/screens/AttemptHistoryScreen/AttemptHistoryScreen.tsx
Acceptance criteria:
EmptyStateprovides consistent empty state UI- Dark mode works correctly
- All simple empty state messages use the component
- Existing tests pass
Goal: Organize the components folder into logical subfolders for better discoverability.
Problem: The components folder is flat with 40+ files, making it hard to find related components.
Tasks:
- Create subfolder structure:
components/ui/- Base UI components (Button, LinkButton, Pill, Card, ContentCard, etc.)components/forms/- Renameinputs/for clarity, add SearchInputcomponents/layout/- Layout, SectionCard, SectionData, SectionHeader, etc.components/feedback/- Spinner, Toast, ErrorCard, EmptyState- Keep existing subfolders:
modals/,navigation/,notation/,FlashCards/
- Update
index.tsexports - Update imports throughout the codebase
Folder structure:
components/
ui/
Button.tsx
LinkButton.tsx
Card.tsx
ContentCard.tsx
ListContainer.tsx
Pill.tsx
CircleHover.tsx
SegmentButton.tsx
SegmentedControl.tsx
PageTitle.tsx
forms/ (renamed from inputs/)
BaseInput.tsx
InputField.tsx
EmailInput.tsx
PasswordInput.tsx
SearchInput.tsx
Select.tsx
Checkbox.tsx
NumberInput.tsx
DurationSelect.tsx
layout/
Layout.tsx
SectionCard.tsx
SectionData.tsx
SectionHeader.tsx
SegmentHeader.tsx
feedback/
Spinner.tsx
Toast.tsx
ErrorCard.tsx
EmptyState.tsx
modals/ (existing)
navigation/ (existing)
notation/ (existing)
FlashCards/ (existing)
index.ts
Files to modify:
- Multiple component files (move to new locations)
apps/react/src/components/index.ts- All files importing from components
Acceptance criteria:
- Components are organized into logical subfolders
- All imports still work correctly
index.tsexports maintain backward compatibility- Existing tests pass
- Consistency: All buttons, cards, and UI elements will have consistent styling
- Dark Mode: Proper dark mode support throughout the app
- Maintainability: Style changes only need to happen in one place
- Discoverability: Organized folder structure makes finding components easier
- DRY Code: Eliminates ~200+ lines of repeated Tailwind classes
- Type Safety: All components properly typed with full prop documentation