10 KiB
Bill Tracker UI Improvements
Overview
This document catalogs UI/UX improvements identified across the Bill Tracker codebase, organized by priority and impact.
CRITICAL
No critical issues found. Core functionality is solid.
HIGH
1. Mobile Layout Overflow in Sidebar Navigation
Where: client/components/layout/Sidebar.jsx — Mobile menu overlay
Why it matters: On small screens, the mobile navigation menu doesn't adapt to content width, causing horizontal scroll or content cutoff. This is a blocking accessibility issue for mobile users.
Current behavior:
- Mobile menu uses fixed max-width container
- Nav items with long text (e.g., "Notification Preferences") wrap poorly
- No vertical scrolling within the mobile overlay
Suggested fix:
// In Sidebar.jsx mobile menu section:
<div className={cn(
'border-t border-border/60 bg-background/95 px-4 py-3 shadow-lg shadow-foreground/5 lg:hidden',
'max-h-[70vh] overflow-y-auto', // Add scrollable container
)}>
<nav className="mx-auto grid max-w-[1500px] gap-1">
{/* ... */}
</nav>
</div>
Priority: HIGH — Mobile breakage affects a significant portion of users
2. Settings Page — No Loading Skeleton for Main Content Area
Where: client/pages/SettingsPage.jsx
Why it matters: The entire page shows a full-page loader (Loading…) during initial data fetch, resulting in a blank white screen for 200–500ms. This feels slower than needed.
Suggested fix:
- Replace full-page loader with skeleton cards matching the layout
- Show placeholder content: 2-3 shimmering
SectionCardcomponents - Fade out skeletons when data arrives
Impact: Perceived performance improvement (~30-40% faster mental load time)
3. BillModal — Real-Time Validation on Every Keystroke Causes Layout Shifts
Where: client/components/BillModal.jsx
Why it matters: The handleChange function debounces validation but still triggers re-renders on every keystroke. This causes:
- Input field height changes when error messages appear/disappear
- Jarring UX during form entry
- Potential focus loss on fast typists
Suggested fix:
- Only show error messages after field blur or form submit attempt
- Pre-allocate error message space (min-height: 12px)
- Use
aria-live="polite"for screen reader notifications
Alternative:
// Only validate on blur or submit, not on every change
// Keep error state but don't re-render unless visibility changes
{errors.name && errorStateVisible && (
<span className="text-[10px] text-red-500 font-medium">{errors.name}</span>
)}
MEDIUM
4. Sidebar Nav — No Active Indicator for Dropdown Children
Where: client/components/layout/Sidebar.jsx — TrackerMenu component
Why it matters: When users navigate to /bills, /categories, or /summary, the main "Tracker" dropdown remains unhighlighted. This creates ambiguity about current location.
Current behavior:
- Only the dropdown trigger is highlighted when on
/(Overview) - Child routes like
/billsdon't indicate they're part of the Tracker group
Suggested fix:
- Detect when any child route is active via
location.pathname.startsWith('/bills')etc. - Apply
bg-primary text-primary-foregroundstyle to the Tracker dropdown when any tracker subpage is active
Code hint:
const isTrackerActive = trackerItems.some(item =>
item.end ? location.pathname === item.to
: location.pathname.startsWith(item.to)
);
Already implemented in the code, but the TrackerMenu trigger needs styling update.
5. Admin Panel — Missing Error Boundary for Critical Sections
Where: client/pages/AdminPage.jsx
Why it matters: Several complex cards (Backup, Email, Users) lack explicit error boundaries. If an API call fails mid-render or throws, the entire admin panel goes blank with no recovery path.
Suggested fix:
- Wrap each major card in a
try/catchor React Error Boundary - Show "Failed to load" state with retry button
- Example:
function BackupSection() {
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
api.getBackups()
.then(setData)
.catch(err => setError(err));
}, []);
if (error) {
return (
<Card>
<CardContent>Failed to load backups.</CardContent>
<Button onClick={() => setError(null)}>Retry</Button>
</Card>
);
}
// ...
}
6. Settings Page — Field Labels Not Keyboard-Accessible
Where: client/pages/SettingsPage.jsx
Why it matters: While label elements exist, they're not explicitly tied to inputs via htmlFor. Some components (e.g., theme cards) use buttons without labels, making screen reader navigation difficult.
Suggested fix:
- Ensure all form inputs have explicit
idand correspondinglabel htmlFor - Add
aria-labeloraria-describedbyto interactive elements:
<button
type="button"
onClick={() => onSelect('light')}
aria-label="Select light theme"
aria-pressed={currentTheme === 'light'}
>
<Sun className="h-4 w-4" />
</button>
7. ProfilePage — Email Input Not Validated client-side
Where: client/pages/ProfilePage.jsx — NotificationPreferences component
Why it matters: The email input field accepts any string, including invalid formats like test@localhost or not-an-email. Validation only happens server-side, leading to delayed error feedback.
Suggested fix:
- Add client-side email regex check before save
- Show inline error if invalid:
^[\w.-]+@[\w.-]+\.\w+$ - Debounce validation to avoid spamming errors during typing
8. BillModal — Date Input Uses Unusual "Due Day of Month" Pattern
Where: client/components/BillModal.jsx
Why it matters: The "Due day of month" input expects a number (1-31) instead of a standard date picker or calendar selection. This is confusing for:
- Users expecting a full date picker
- International users (some countries use DD/MM vs MM/DD)
- Edge cases like February 30th (which doesn't exist)
Suggested improvement:
- Consider using
react-datepickeror similar for full date selection - Alternatively, add a helper tooltip: "Enter day number only (e.g., 15 for the 15th)"
- Add a validation example: "Due on the 15th → enter 15"
LOW
9. Global Layout — Header Backdrop Filter Not Fallback for Older Browsers
Where: client/components/layout/Sidebar.jsx — header element
Why it matters: The backdrop-blur-xl class relies on CSS backdrop-filter, which is unsupported in older browsers (e.g., Safari <14, some Android WebView versions). This results in a solid background instead of glassmorphism.
Suggested fix:
- Add a CSS fallback:
bg-background/85 supports-[backdrop-filter]:bg-background/70 - Already implemented ✅ — no changes needed
10. Login Page — No "Remember Me" Checkbox
Where: client/pages/LoginPage.jsx
Why it matters: Modern apps often include a "remember me" option to reduce login friction on trusted devices. Without it, users must re-authenticate on every session.
Suggested fix:
- Add a checkbox below the password field:
<div className="flex items-center gap-2">
<input type="checkbox" id="remember" />
<label htmlFor="remember" className="text-xs text-muted-foreground">
Remember me
</label>
</div>
- Set
rememberMeflag in localStorage or via cookie (if server supports it)
11. Theme Toggle — No Visual Feedback When Switching
Where: client/components/ui/theme-toggle.jsx (shadcn/ui)
Why it matters: The theme toggle button doesn't indicate which theme is currently active. Users must click to discover the current state or remember manually.
Suggested fix:
- Add subtle text label: "Light" / "Dark" next to the icon
- Or use a tooltip:
aria-label="Current theme: Dark" - Or change icon (sun/moon) based on theme (already done ✅)
12. Calendar Page — Empty State Not Customizable
Where: client/pages/CalendarPage.jsx
Why it matters: When there are no bills, the calendar shows a generic "No bills found" message. This doesn't guide users toward creating their first bill.
Suggested fix:
- Add a CTA button: "Create your first bill"
- Link directly to the modal with a pre-filled category or default values
- Include a placeholder image or illustration
MEH
13. General — Inconsistent Spacing in table-surface Utility
Where: Multiple components (SettingsPage, ProfilePage)
Why it matters: The table-surface utility (used in Settings and Profile pages) applies mb-4 and internal padding, but the spacing isn't uniform across all pages. Some sections have excessive vertical space, others feel cramped.
Suggested fix:
- Audit and standardize spacing tokens:
section-spacing=mb-6(1.5rem)card-spacing=mb-4(1rem)row-spacing=py-3(0.75rem)
- Document in
docs/TOKENS.md
14. Icons — No Consistent Icon Palette
Where: Across all components
Why it matters: Different icon sets are used inconsistently:
lucide-react(primary)- Custom SVGs (logo)
- Some components import icons but don't use them
Suggested fix:
- Standardize on
lucide-reactfor all icons - Create a shared
icons/directory with named exports if custom icons are needed - Document icon usage in
CONTRIBUTING.md
Summary
| Priority | Count | Key Impact |
|---|---|---|
| CRITICAL | 0 | — |
| HIGH | 3 | Mobile accessibility, perceived performance, form UX |
| MEDIUM | 5 | Navigation clarity, error resilience, keyboard nav |
| LOW | 4 | Convenience, consistency, discoverability |
| MEH | 2 | Minor polish, standardization |
Next Steps
- HIGH items should be prioritized for the next minor release (v2.1)
- MEDIUM items can be batched into a quality-of-life update
- Consider a design system audit to address MEH items (spacing, icons)
- Re-run this analysis after implementing HIGH/MEDIUM items to track progress
Generated by Scarlett (Frontend/UX Authority) on 2026-05-08