From 5abd61d084bfbf99f638010b45df38a97523ea44 Mon Sep 17 00:00:00 2001 From: WEIREN FENG Date: Thu, 8 Oct 2026 17:18:02 -0700 Subject: [PATCH] Give text fields 16px text on phones so iOS does not zoom in iOS Safari zooms the whole page when it focuses a text field whose text is smaller than 16px. Every text field on the site was 13-15.5px: the lab inputs, the quiz fill-ins and the command palette (code and UX item 30). Below 560px, text fields (including the labs' inputs, which have no type attribute) get 16px. The rule uses !important because chapter styles size their lab inputs with more specific selectors. narrow.spec.ts measures every lab input on six chapter pages at 390px, plus a field with the quiz's and the palette's classes. It fails at 15px without the rule. Co-Authored-By: Claude Opus 5.5 --- app/globals.css | 13 +++++++++++++ test/e2e/narrow.spec.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4edd215..84e94ac 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2334,6 +2334,19 @@ li code { .nodec { width: 40px; height: 40px; font-size: 12.5px; } } +/* iOS Safari zooms the whole page when it focuses a text field whose text + is smaller than 16px. Phones get 16px in every text field; !important + because chapter styles size their lab inputs with more specific selectors. */ +@media (max-width: 560px) { + input:not([type]), + input[type="text"], + input[type="search"], + textarea, + select { + font-size: 16px !important; + } +} + @media (prefers-reduced-motion: reduce) { *, *::before, diff --git a/test/e2e/narrow.spec.ts b/test/e2e/narrow.spec.ts index c3ea61c..9ef81a8 100644 --- a/test/e2e/narrow.spec.ts +++ b/test/e2e/narrow.spec.ts @@ -1,5 +1,6 @@ -// On a phone nothing is cut off at the right edge of any page, and printing -// shows every section, without the shell chrome, in the light palette. +// On a phone nothing is cut off at the right edge of any page and text fields +// are large enough that iOS does not zoom in; printing shows every section, +// without the shell chrome, in the light palette. import { expect, test } from "@playwright/test"; import { elementsPastViewport } from "./overflow"; @@ -36,6 +37,35 @@ for (const width of [360, 390]) { }); } +test.describe("text fields on a phone", () => { + test.use({ viewport: { width: 390, height: 844 } }); + + test("are at least 16px, so iOS Safari does not zoom in on focus", async ({ page }) => { + // The labs' inputs are on the page from the start + for (const route of ["/string", "/hash", "/bst", "/heap", "/trie", "/advanced"]) { + await page.goto(route); + const sizes = await page + .locator('input:not([type]), input[type="text"]') + .evaluateAll((els) => els.map((e) => parseFloat(getComputedStyle(e).fontSize))); + expect(sizes.length, route).toBeGreaterThan(0); + for (const size of sizes) expect(size, route).toBeGreaterThanOrEqual(16); + } + // A quiz fill-in appears only on its question, and the palette only when + // open, so measure a field with each of their classes instead + const shared = await page.evaluate(() => + ["q-input", "cmdk-input"].map((cls) => { + const field = document.createElement("input"); + field.className = cls; + document.body.append(field); + const size = parseFloat(getComputedStyle(field).fontSize); + field.remove(); + return size; + }), + ); + for (const size of shared) expect(size).toBeGreaterThanOrEqual(16); + }); +}); + test("printing shows every section, without the shell chrome, in the light palette", async ({ page, }) => {