Recently, I made a free game. It’s a little mystery investigation game which I was inspired to write after enjoying games like Case of the Golden Idol, Roottrees are Dead, and Return of the Obra Dinn. I thought my UX ideas were pretty simple, so I decided to implement it in HTML5.
I know, I know: games should be made with game engines. But listen: The game is mostly text. It has a dirt simple single-page ("SPA") type layout, static images, a few fonts, a few sounds, and that’s it. No need for a backing server, no 3D graphics, no complex animations, and no particularly nov…
Recently, I made a free game. It’s a little mystery investigation game which I was inspired to write after enjoying games like Case of the Golden Idol, Roottrees are Dead, and Return of the Obra Dinn. I thought my UX ideas were pretty simple, so I decided to implement it in HTML5.
I know, I know: games should be made with game engines. But listen: The game is mostly text. It has a dirt simple single-page ("SPA") type layout, static images, a few fonts, a few sounds, and that’s it. No need for a backing server, no 3D graphics, no complex animations, and no particularly novel interactions. Just a single page load, and then clicking and reading.
I figured it should be easy to make this game (app, really) work on multiple browsers and devices, so that players could access it without a native install. The web has supported these basic functions for over a decade. Surely in the year 2025, I thought, HTML5 is a good choice for these simple needs. It’s the future now!
What really happened was, I hit over 50 surprising problems related to gaps in web standards, requiring me to spend over half of the total development time on rework for cross-browser and cross-device support. In this article I’d like to list the problems I hit, as an illustration of what a novice web developer faces on the modern web today. I’ll give some simple advice on reducing rework costs, and conclude with some points on what web standards in 2025 do and don’t do for us.
Surely this is my fault

