Web App Responsiveness
Responsividade e conformidade mobile para web apps Koder (admin, dashboards, consoles SaaS): breakpoints, touch targets, iOS Safari, hover, tabelas, formulários. Distinto de landing pages (specs/ landing-pages/), que tem regras próprias.
Spec — Web App Responsiveness
Applicability
All Koder web applications with user-facing UI pages — admin panels, dashboards, SaaS consoles, and any route served beyond a static landing page. Examples:
- `id.koder.dev/admin
.app-shell { display: grid; gridtemplatecolumns: 1fr; }
.sidebar { position: fixed; inset: 0; transform: translateX(-100%); z-index: 200; transition: transform 0.25s ease; }
.sidebar.is-open { transform: translateX(0); }
.mobile-topbar { display: flex; }
@media (min-width: 1200px) { .app-shell { gridtemplatecolumns: 240px 1fr; }
.sidebar { position: sticky; top: 0; height: 100svh; transform: none; }
.mobile-topbar { display: none; } }
**Common wrong pattern (avoid):**.appshell { gridtemplate-columns: 240px 1fr; } @media (max-width: 900px) { .sidebar { display: none; } }
@media (max-width: 900px) { ... }
---
## 2. Touch Targets
- All interactive elements (buttons, links, checkboxes, form controls, icon buttons) must have a minimum clickable area of **44 × 44 px** (Apple HIG minimum).
- If the visual element is smaller, add `padding` or use `::before`/`::after` to extend the hit area.
- Interactive elements must never overlap or be spaced less than **8 px** apart on mobile.
---
## 3. Navigation
### 3.1 Sidebar / Nav rail
- **Desktop (≥ 1200 px)**: persistent sidebar visible (sticky column, `240px` wide by default).
- **Tablet + Mobile (< 1200 px)**: sidebar collapses to a full-height slide-over drawer (preferred) or an icon-only rail (acceptable). Default: drawer triggered by a top-bar hamburger button. Use the drawer for new implementations — icon-only rail only if there is a strong product reason.
- The sidebar drawer must be `position: fixed; inset: 0; z-index: 200` and slide in from the left with `transform: translateX(-100%) → translateX(0)`.
- A semi-transparent backdrop (`position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 190`) must appear behind the open drawer and close it on tap.
### 3.2 Top bar
- Must be present on all three breakpoints.
- On mobile: product logo + menu button. No horizontal nav items visible; they move into the drawer.
- Minimum height: `52px`.
### 3.3 Menu button (hamburger)
- Required in the top bar when sidebar is not visible.
- Must be at least 44 × 44 px.
- Must toggle a drawer with `aria-expanded` on the triggering button.
---
## 4. Typography
| Context | Minimum `font-size` |
|----------------------|---------------------|
| Body / paragraphs | 14px |
| Form inputs | **16px** (prevents iOS auto-zoom on focus) |
| Labels / captions | 12px |
| Headings (h1–h3) | Scale relative; h1 may shrink on mobile but never below 20px |
- Line height: ≥ 1.4 for body copy.
- Never use `font-size < 12px`.
---
## 5. Forms
- Inputs, selects, and textareas must be `width: 100%` on mobile.
- Labels must stack above their inputs on mobile (not inline-left).
- `font-size: 16px` on all `<input>`, `<select>`, `<textarea>` — prevents iOS Safari from zooming in on focus.
- Submit buttons must span full width on mobile.
---
## 6. Tables
Data tables must not break the layout. On mobile (< 768 px):
- Option A (preferred): Wrap the table in `overflow-x: auto` — table scrolls horizontally inside the wrapper.
- Option B: Reflow to card/list layout via CSS.
- Never allow `table-layout: fixed` with large fixed widths that cause `overflow: hidden` to clip data silently..table-wrapper { overflow-x: auto; webkitoverflow-scrolling: touch; }
---
## 7. Modals and Dialogs
On mobile:
- Full-width (`width: 100%; max-width: 100%`) or bottom-sheet pattern.
- `max-height: 90svh` with `overflow-y: auto` on the scrollable content area.
- Never extend below the fold without a scroll container.
- `position: fixed` works on iOS for modals — no workaround needed for modals (only for sticky bars — see §8).
---
## 8. iOS Safari — Required Rules
### 8.1 Viewport height
Never use `height: 100vh` or `min-height: 100vh` on full-screen containers. iOS Safari includes the browser chrome in `100vh`, causing content to be partially hidden.
Use `100svh` (small viewport height — excludes browser chrome):.appshell { minheight: 100vh; }
.app-shell { min-height: 100svh;
minheight: -webkitfill-available; }
### 8.2 Safe area insets (notch / home indicator)
Containers that extend to screen edges must account for the notch and home indicator:body { paddingtop: env(safeareainsettop); paddingbottom: env(safeareainsetbottom); paddingleft: env(safeareainsetleft); paddingright: env(safeareainsetright); }
.top-bar { paddingtop: calc(12px + env(safeareainsettop)); }
.bottombar, .tabbar { paddingbottom: calc(8px + env(safeareainsetbottom)); }
Required in the `<head>`: `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">`.
`viewport-fit=cover` is what enables `env(safe-area-inset-*)` to work.
### 8.3 position: fixed caveats
`position: fixed` elements (top bars, sidebars, bottom nav) may not behave correctly on iOS when the virtual keyboard is open. Do not rely on `position: fixed` for elements that must stay visible while an input is focused. Consider repositioning to `position: sticky` within a scrollable parent for such cases.
### 8.4 Meta viewport — forbidden values
Never add `user-scalable=no` or `maximum-scale=1` to the meta viewport tag. These break accessibility (pinch-to-zoom) and violate WCAG 1.4.4.<!-Forbidden - <meta name"viewport" content"widthdevicewidth, initialscale1, user-scalable=no">
<!-Correct - <meta name"viewport" content"widthdevicewidth, initialscale1, viewport-fit=cover">
---
## 9. Hover — Touchscreen Safety
All `:hover` rules applied to cards, rows, buttons, and interactive containers must be wrapped in `@media (hover: hover)`. This prevents "stuck hover" on touchscreens (the hover state activates on tap and never clears)..card:hover { background: var(-urface-raised); transform: translateY(-2px); }
@media (hover: hover) { .card:hover { background: var(-urface-raised); transform: translateY(-2px); } }
**Exception**: color-only changes on text links (underline, color) do not require the wrapper.
---
## 10. Images and Media
- `max-width: 100%` on all `<img>`, `<video>`, `<iframe>`.
- Never set `width` or `height` as inline style with a fixed pixel value that exceeds the viewport.
- Use `object-fit: cover` or `object-fit: contain` for images in fixed-size containers.
- Decorative images: `alt=""`.
---
## 11. Scrolling
- Set `overflow-x: hidden` only on `<body>` or the root app shell — never on content containers where it would clip visible content.
- Scrollable lists and panes must use `-webkit-overflow-scrolling: touch` (momentum scrolling on iOS).
- Avoid nested scroll containers where possible — they produce poor UX on mobile.
---
## 12. Build & Deploy
### 12.1 Asset cleanup
Build tools (Vite, Webpack, etc.) append a content hash to output filenames (`index-CBGY9LJz.css`). When a rebuild produces a new hash, the old files stay on disk unless explicitly removed. Browsers that cached the old `index.html` (which references old hashes) will continue loading old CSS/JS even after a new deploy.
**Rules:**
- Always clean the build output directory before or after each build: `rm -rf dist