This guide explains how frontend contributors can test and verify that application UI components and pages behave correctly across the supported responsive viewport range (320px to 1440px+).
This document is written for frontend contributors who are building new pages/components or refactoring existing layouts. The goal is to ensure that all interface changes meet our responsive quality guidelines and do not introduce layout defects or regressions.
Before verifying, make sure your components utilize our custom Tailwind breakpoints and spacing scale (configured in tailwind.config.js).
| Name | Width | Key Device Reference | Testing Priority |
|---|---|---|---|
| 320px | 320px | iPhone SE | 🔴 Critical (Smallest mobile layout, most overflow bugs happen here) |
| 375px | 375px | iPhone 14 / 15 | 🔴 Critical (Primary target for standard mobile layout) |
| 450px | 450px | Foldables / Large mobile | 🟡 High (Intermediate width, tests grid-collapsing transitions) |
| 768px | 768px | iPad Portrait | 🟡 High (First tablet breakpoint, column layout transition) |
| 1024px | 1024px | iPad Landscape | 🟢 Medium (Desktop transition breakpoint) |
| 1440px | 1440px | Standard Desktop | 🟢 Medium (Default desktop width, verifies max content width limits) |
When verifying a feature, your changes must pass the following key checks:
- No Horizontal Overflow (Scroll)
The total width of the document (
documentElement.scrollWidth) must never exceed the viewport client width (documentElement.clientWidth). The layout must scale dynamically without causing horizontal scrolling. - Touch Targets (WCAG 2.1 Level AAA)
All interactive components (buttons, links, form inputs) must have a touch target of at least 44x44px on mobile.
- Use CSS class
.touch-targetto guarantee a minimum height/width of 44px. - Use CSS class
.touch-target-wideto guarantee a minimum height of 44px and width of 88px.
- Use CSS class
- Minimum Mobile Typography To prevent iOS Safari from automatically zooming into input text fields, body and input text on mobile devices (width < 768px) must have a font size of at least 14px (preferred: 16px).
- iOS Notch and Safe Areas
Respect physical notches/Dynamic Islands and dynamic taskbars by applying environment-safe padding:
.safari-safe-top.safari-safe-bottom.safari-safe-left.safari-safe-right
We use Playwright to run layout audits at scale. This is the preferred method for automated regression testing.
To run the automated responsive test suite locally:
npm run test:e2e -- tests/e2e/responsive-split-savings.spec.tsWhen introducing a new feature, you should write a matching E2E spec. Create a file under tests/e2e/ (e.g., tests/e2e/responsive-my-feature.spec.ts) using the following structure:
import { test, expect, type Page } from '@playwright/test';
// Define the viewports we support
const viewports = [
{ name: 'iPhone SE', width: 320, height: 568 },
{ name: 'iPhone 14', width: 375, height: 667 },
{ name: 'Foldable', width: 450, height: 800 },
{ name: 'iPad Portrait', width: 768, height: 1024 },
{ name: 'Desktop', width: 1440, height: 900 },
];
// Helper to check for horizontal page overflow
async function checkNoHorizontalOverflow(page: Page) {
const hasOverflow = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(hasOverflow).toBe(false);
}
// Helper to check element-level horizontal overflow relative to client viewport
async function checkNoElementOverflow(page: Page, rootSelector = 'main') {
const offenders = await page.evaluate((selector) => {
const root = document.querySelector(selector);
if (!root) return [];
const viewportWidth = document.documentElement.clientWidth;
return Array.from(root.querySelectorAll<HTMLElement>('*'))
.filter((el) => {
const style = window.getComputedStyle(el);
if (
style.position === 'fixed' ||
style.display === 'none' ||
style.visibility === 'hidden'
) {
return false;
}
const rect = el.getBoundingClientRect();
return rect.right - viewportWidth > 1 || rect.left < -1;
})
.slice(0, 5)
.map((el) => ({
tag: el.tagName.toLowerCase(),
className: el.className,
}));
}, rootSelector);
expect(offenders).toEqual([]);
}
// Helper to check touch target boundaries
async function checkTouchTargetSize(page: Page, selector: string, minWidth = 44, minHeight = 44) {
const element = page.locator(selector).first();
const box = await element.boundingBox();
if (box) {
expect(box.width).toBeGreaterThanOrEqual(minWidth);
expect(box.height).toBeGreaterThanOrEqual(minHeight);
}
}
test.describe('My Feature - Responsive Verification', () => {
viewports.forEach(({ name, width, height }) => {
test(`${name} (${width}x${height}) - Layout and Overflow`, async ({ page }) => {
// Set the viewport size dynamically
await page.setViewportSize({ width, height });
// Navigate to the target page
await page.goto('/split');
// Wait until DOM content is loaded
await page.waitForLoadState('domcontentloaded');
// Assert that there is no horizontal page overflow
await checkNoHorizontalOverflow(page);
await checkNoElementOverflow(page, 'main');
});
test(`${name} (${width}x${height}) - WCAG Touch Targets`, async ({ page }) => {
await page.setViewportSize({ width, height });
await page.goto('/split');
await page.waitForLoadState('domcontentloaded');
// Verify interactive element bounds (e.g., Save Allocation button)
const saveBtn = page.locator('button:has-text("Save Allocation")');
if (await saveBtn.isVisible()) {
await checkTouchTargetSize(page, 'button:has-text("Save Allocation")', 44, 44);
}
});
});
});If you are performing local visual audits or validating dynamic animations/interactive hover behavior:
Make sure the app is running:
npm run dev- Open http://localhost:3000 in Google Chrome.
- Press
Ctrl + Shift + I(Windows/Linux) orCmd + Option + I(macOS) to open the DevTools panel. - Click the Toggle Device Toolbar icon (or press
Ctrl + Shift + M/Cmd + Shift + M).
Instead of choosing pre-defined browser presets, configure custom dimensions to target our exact design breakpoints:
- Set the dropdown at the top of the viewport to Responsive.
- Manually enter the target widths in the width input field:
- Test at 320px (iPhone SE)
- Test at 375px (iPhone 14)
- Test at 450px (Foldable)
- Test at 768px (Tablet Portrait)
- Test at 1024px (Tablet Landscape)
- Test at 1440px (Desktop)
- Slowly drag the resize handle from 320px up to 1440px to ensure that layout blocks wrap smoothly and font/padding transitions occur without awkward text clipping or whitespace issues.
When verifying layout behaviors, keep an eye out for these frequent issues:
- Problem: Text or buttons inside narrow containers are clipped or overflow because of hardcoded widths (e.g.
w-[200px]). - Fix: Avoid hardcoded widths on layouts. Use responsive classes like
w-full max-w-xsor Flexbox/Grid structures (flex-wrap,grid-cols-1 375:grid-cols-2).
- Problem: Text using standard tailwind
text-xs(12px) makes forms hard to read and triggers automatic zoom on iOS Safari input fields. - Fix: Ensure body copy and inputs scale progressively using breakpoints:
className="text-sm 375:text-base"
- Problem: Large padding designed for desktop remains on mobile, squeezing the content width, or padding becomes too sparse.
- Fix: Implement progressive padding that adapts across our breakpoints:
className="p-5 320:p-6 375:p-7 sm:p-8"
- Responsive Breakpoint Quick Reference Guide - Design patterns, spacing tokens, and safe area guides.
- Responsive Breakpoint Visual Guide - ASCII wireframe representation of layouts.
- Tailwind CSS Configuration File - Exact values for custom breakpoints.
- Frontend Testing Guide - General test suite conventions and execution commands.