I know, I know: new features are always being added to browsers. There’s always going to be some "wet paint" in web standards. If I make a web project that depends on the newest features, it’s not going to work everywhere. You might be thinking that in order to hit 50+ cross-browser problems, I must have used the hottest, newest, most experimental web APIs, and that was all my fault.
The common wisdom is that if only I restrain myself to the "safe harbor" of the oldest, most time-tested parts of the standard web, then I can rest assured that my web page will work on any "modern" browser and device.
But sadly, no.
Unfortunately some of the most buggy, glitchy, and/or divergent behaviors I found were in 20+ year old standard web features. It’s not (just) the new stuff. I was surprised to find no clear safe harbor feature set for building polished, interactive web-based projects, even if they are pretty simple. Even with other projects that were barely interactive, I’ve hit fairly important platform and browser variances, and needed at least some rework for multiple browsers.
Even so, I imagine that an experienced web developer could read the list of problems below and nod to themselves knowingly: "sure, these are all common pitfalls. That’s why we have frameworks that polyfill many of these problems, and experienced designers and frontend engineers who know all the best practices."
Perhaps that’s true, but my point is: should it be? If a "standard platform" requires a giant device testing lab and a priesthood of finger-wagging frontend druids with trauma response for engineering intuition, is this "standard" really helping us a lot? Even if web standards don’t let us live the dream of "write once, run everywhere", shouldn’t it at least be predictable where the problems are going to be?
The novice web developer experience
The way I ended up hitting all these nasty surprises was not as naive as it may sound. I am not a novice web developer; I am, unhappily, one of the druids. I did not naively blunder into cross-browser pitfalls, I’m perfectly well aware that huge browser differences exist (spoiler: mobile) and need design consideration, and testing.
But I did accidentally simulate the naive web developer experience with my game project, because of how I was prototyping. In game development, the core game mechanics have to be fun or else the rest of the game is not worth making. So I whipped up a prototype wireframe in HTML5 to get a feel for core puzzle mechanics, purposely disregarding cross platform considerations. I willfully ignored phones and tablets and Safari, and iterated just on my own MacBook with only one browser.
Only later, after the wireframe game mechanics had been refined with playtesting, did I decide to polish the prototype into a cross-browser game that also worked well on mobile. If you work at a giant company you wouldn’t work like this; in fact you probably wouldn’t get to design the UX at all, because you’d receive the proposed interactions from a paid UX designer.
But a new developer working alone probably would do this? And why shouldn’t they? It should be reasonable to get an idea working on one’s own computer first, and not be surprised later by massive rework on a second device/browser.
So, where are we on that in 2025? To find out, I compiled all the browser bugs and rework tasks from my game project into this list.
TLDR: buy an old iPhone and test with it daily
If you don’t read the rest of this rant, I can sum up my advice as just this: if you’re making a web project, even a simple one, do your rapid, many-times-a-day iteration loop testing on an older iPhone as your test mule. Yes this is a pain, because none of us are programming on an iPhone soft keyboard. We’re sitting at a computer or laptop, and so that’s the platform it’s most natural to iterate on. Most frontend tools do not make it easy to have a quick edit-and-reload cycle with a real mobile device. So you’ll have to either frequently push to a private web server, or use some exotic ssh tunnel contraption, to get it so that your test mule iPhone can view your test web project.
This is not because iPhones are good, it’s because they’re bad. iPhones are more peculiar and less compliant than any other device I tried. I promise that if you get your web project looking good and working smoothly on a crappy iPhone, your residual costs to test and polish on all other platforms and browsers will be fairly low. (For extra bravery, I recommend Firefox iOS instead of Safari, because it is the most buggy, least compliant browser I was able to find. If it works on Firefox iOS it’s going to work anywhere. See below.)
Yes all the desktop browsers have some form of "design for mobile" mode, and that’s better than nothing. But these tools will not reveal 80% of the problems I’ve described below. Not even the Safari responsive design mode. Not even the XCode device simulator.
If you’d like to hear more, then let’s start with some gleeful indignation at one of the biggest culprits in web development:
Culprit #1: Apple has opinions
Writing this with fresh bruises from my iOS testing lab, I feel like it’s fair to say that Apple’s mobile devices (iPhone, iPad) wilfully and unapologetically diverge from the rest of the web more than any other platform. Safari has a few bugs and is the slowest to support any given web API. But this section is not about Safari’s bugs and sandbagging; more on that below. This section is just about Safari’s strong opinions. Here are few issues I hit:
Can’t use the whole screen
In a game, and arguably in any well designed UX, the application wants to use all the available screen space. The design should neither waste space on unintended gutters or dead pixels, nor should it confuse the user by letting important information fall below the fold where the user will have to scroll to get at it, or will be confused because they won’t notice it. I want to simply fill the whole canvas but not overspill it, and this is comically difficult on iOS.
Mandatory automatic resizing: First, the navigation bar at the top of the screen will appear and dissappear depending on the user’s most recent gesture. There is no event your application can receive to know this is happening, nor any measurement you can take to detect it. [surprising problem #1]
The standard CSS features vw and vh units are broken by this behavior, so trying to create a whole-page user experience will break on iOS Safari.
Broken CSS units: Experienced web developers have tried complicated tricks to work around this resizing behavior. Later, new units were introduced to CSS to try to give the web page more control over this. And although newer Safari iOS versions support them, Firefox iOS still doesn’t. [#2]
Anti-competitive non-standards: Relatedly, there is a Fullscreen API in the web standards, which every browser supports... except Safari iPhone. [3] The community hypothesizes that fullscreen is purposely hobbled on iOS so that Apple can sell more native iOS games without web-based games being a viable competitor. When I considered using the fullscreen web standard to give my game better use of the screen, I was thwarted by this.
Useless fullscreen behavior: The Fullscreen API is supposed to work on iPad, and I wasted time trying to offer it to my users. But it has three ridiculous problems: First, it replaces the top of the screen with a black area and an irremovable "X" which doesn’t actually reclaim any more space than the address bar would have. [4]
Second, if the user scrolls down in any element anywhere on the page, full-screen mode ends. [5]

Third, Safari will refuse to summon the soft keyboard if fullscreen is currently on. Newer versions of Safari simply exit fullscreen, but in iOS 12 I encountered this hilarious error dialog informing the user that Apple believes the web page is phishing them. (!) [6] Since my game had both scrolling elements and occasionally needed the keyboard, I gave up in disgust so now I simply hide the fullscreen button on iOS.
Workarounds: My solutions were to (a) give up on the Fullscreen API on iOS, (b) use position:fixed and some cleverness with CSS transforms to fill the screen at least horizontally, and (c) to blindly add in some height fudge factors to try to stay below the unknowable threshold of vertical scrolling on these browsers. My game now uses almost the whole screen on iOS.
Touch screen problems
Apple’s mobile browser is perfectly okay for scrolling to read articles and tapping hyperlinks. But for my game I needed to design more interactive elements. Although I tried to keep it simple, I was still hit by a number of Apple-specific surprises:
Dragging does what: Dragging with the mouse on a desktop browser will drag any draggable elements, or do text selection otherwise. But on Safari iOS, dragging a finger will only scroll. [7] Even if the touched area isn’t scrollable, and even if the finger lands on a draggable element, swiping gestures only scroll. If the user does a compound press-and-hold-and-then-drag gesture, then on Android this will drag the draggable element but on iOS it will select text instead. [8] I was able to fix this with a liberal sprinkling of user-select:none styles on many more elements than Android needed.
What is scrollable: In the name of saving space and beautifying the display, Apple defaulted us all to the "overlay" style of scroll bars. These elements are translucent, narrow, or entirely invisible for most of their lifetime. The result looks simple and attractive, and next time I want to print out my phone’s screen and hang it on my wall as art, I’ll thank them for that. The rest of the time, this makes it impossible for the user to discern which areas of the screen are scrollable. This is probably a perfectly reasonable tradeoff decision for when the browser is displaying a wikipedia article about raccoons. But for anything more interactive, the inability for the user to discover what is scrollable is pretty bad. I had to add on-screen glinting elements to draw the user’s attention to the otherwise invisible scrolling areas. [9]
Apple’s finger location algorithm: I assigned click Event listeners to various on-screen objects, some of which were <button> but many of which were <div> elements. My testing revealed that Android Chrome will deliver a click event if the user’s finger touches most/all of the area of the element. If the finger hits multiple elements, Chrome Android delivers the event to the one closest to its own proprietary idea of the perceptual center of the finger touch. When I tested my game board UX scaled way down for a tiny mobile screen, I was pleasantly surprised to find that the user can still tap them somewhat successfully even if they’re visually small... but not on iPad.
On Safari iOS with a scaled down UI, the user has to get the center of their finger to hit the element or else the click event won’t be delivered. [10] This made my game effectively unplayable on iOS because the playing field had 10px hit targets which were too small to tap. I fixed it by creating a large invisible <div> over every dot on the field, to catch touch input on iOS.
Can’t play audio
My game has both sound effects and background music. Sound was exotic on the web in 2004, but it’s long since been standardized... supposedly. Not counting composing the sounds in Garage Band, I got these working on my laptop’s browser in under ten minutes. Then getting them to work on iPad and iPhone took me another week of effort.
AudioContext always sounds silent: My first try at sound effects used an AudioContext to wire up Gain and Oscillator nodes to play game beeps. These features are "fully supported" on iOS, but no sound would be produced. [11] I eventually learned that if you use these APIs to make sound, iOS considers them to be "ringtones" rather than "media", which means that the user’s media volume control will have no effect on the game’s sounds. The user must enter their "Settings" app and change their device’s ring volume. The normal hard buttons for volume up and down on an iOS device control media volume, not ring volume. I despaired of instructing any user to do this.
Audio tag play() does not function: Next I moved to using <audio/> tags to play pre-recorded sounds in response to in-game events. I created six HTMLAudioElement objects for my five kinds of sound effect, and one more to play the background music. However, the .play() method, which is marked on MDN as "fully supported" and "widely available", does not function at all on iOS. [12]
You see, Apple is of the opinion that .play() method should only work if it’s in response to a user action, which they implement by checking if the user has tapped an element on the page. Since a game has story events for the user to react to, no such sound effects could work. I worked around it (see below) but it made the system very complicated.
Audio tags skip the first 300ms of sound after the first play: Safari will play MP3s through the HTMLAudioElement — once. But after each tag plays once, Safari aggressively unloads the media, I presume to save memory. [13] I think this is reasonable, except that then when my sound effect plays a second time, the audio has been unloaded and Safari plays silence until it can get the MP3 data back into its buffers again. I would actually have been fine with this behavior if it were to wait to play until the audio was loaded, but it doesn’t do that. Safari is of the opinion that it’s more important to keep up with the scheduled play of the sound than to hear it. So it non-deterministically jumps into the middle of the sound, cutting off the beginning. This made all my sound effects seem glitchy and truncated.
Audio volume not controllable: I created a volume control for both music and sound effects in the game, in case the user wanted to mute them or make them quieter. I did this by setting the .volume property on the Audio tags, and this turned out to be broken on iOS. [14] (To their credit, MDN lists the .volume property of media tags as "limited availability".) Apple simply says "volume is under the user’s control", and does not try to make the thing work. Apple documents its behavior like this:
Reading the volume property always returns 1
This would be a handy behavior to at least write some feature detection logic around, but sadly it’s not true. [15] The actual behavior is that if you modify the .volume property of an HTMLAudioElement then it will seem to change, but (a) no actual volume change will occur, and (b) at the next event loop idle, the volume value will be set back to 1. This wasted an hour of my time to isolate the true behavior and write complicated feature detection logic to check that mutation of the .volume property survives past the next idle loop.
Workarounds: The recommended workaround for the anti-autoplay behavior is to create a little invisible farm of dummy HTMLAudioElement objects which play silent sounds when the user taps anything on the screen. I inserted a "start" screen into my game when the page loads, which has no purpose except to provide the user with something to tap before the game begins.
Once the user taps, I can have the farm of elements play their silent sounds once. Then the programmer is free to rewrite their src attribute to play other media at any time. This works because the constraint to play() from within a user action event handler apparently only pertains to the first use of play on a per-tag basis. [16] So after writing about 200 lines of priming logic, I had a bank of sound effects working. (And can I just say: if this workaround works for me, it works for bad actors too. So Apple’s mitigation adds complexity but doesn’t actually protect users as intended. Awesome!)
To work around the cut-off sound effect problem, I re-recorded all my sound effect MP3s in Garage Band to start with 300ms of silence. This created an apparent lag in the responsiveness of the game, but at least the user could hear the entire sound.
To make mute work, I had to change my code to actually stop the audio instead of setting volume to zero. I also had to write 20 lines of behavior probing code to detect the non-functioning .volume property and offer iOS users an "ON"/"OFF" switch without the gradiated volume controls.
Culprit #2: Behaviors and bugs
Although a lot of the behavior differences I’ve described above are annoying opinions, there are definitely also some more forgivable but still annoying variances in behavior within the standards. And also, good old browser bugs.
Layout problems
Fonts vary by platform, even when they shouldn’t
Font names aren’t reliable: I had a lot of small layout glitches caused by browser differences in choice of font, even fonts that could easily be exactly the same on all platforms like Arial and Courier. [17] I fixed these by using custom woff2 fonts loaded at game start. In a normal web application I think it’s arguable that the application should set a specific font, but in a game where the fonts are part of the aesthetic, this is table stakes.
Aside: woff2 is great: On the bright side, I was pleasantly surprised at how consistent layout can be across platforms when using woff2 fonts to prevent font fallback. (And display:grid is also very consistent, more on that later.) These were well supported even on my worst browser (Firefox iOS) and my second worst browser (Safari on an old iOS 12 device I had lying around). It is almost attainable to have pixel-perfect consistency across platforms with just these two features. I had not realized how much fonts (and localization, if your project has that) are such a large source of layout ambiguity.
(Yes yes, expecting pixel-accurate layout across devices is unreasonable. Yes yes make your designs responsive so that they’ll gracefully handle any shape and size of screen and font setting. But since I’m making a game, the interaction design had a hard requirement of certain things being on the same screen relative to each other. The normal vaguaries of the web would result in broken interactions. My responsive compromise was to define five specific layout sizes and snap to each of those based on font and zoom settings. This made it easier to trust that a playtest of the game would work for a given size of screen.)
Incorrect ligature defaults on Safari iOS: I used Google’s very clever Material design icons to save myself a lot of time drawing and flowing simple icons in the app. It’s implemented with a kooky abuse of the ligature feature of custom font faces, which... weird! But okay! Unfortunately even when pasting the Google-provided code snippet verbatim, Safari’s defaults for the font-ligature property are not to spec, so Google’s icons don’t work on older Safari iOS. [18] I was able to fix this easily just by explicitly setting the property, but it wasted about an hour of my time debugging that.
Safari iOS: late to every party
Although all the browsers adopt new features in their own time, it is my unscientific sense that Safari is unusually slow. And it had a few more bugs, as well. To wit:
Unsupported flex gaps: There are a lot of situations where I prefer the mental model of display:flex over display:grid, but this caused a number of cross-browser unpleasant surprises. First, Safari was unreasonably late to the party on correctly supporting row-gap and column-gap with flex, even though they do what you expect with grid as far back as iOS 12. [19]
Flex shrink problems: Safari chooses to aggressively shrink text in flex layouts in situations where other browsers do not. Specifically it seemed to be related to when a flex-grow area was marked as scrollable. Other browsers would let the elements before and after the scrollable item size naturally, but Safari truncates them. [20] So after a lot of layout testing I had to sprinkle flex-shrink:0 in a bunch of places. Ugh.
Missing support for lh units: I had used the lh length unit in some of my layouts, not realizing that Safari didn’t add support for this until last year. [21]
Missing support for scale syntax: In all browsers I tested except Safari iOS 12, you can say transform:scale(50%) but this does not work on Safari. I had to change these to transform:scale(0.5). [22]
Missing support for ResizeObserver: Safari iOS now supports ResizeObserver for reacting to element size changes, but it was last to do so. To support older browsers I had to write some nasty timer-based measurement code. [23]
Bugs in CSS custom properties: I tried to set the background-image of my game using a var(--my-background-url) CSS custom property. This seemed to mostly work, but in my testing I found that if the custom property changes, Safari will not propagate it correctly to the BODY:backdrop element and will cause a missing background bug in full screen. (I was also able to reproduce this on Safari MacOS, but no other MacOS browsers.) [24]
Late support for smooth scrolling: I had code in my game to programmatically scroll an element during the tutorial, to show the user that it can be scrolled. I set the scroll-behavior:smooth CSS property so that the user’s attention would be drawn to the animating motion of the scrolling area. Older versions of Safari iOS disregarded this directive, making the element pop instantaneously instead of visibly scrolling. This doesn’t seem like a failure, but without it my tutorial failed to teach the user something important. [25]
Button spacing: Safari, and only Safari, adds a bunch of padding to their <button> elements which you have to style away if you care how big your buttons are. [26] (Maybe they do this to compensate for their hit target choices?) Although the agent stylesheet is allowed to contain anything it wants, in practice this can disrupt the intended layout. I didn’t catch it until I did cross-browser testing.
meta viewport incantations

Single-page application designs that want to work on mobile will need to sprinkle attributes into the meta header tag in order to persuade mobile browsers to scale elements predictably:
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
Without these directives, browsers apply various opinionated fudge factors to try to guess what the web page intended while disregarding how the web page is actually styled. Viewport control directives are very useful and worth understanding... but without them, desktop browsers tend to do about the same thing, and mobile browsers do quite different things. [27]
Firefox iOS has no idea where the edges of the screen are
Firefox iOS wins the award for least competent browser in this area:
Broken CSS units: Firefox iOS has a bug which prevents use of the special svh and lvh units which are themselves workarounds to the self-hiding address bar behavior that all the mobile browsers have. [28]
Occluded layout on iPhone: Newer iOS devices lack a physical home button, instead consuming the bottom edge of the touch screen with a line-shaped affordance which the user is supposed to know to swipe or touch depending on their intention. Applications are supposed to either stay out of that area or draw transparently behind it. Firefox does neither! It simply draws your web page with some of the bottom edge cut off, but only on these newer iOS devices. [29] Safari doesn’t have this problem.
Crash on fullscreen: Firefox iOS overrides the requestFullScreen function with an agent script... but that agent script has a bug so that if you call it, you just get a Javascript error. Cool. [30]
Mobile browsers have crazy behavior when you pinch zoom
To try to fill the screen on all platforms I admittedly made a fragile choice: I used transform:scale to ensure that the main screen element was centered and sized. This worked like a charm on all the desktop browsers. And it almost worked on all the mobile browsers too. Except that if the user used pinch zoom in and then out again, the browser would draw the screen with a few pixels at the top missing. [31] The more times the user did this, the more pixels would be missing. [32] My application was not receiving any resize, drag, or scroll events when this happened, and the measurements of the screen as reported by offsetTop, scrollTop, etc were all unchanged. I spent many hours trying to find a cause or workaround for this problem.
Eventually I created an isolated test case, and fixed the problem by adding a position:fixed;top:0;left:0; style to the root element. There is no standards-based reason why that should have fixed the problem, but it did.
Animation and graphics woes
Games need more precisely controlled animations and visuals than is typical for a Wikipedia page about raccoons. This caused me more than a few problems:
Text selection styling: It is technically possible to style the selected text using the ::selection pseudoclass, and I tried to use this to create an animating typewriter effect in my game. Sadly, this is not supported on Safari iOS so I had to rework the animation to work by modifying the DOM in a Javascript loop. Inelegant but functional. [33]
Blur bugs: I used filter:blur() and backdrop-filter:blur() CSS properties in various places to create glow and frosted glass effects for the game’s visuals. These sometimes worked in Safari, but were broken in certain situations, like on <svg> elements. [34] Safari was also late to support this and needed a -webkit-backdrop-filter:blur() variation to get the visual effect to appear on older tablets.
Sync animations: The animation CSS property is surprisingly excellent, a much needed alternative to the often useless transition system, especially on browsers that don’t yet support the just-added content-visibility property. (There, I said something nice about web standards.) There’s a Javascript API to manipulate and restart animations so that (for example) blinking warning lights can be sync’d up with each other. As usual my 2019 iPad was just old enough to not support this. [35] I didn’t bother with any workaround, so on older tablets this remains a glitch.
Audio problems
Apple’s audio opinions described above were the biggest problem with audio, but Apple wasn’t the only culprit:
Error on play interruption: Chrome, and only Chrome, is of the opinion that it’s a bug to .pause() or assign .currentTime on an audio element while a previous .play() request is "in progress", whatever that means. The other browsers will simply fulfill your request, but not Chrome. Chrome generates an unhandled promise rejection which looks like a game crash when the audio pauses. [36] So I had to write some complicated guarding logic to protect the audio widgets from concurrent manipulation during their sensitive moments.
Popping and skipping sounds: Firefox on both MacOS and Android cannot seem to just run an OscillatorNode competently. Before I switched away from AudioContext, I found that although Chrome and Safari on MacOS could play my graph of sound effects smoothly, Firefox introduced "pops" (audio artifacts caused by incorrectly reproducing a sound wave) and missing sounds. [37] I was able to mostly fix these by adding more attenuation ramps and silence at the start and end of sounds, but I definitely shouldn’t have to. Avoiding mathematical errors that cause popping sounds should be table stakes for any piece of software that produces audio. You guys.
Safari iOS late to support AudioContext: AudioContext is documented as fully supported since forever, but you have to read the fine print! The function has been in Safari iOS for many years, but only if you call it via the special webkitAudioContext. [38] It was only renamed to match the other browsers a couple years ago. So that wasted a half hour of my time to chase down.
iPad GainNode timings inaccurate: I found that Safari on iOS 12 does not honor linear gain ramps the way other browsers do. [39] So even once my sound effects were audible, they sounded like strange whooping slide whistles on iPad even though they sounded correct on Android and MacOS. I can’t tell if this is a bug or an opinion, but either way, it was unacceptable. These two problems caused me to abandon the AudioContext API entirely. (My iOS 17 device doesn’t have this behavior, so I guess Safari agreed that it was a bug.)
Other interactions
Drag and drop control: My drag-and-drop Safari woes were not only caused by Apple’s strong opinions. I found that I had quite a few touch screen bugs caused by varying interpretations of CSS and HTML standards. For example, I was using inert=true to prevent interactions, but older Safari browsers ignore this attribute. [40] I had to also add user-select:none; -webkit-user-select:none; pointer-events:none; in various places to get Safari to stop showing my users unwanted text selection areas when dragging was wanted.
Draggable sprites: I did get dragging to work on Safari iOS, but I found that if the draggable element is a <span> which wraps to the next line, then Safari will garble the visual effect of the dragged sprite as it moves on the screen. [41] I ended up reworking the draggable elements to ensure that they could never have a line break.
Clipboard API: I used the clipboard API for a simple import/export game feature, since it is documented as fully supported on all major browsers. As usual Safari iOS was the last to add support, and I found that my old iPad was just old enough to fail with this API. [42]
Microsoft Edge: copying Chrome’s homework real hard
The only browser I tested which needed (almost) no rework was Microsoft Edge on Windows 10.

Styling for traditional scrollbars
The one problem I observed with Edge was scrollbar styling. On Windows 10 the default scroll bar type is more likely to be "traditional" than "overlay", so I happened to notice that glaring white scrollbars were shown in my dark-themed space game. [43] This situation can arise on any OS, not just Windows, because "always show scrollbars" is a user-facing accessibility setting on several platforms.
I fixed this by styling darker scrollbars for all browsers using the newly available scrollbar-color CSS property. (Except for Safari, [44] which requires a ::-webkit-scrollbar pseudoclass, of course.)
Other than this issue, Microsoft Edge was impressively trouble-free. It passed my full test suite on the first try, and had no other platform-specific problems. I attribute this not particularly to the skill of Microsoft, but that my game was already very well tested on Chrome MacOS. Edge seems not to have diverged much at all from the Chrome ancestry from which was is forked.
Culprit #3: Mobile Redesign
Zero web druids will be surprised to hear me say that the biggest source of pain with designing a polished, interactive HTML5 application is the difference in browser behavior between phones and tablets vs desktop computers. Of course within the priesthood we consider "responsive design" to be table stakes. But taking a step back from our craft for a moment: is it so crazy for a novice developer to expect that they could design a web-based project on their laptop and see it work fine on a phone with no changes? Why can’t this be a reality?
Early on in the popularization of touch screens, manufacturers like Apple made some reasonable but aggressive decisions to permanently fracture the desktop and mobile browsing experiences. I don’t think these were bad decisions! When a user has a small touch screen, the most efficient interactions for them aren’t the same as when they have a very large display, a mouse or trackpad, and a hard keyboard. But it did double the size of the design space, and theoretically, this could have been avoided.
Here are some mobile-specific snags I hit while cosplaying a novice web developer:
Tapping and touching
Dragging does what, part 2: You would think that dragging would work easily on touch screens, and I did sort of expect that my game’s drag-and-drop interactions would port nicely to mobile. Except no! Because mobile browsers have reserved dragging for themselves. Vertical swipe, edge-swipe, and other finger-drag type inputs command the browser to scroll, navigate, and select text. Those events either don’t reach the web page’s elements at all, or they are delivered as an overload with other interactions. [45] Various CSS hints like pointer-events:none; and user-select:none must be sprinkled into the page to control these interactions more carefully.
Surprise zoom: One silly example is double-tapping. The <button> element which was designed to submit forms to a server never needs to be repeatedly tapped. But in a game, the user may repeatedly tap to fire a weapon or move a gamepiece. On mobile, these repeated taps will rather surprisingly zoom in on the button. [46] These elements must be given the magical touch-action:manipulation; CSS style to hint that double-tapping should not be interpreted by the browser. One more thing to know.
What do you call it? This is a very small thing, but induced a fair amount of rework for me so I will mention it here: My game has on-screen instructions which tell the user how to play. A web page about raccoons would not need to provide instructions saying, "here is how to scroll down! Here is how to click hyperlinks!" ...but a game does need this! My original prototype had a lot of "click the arrow to continue" type text, but the word "click" is not intuitive language for someone using a touch screen. I could have left it in, figuring that most people could make the deductive leap that anything that could be "clicked" with a mouse they didn’t have could also be "tapped" using their available fingers. Since I wanted the game to feel as native as possible, I didn’t want to leave this in. But I couldn’t think of any single verb that would feel equally intuitive to both mouse and touch users? [47] So I added complexity to the game to detect the presence of a touch screen, which is much harder than it sounds, and use the word "click" or "tap" depending on the device.
Hovering
Not because hovering is the most pivotal interaction, but it’s a simple example of a willful divergence: When the user places their mouse inside a region of the screen, that region’s "hover" interactions will trigger. This can be used for non-essential visual texture such as slightly brightening a button to cue its interactivity. But it could be (and is) used for anything else, like revealing a hidden sub-menu which is critical to the user’s task success.
Arguably, mobile browsers could have been designed to give touch screen users a way to use interfaces designed for a mouse. As a strawman example, Safari for iPhone could show a little crosshair that you move around with your finger. When the crosshair enters a region of the screen, that region’s :hover interaction could be shown, giving the touch screen user the same experience as a mouse user. But mobile browser designers purposefully chose to take away the ability to easily express hovering. I’m not saying this was a bad choice: interpreting finger motions as immediate actions saves the user time and dexterity difficulties. Moving a crosshair around might be annoying and difficult.
Apple probably set this standard by being the first to sell an extremely popular touch screen web browser, so they made this decision for all of us. Right or wrong, this decision means that users can’t experience hover interactions on their iPad. If a developer creates an important hover-based interaction, then they have introduced an unsolvable design flaw into their user interface which they won’t discover without a mobile testing lab.
Is :hover a web standard? Supposedly yes. MDN and caniuse both list it as "widely available" and describe it as "fully supported" on mobile browsers. Caniuse mentions an iOS Safari glitch, but doesn’t say "this will basically never work on mobile" even though it should. [48] MDN is better, with a blue callout warning the developer to consider "accessibility" on "devices with limited hovering capabilities". This is technically true but undersells the case pretty significantly. Here’s what MDN should say:
Hover is effectively unusable on the world's most popular browsers and devices.
Web projects which seek to work consistently for all users should refrain entirely from using this feature.
The web standards don’t say this because web standards aren’t trying to create a consistent experience for all users. There is tribal knowledge amongst seasoned professional web developers that hover "shouldn’t" be used for any "critical" interactions like providing the user with instructions, or concealing interactive elements necessary for primary task completion. But if our novice designer/developer created a hover-based interaction and it worked on their computer, they will not discover until mobile testing that they have stepped outside the safe harbor, and now need to majorly rework their design.
Soft keyboard
There’s a bunch of completely reasonable interactions that a novice developer could build without thinking about the fact that they depend upon the user having a physical keyboard. Most mobile users have access to only a soft keyboard, which (a) lacks a few capabilities and (b) consumes an exhorbitant amount of screen space. This screen space is almost always better allocated to something else, which is why mobile browsers aggressively hide the soft keyboard. Here’s some problems that are easy to hit:
Shift-click
An early prototype of my game accelerated certain interactions with shift-click. But shift-click is not offered on a mobile device, so these interactions had to be redesigned for mobile. [49] MDN lists the shiftKey property of MouseEvent as "widely supported", even though soft keyboards on mobile will NOT deliver this event with shiftKey set to true, ever. Safari at least has the honesty to say that it’s unsupported on TouchEvent. The rest of the browsers document it as "supported" even though this will 100% not do what the developer intends.
You could argue that MouseEvent is obviously unsafe on mobile because there is no mouse. But despite the name, mobile browsers do work hard to transparently support the "click" event when the user taps, and they deliver a MouseEvent when the touch happens. There was clearly some attempt to ensure that MouseEvent was part of the "safe harbor" we all wish existed, but it’s not a complete attempt.
I hope at this point you’re yelling at your screen, "these are accessibility design problems! You can’t expect MDN to protect you from not designing properly!!!" And to that I say — I hear you, but, are we sure about that? Consider: One major reason why accessibility is so bad across modern computer interfaces is that developers must do something extra to offer accessibility. But these secondary quality characteristics will always be, well, secondary. One way we could have ensured that designs are accessible is to make it impossible to build anything else. Instead, we’ve filled the standard web API with conditional features that don’t work for most people, and then we describe them as "widely supported". We are making this problem worse when we could be making it better.
Text input

The <input> tag is like 30 years old, but that has apparently not been enough time for us to figure out how to make it usable! My original game design had a good amount of typing in it, to let the user input their guesses to solve a mystery. When I first tested this experience on mobile, I found that the soft keyboard’s consumption of the screen almost always covered up or reflowed an important clue that the user needed to read as they typed their guess. [50] Worse, the game is wide-screen so the user would likely have their phone tilted to landscape mode. In this orientation, the soft keyboard consumes much more than 50% of the vertical screen space. What’s left is almost entirely consumed by the text input area. [51]
This seemingly reasonable allotment of space leaves nothing for the clue context. This made the game unplayable and required me to dramatically rework the design to eliminate typing. I spent more engineering time on this rework than on any other single change for mobile. If I had prototyped on mobile first, I would have realized this problem much sooner.
Input focus
With a hard keyboard device, calling .focus() on a likely input element can be an incidental convenience. It gives the user a very gentle hint at interactivity, and saves them a click if they would like to start typing. But they can also easily ignore the focused element, and click something else on the screen if they like.
Soft and hard keyboards both need the unfortunate concept of keyboard focus, usually indicated by some extra bold outlines and a blinking vertical line which users understand to be a "cursor". Unlike on a laptop, a soft keyboard also needs a cue for when it is wanted. Showing the soft keyboard when it is unwanted is an annoyance because of how much screen space it wastes. But failing to show the soft keyboard when it is wanted fully prevents primary task completion. They need to type, and cannot. Disaster.
So I definitely sympathize with mobile browsers that show the soft keyboard on .focus() of any input element. But if any web page does focus an element, the soft keyboard will take over most of the screen. [52] The user will be shut out of all other tasks until they dismiss the gigantic soft keyboard. This means that calling .focus() should only be done when the input is assured to be the primary task. It cannot be used incidentally like it can on desktop.
Responsive layout: yes you have to
A lot of the documentation at large about designing for mobile will mention "responsive design". You could be forgiven for thinking that the entirety of this concept is just learning how to set up the confusingly named media queries to conditionalize a web page’s layout. There is a lot of advice about responsive design out there, not all of it wise. So here’s what I think is the heart of it:
Design for mobile first
Unless your HTML5 project is a static list of raccoon facts shown in a single column of black text on a white background, it is unlikely that a UX designed for a desktop browser will also be usable on a phone. [53] You’re more likely to have a phone design look okay on desktop than the other way around. So if you really really don’t want to create two separate designs, you need to iterate your design on a mobile phone first. (Or at the very least a mobile simulator like is integrated into Chrome and Firefox.)
You probably need at least two layouts
Web designers these days seem unable to resist cramming the margins of web pages with hamburger menus, nav bars, chat bots, and ads. None of these will look right on a phone’s tall, narrow screen. [54] So if you can’t refrain from multi-column layout, you will need to make at least two different layouts: a single-column layout for phones and then a multi-column layout for desktop browsers.
No you can’t control the real size of anything
CSS has a wealth of units which you might imagine can give you all the options you need to make text and controls the right size on screens big and small. But these are all lies, even the ones like cm and mm which sound like they should, you know, be SI distances like we learned in school. Actually, a CSS cm is defined a