Half-Point CSS Borders in iOS

 — 7 Min Read

iOS 7 embraces retina displays by increasing the prominence of half-point line work; a welcome move, but not one that's easy to replicate with web-based code. I recently had to draw a series of 1px/0.5pt lines for a web view in an iOS app. There's no prescribed way to do this and for each case the best approach is quite different.

Most developers are tackling this with the box-shadow property by manipulating the spread and blur-radius in a way that only a portion of the shadow is shown. But this offers limited control and tends to create unpredictable results, especially if you need to use a precise colour, which is likely to be the case.

Instead, I found using pseudo-elements and a combination of overshooting the desired size of a line by a factor of two and then scaling it back with CSS transforms to be more useful. Here's an example:

pre {
    position: relative;
    margin: 12px;
}
pre::before,
pre::after {
    content: '';
    position: absolute;
    left: 0;
    background: #ddd;
    width: 100%;
    height: 1px;
    transform: scaleY(0.5);
}
pre::before {
    top: 0;
    transform-origin: 0 0;
}
pre::after {
    bottom: 0;
    transform-origin: 0 bottom;
}

With this code, I'm creating a pseudo-element at the top and bottom of my pre element at double the desired height (1px in CSS translates to 2 actual pixels on a retina display). I'm then using transform to scale it back to half it's size. The key for pixel-perfection is also to adjust the transform-origin so that the transformation is handled from a defined point.

The above example is ideal for top and bottom rules, but doesn't cover the case where you want to draw a border around an element on all four sides. To do that, we only need one pseudo-element but there are some important additional properties to define:

pre {
    position: relative;
    margin: 12px;
}
pre::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    border: 1px solid #ddd;
    box-sizing: border-box;
    width: 200%;
    height: 200%;
    transform: scale(0.5);
    transform-origin: left top;
}

To achieve this, we need to change the box model with box-sizing as we're going to use actual CSS borders. This time we double the width and height then scale it back and set the transform-origin. A great advantage of this is that properties like box-shadow and border-radius are still available to us—just remember that all units need to be doubled.

One point to note is that this breaks the ability to select text on the pre element's contents. To restore that functionality, you can set negative z-index values on the pseudo-element, or use a child node to pull the text on top of the pseudo-element.

So far I've used this method to draw top and base rules on block elements, add four-sided borders to inline elements, hr elements and even draw horizontal and vertical divisions of a table. It's not going to cover every scenario but it's a good starting point and appears to be the most robust approach.

Notes:

  1. I've omitted the prefixed versions of some properties for the sake of simplicity. Needless to say, make sure you include those for production.
  2. These (and other approaches to this problem) are all hacks and I would only recommend using them in web views or cases where you can guarantee only iOS will be rendering them.