How to Achieve True 1px Borders on High‑DPI Mobile Screens

This article explains why 1 px borders appear thick on high‑resolution mobile devices, explores the role of devicePixelRatio, and presents five practical solutions—including iOS 0.5 px borders, border‑image, box‑shadow, pseudo‑elements, and viewport scaling—along with their pros, cons, and common pitfalls.

MaoDou Frontend Team
MaoDou Frontend Team
MaoDou Frontend Team
How to Achieve True 1px Borders on High‑DPI Mobile Screens

Introduction

Mobile web projects are increasing, and designers demand UI elements such as 1 px borders. On high‑resolution screens a CSS 1 px becomes thick, so a “fake” 1 px is often used.

Fake 1px border example
Fake 1px border example

This is a real 1 px border.

Real 1px border example
Real 1px border example

1. Cause

The issue is caused by the devicePixelRatio (DPR), which is the ratio of physical pixels to CSS pixels.

window.devicePixelRatio = physicalPixels / CSSPixels

Most screens have DPR = 2 (e.g., iPhone 8) or DPR = 3 (e.g., iPhone 8 Plus). To draw a physical 1 px on a DPR = 2 screen you need 0.5 CSS px, etc. iOS 8+ supports 0.5 px, while Android does not.

2. Solutions

1. WWDC iOS solution

WWDC recommended using a 0.5 px border, which renders as a physical pixel on iOS. border: 0.5px solid #E5E5E5; Chrome on iPhone 8 Plus does not render below 0.46 px.

Pros: simple, no side effects

Cons: works only on iOS 8+, not Android

2. Border image

Use a 1 px transparent border with a border‑image.

border: 1px solid transparent;
border-image: url('./../../image/96.jpg') 2 repeat;

The image can be custom‑made.

Border image example
Border image example

Pros: simple, no side effects

Cons: need to recreate the image when the color changes; rounded corners become blurry

3. Box‑shadow

Use box-shadow to draw thin lines.

box-shadow: 0 -1px 1px -1px #e5e5e5, /* top */
            1px 0 1px -1px #e5e5e5,  /* right */
            0 1px 1px -1px #e5e5e5,  /* bottom */
            -1px 0 1px -1px #e5e5e5; /* left */

The first two values control which side, the next two control blur and spread.

Pros: easy, works with rounded corners

Cons: looks like a shadow rather than a border

4. Pseudo‑element

Most used method: create a ::after pseudo‑element and scale it.

.setOnePx{
  position: relative;
  &::after{
    position: absolute;
    content: '';
    background-color: #e5e5e5;
    display: block;
    width: 100%;
    height: 1px;
    transform: scale(1, 0.5);
    top: 0;
    left: 0;
  }
}

Scaling the Y axis by 0.5 creates a true 1 px line.

For a border on all sides:

.setBorderAll{
  position: relative;
  &::after{
    content: " ";
    position: absolute;
    top: 0;
    left: 0;
    width: 200%;
    height: 200%;
    transform: scale(0.5);
    transform-origin: left top;
    box-sizing: border-box;
    border: 1px solid #E5E5E5;
    border-radius: 4px;
  }
}

Pros: works on all devices, true 1 px, supports rounded corners

Cons: uses ::after, which may affect float clearing

5. Viewport scale + rem + JS

Adjust the viewport scale according to DPR and set the root font size.

<html>
  <head>
    <title>1px question</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <style>
      html{font-size:1px;}
      *{padding:0;margin:0;}
      .top_b{border-bottom:1px solid #E5E5E5;}
      .a,.b{box-sizing:border-box;margin-top:1rem;padding:1rem;font-size:1.4rem;}
      .a{width:100%;}
      .b{background:#f5f5f5;width:100%;}
    </style>
    <script>
      var viewport=document.querySelector('meta[name=viewport]');
      if(window.devicePixelRatio==1){viewport.setAttribute('content','width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no');}
      if(window.devicePixelRatio==2){viewport.setAttribute('content','width=device-width,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no');}
      if(window.devicePixelRatio==3){viewport.setAttribute('content','width=device-width,initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no');}
      var docEl=document.documentElement;
      var fontsize=32*(docEl.clientWidth/750)+'px';
      docEl.style.fontSize=fontsize;
    </script>
  </head>
  <body>
    <div class="top_b a">Bottom border width is virtual 1px</div>
    <div class="b">Top border width is virtual 1px</div>
  </body>
</html>

Pros: works on all devices, simple 1px usage

Cons: suitable for new projects; legacy projects may require extensive changes

3. Pitfalls

Various issues were encountered, especially when using pseudo‑elements inside pseudo‑classes and with elements that do not support pseudo‑elements (e.g., textarea).

For a vertical line set width:1px; height:100%; and scaleX(0.5); for a horizontal line set width:100%; height:1px; and scaleY(0.5).

4. Conclusion

For new projects the viewport‑scale method is recommended because of its compatibility and ease of use. For existing projects the pseudo‑element + transform method is the most practical. Border‑image and box‑shadow methods are less flexible and have poorer compatibility.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

mobileFrontend Development1px borderdevicePixelRatio
MaoDou Frontend Team
Written by

MaoDou Frontend Team

Open-source, innovative, collaborative, win‑win – sharing frontend tech and shaping its future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.