Pixel snapping: keeping icons crisp on low-density screens
Learn how anti-aliasing blurs icon edges that fall between pixels, how to align edges and strokes to the pixel grid, and why it still matters at 16px.
6 min read
An icon can be drawn perfectly and still look soft. The usual cause is that its straight edges fall partway across a pixel, so the screen paints a gray fringe where you expected a sharp line. On a 2x or 3x display you barely notice. On a standard 1x monitor, and especially at 16 px, it's the difference between a crisp icon and a slightly blurry one.
Pixel snapping means placing straight edges exactly on pixel boundaries. This guide explains the mechanism, shows the coordinates that work for fills and strokes, and covers the places where snapping gets undone: scaling, page layout and fractional display scaling.
How anti-aliasing turns edges gray
A screen pixel can only show one color. When a shape's edge passes through the middle of a pixel, the renderer works out how much of that pixel the shape covers and blends the colors in proportion. A black shape covering half of a white pixel produces a pixel of roughly 50% gray.
That's anti-aliasing, and for curves and diagonals it's exactly what you want: it smooths what would otherwise be a staircase. For horizontal and vertical edges it's a cost. An edge at x = 4 lines up with a pixel boundary and renders as a clean switch from one color to the other. An edge at x = 4.5 renders as a column of half-strength pixels, so the edge looks a pixel wide and fuzzy.
The effect is strongest on thin parts. A 1 px bar that starts on a pixel boundary is one row of solid color. The same bar shifted by half a pixel becomes two rows of half-strength color: twice as wide, half as dark, and noticeably blurry.
Aligning filled shapes
For filled shapes, the rule is simple: at the size you render, put horizontal and vertical edges on whole pixels. When the icon renders at the same size as its canvas, such as a 24-unit drawing shown at 24 px, whole units are whole pixels.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<rect x="3" y="4.5" width="18" height="3" fill="currentColor"/>
<rect x="3" y="10" width="18" height="3" fill="currentColor"/>
</svg>
Both bars are 3 units tall. The first one's top and bottom edges sit at 4.5 and 7.5, so at 24 px it renders as two solid rows with a half-strength row above and below. The second one's edges sit at 10 and 13, so it renders as exactly three solid rows. Same shape, very different sharpness.
Strokes and the half-pixel rule
Strokes add a twist: a stroke is centered on its path, so half its width falls on each side.
| Path position | Stroke width | Covers | Result at 1 unit = 1 px |
|---|---|---|---|
| y = 8 | 1 | 7.5 to 8.5 | Two half-strength rows, blurry |
| y = 12.5 | 1 | 12 to 13 | One solid row, crisp |
| y = 17 | 2 | 16 to 18 | Two solid rows, crisp |
| y = 17.5 | 2 | 16.5 to 18.5 | Three rows, the outer two at half strength |
The rule of thumb: odd stroke widths go on half-unit coordinates, even widths go on whole ones. This is why line icons drawn with a 2-unit stroke on a 24 grid are easy to keep crisp at 24 px, and why 1-unit strokes keep ending up on .5 coordinates.
Line caps matter too: a butt cap ends exactly at the endpoint, while a square cap extends by half the stroke width and moves the end edge with it. Many designers convert strokes to filled outlines for production so every edge is explicit; strokes or fills covers that trade-off.
Snapping holds at one size only
Coordinates that are crisp at 24 px aren't automatically crisp at other sizes. At 16 px, each unit of a 24-unit drawing becomes two-thirds of a pixel, so an edge at x = 4 lands at 2.67 px. Only multiples of 3 units land on whole pixels at 16 and 32 px; why icons are drawn on a 24 × 24 grid has the full table.
Fractional display scaling shifts the target again. Windows laptops are commonly set to 125% or 150%, which gives a device pixel ratio of 1.25 or 1.5:
| Icon size | Device pixel ratio | Device pixels | Crisp coordinates on a 24-unit drawing |
|---|---|---|---|
| 16 px | 1 | 16 | Multiples of 3 |
| 16 px | 1.25 | 20 | Multiples of 6 |
| 16 px | 1.5 | 24 | Every whole unit |
| 24 px | 1 | 24 | Every whole unit |
| 24 px | 1.25 | 30 | Multiples of 4 |
| 24 px | 1.5 | 36 | Even units |
You can't snap to every combination, so pick the one that matters most. For your own icons, that might mean a dedicated 16 px drawing; designing token icons that survive at 16px covers when that's worth it. For a set used at many sizes, putting key edges on multiples of 3 units keeps them crisp at 16, 24, 32 and 48 px on both 1x and 2x screens.
Layout can undo it
A perfectly snapped icon still blurs if the page places it at a fractional position. Common causes:
/* 20px icon centered in a 31px row: 5.5px of space above it */
.row { height: 31px; display: flex; align-items: center; }
/* even leftover space centers on a whole pixel: (32 - 20) / 2 = 6 */
.row { height: 32px; display: flex; align-items: center; }
/* a 13px badge centered with a transform lands on a half pixel */
.badge { width: 13px; height: 13px; transform: translate(-50%, -50%); }
Whether the browser rounds a fractional position to a whole pixel depends on the browser and on how the element is drawn. Transforms in particular often keep the fraction. Line heights cause the same problem indirectly: line-height: 1.5 on 15 px text gives a 22.5 px line, and everything centered on it inherits the half pixel.
Give icon slots and their containers sizes with an even difference, so centering divides cleanly. Use whole-pixel offsets instead of percentage transforms for small positioned elements, such as a chain badge on a token icon (see network badges). Set line heights in pixels, or pick font sizes that multiply out to whole numbers.
Why it matters less on dense screens, but still matters
On a 2x display, a half-pixel offset in CSS is a whole device pixel, so that fringe disappears, and any remaining blur is physically half as wide. On a 3x phone screen it's harder still to see.
Small sizes are the exception. A 16 px icon on a 2x screen has only 32 device pixels across, and each unit of a 24-unit drawing is still a fraction of one, so thin lines and narrow gaps still soften. On 1x external monitors and fractionally scaled laptops, which are still common, everything here applies in full. Set the device pixel ratio to 1 in your browser's DevTools to see what those users see.
Snapping someone else's logo
Snapping is production cleanup, not redesign. On a third-party logo, moving a horizontal or vertical edge by a fraction of a unit is hard to notice at large sizes and can sharpen it noticeably at 24 px. Keep moves small, leave proportions alone, and don't touch curves or diagonals; anti-aliasing is what makes them look smooth.
Avoid shape-rendering="crispEdges" on logos. It asks the browser to turn off anti-aliasing, which hardens straight edges but can shift them by up to half a pixel and gives curves jagged, stair-stepped edges. It suits strictly rectangular UI shapes only.