Half-Pixel Hell: How Obsessing Over Decimal Dimensions Is Quietly Destroying Mobile Performance
There's a specific kind of developer pain that doesn't show up in Lighthouse scores until it's already too late. It doesn't throw console errors. It doesn't break layouts in Chrome DevTools. It just quietly degrades the experience for the people using a $300 phone on a Tuesday afternoon — which, statistically, is a huge chunk of your actual user base.
We're talking about subpixel rendering. And more specifically, we're talking about what happens when designers and developers fall in love with decimal values that browsers were never really built to handle cleanly.
What's a Subpixel, Anyway?
Physical screens are made of pixels. That's the floor. A pixel is the smallest unit a display can actually light up. But CSS? CSS operates in a virtual coordinate space that doesn't have to align with those physical pixels — especially on high-DPI screens where one CSS pixel might map to two, three, or even four physical pixels.
Subpixel rendering is the browser's attempt to fake precision it doesn't technically have. When you declare a width of 33.33% or a font-size of 12.5px, the browser has to figure out how to represent that value across a grid of whole-number physical pixels. It does this through anti-aliasing — blending colors at the edges of elements to create the visual illusion of smooth, precise positioning.
On a desktop with a powerful GPU and a consistent display density, this is mostly fine. On a mid-range Android device with a variable pixel ratio and a browser engine that's already juggling compositing layers, touch event handling, and scroll performance? It's a tax. A real one.
The Math Nobody Does Before Shipping
Here's where it gets measurable. When a browser encounters a subpixel value, it doesn't just round and move on. It triggers what's known as a subpixel accumulation problem — particularly in flex and grid layouts where percentage-based widths are summed across multiple children.
Say you've got a three-column grid, each column set to 33.33%. The browser calculates the pixel width of each column independently, rounds each one, and then discovers the total doesn't quite add up to the container width. So it compensates — sometimes by adding a pixel to one column, sometimes by leaving a fractional gap. And every time the viewport resizes, or the layout reflows, it does this math again.
Multiply that across a page with dozens of components — cards, grids, type scales built on modular ratios — and you're looking at a non-trivial amount of layout recalculation that happens continuously during scroll, resize, and interaction events.
On a Pixel 7 or an iPhone 15, you might never notice. On a Galaxy A15 — which outsells the flagship by a wide margin in the US prepaid market — you'll feel it as jank.
The Font-Size Problem Is Worse Than the Width Problem
Percentage widths are annoying. Fractional font sizes are actively hostile to rendering performance.
When you set a font to 12.5px or use a type scale that produces values like 13.7px, the browser has to make decisions about how to render each individual glyph. Text rendering is already one of the most expensive operations in a browser's paint pipeline — it involves hinting, kerning, and anti-aliasing at the character level. Subpixel font sizes force the engine into additional rounding decisions for every character, on every line, in every text node.
On desktop browsers, font smoothing is often handled by the OS (macOS's Quartz renderer is famously opinionated about this). On Android, Chrome's Skia-based rendering pipeline handles it directly, and it's significantly more sensitive to fractional values — especially at small sizes where the difference between a rounded and unrounded glyph is visually meaningful.
The result: more paint time, more composite layers, and a text rendering pass that's doing more work than it needs to.
How This Actually Shows Up in the Wild
If you've ever profiled a page in Chrome DevTools and seen oddly expensive paint events that don't correspond to any obvious animation or interaction, subpixel chaos is often a contributing factor. Look for:
- Layout thrashing during scroll on pages with percentage-based grid children
- Long paint times in the Performance panel that correlate with text-heavy sections
- Composite layer explosions where the browser promotes more elements than expected to GPU layers as a workaround
The frustrating part is that none of this is obvious from the code. 33.33% looks perfectly reasonable. 12.5px looks like someone was being thoughtful about their type scale. The problem only surfaces when you measure on real hardware, in real conditions.
Fixes That Don't Require Throwing Out Your Design System
The good news is that you don't have to abandon precision to fix this. You just have to be precise in a way that the browser can actually work with.
Use whole-number pixel values for font sizes. If your type scale produces 12.5px, round it to 12px or 13px. The visual difference is imperceptible. The rendering difference is not.
Swap percentage widths for fr units in CSS Grid. The fr unit distributes available space without forcing the browser to do subpixel accumulation math. 1fr 1fr 1fr is cleaner than 33.33% 33.33% 33.33% in every measurable way.
Use calc() with intention. If you need a percentage-based layout with a fixed gutter, calc(33.33% - 16px) is still going to produce subpixel values. Consider whether gap with fr units solves the same problem without the math.
Set text-rendering: optimizeSpeed on body text. This tells the browser to skip some of the more expensive glyph-level optimizations for large blocks of text. It's not always the right call for headlines, but for body copy at small sizes, it reduces paint overhead noticeably.
Test on actual mid-range hardware. Chrome DevTools' CPU throttling is useful but not a perfect proxy for a real device. If your team doesn't have a budget Android device in the office for testing, it's worth getting one. The $200 you spend on a test device will tell you more than any synthetic benchmark.
Precision Is a Design Value — Performance Is a User Value
The tension here isn't really between designers and developers. It's between what looks correct in a design tool and what actually performs well in a browser. Figma renders everything in a vector canvas with no concept of pixel grids or rendering pipelines. It will happily show you 12.5px text that looks perfect, because for Figma, it is perfect.
The browser is working in a different reality. It has physical pixels to map to, a rendering budget to stay within, and users who notice when things feel sluggish even if they can't explain why.
Subpixel anti-aliasing isn't a bug — it's the browser doing its best with values that don't cleanly map to the physical world. The fix isn't to blame the browser. It's to stop feeding it problems it has to approximate its way out of.
Your users on mid-range hardware will thank you. They just won't know why.