[css-borders] Add a 'hairline' border-width value · Issue #3720 · w3c/csswg-drafts (original) (raw)
People regularly ask for the ability to size things in device pixels (#3715 is the latest). This is virtually always intended for the use-case of sizing borders, to create "hairline" borders or separators.
Since adding an actual device pixel unit is out of the question, we should still address this use-case directly, and add a hairline
keyword to 'border-width', representing "the thinnest width that the UA believes is recognizable, snapped to the nearest whole number of device pixels". Right now, that'll be 1 device pixel, but if screens get significantly higher-res, it'll probably resolve to whatever number of device pixels gives you a value between 1/3 and 1/2 of a CSS px.
Why can't we add a device pixel unit, so you could just say border-width: 1dp
?
Because device pixels are unpredictable in size. If you try to size anything bigger than a hairline with device pixels, the size will, today, vary by more than 300%, as there are devices that have 1dp==1px, and devices with 3dp=1px, and I think some devices are pushing higher than that. As display technology gets better, this gulf will continue to grow, as there will likely always be 1dp==1px devices in use.
We already know from past experience that authors can't generally handle units with an unpredictable "sliding scale" ratio; the CSS2 spec used to have in
and px
decoupled, so that in
was as close to an actual inch as the browser could get, and px
was the nearest whole number of device pixels approximating the visual angle of a device pixel on a 96dpi monitor. However, so many authors used the physical units (pt
/in
/cm
/mm
) to size their pages while assuming 96px=1in that browsers implementing the spec got broken pages on devices whose in-px ratio was different. And that was a ratio that could differ whose min and max could differ by, at most, 50%! (Handling an unpredictable ratio is a fundamentally very difficult problem! We have better, less fragile layout tools to help with that these days, but it's still virtually guaranteed to result in problems if we expose this.)
Plus, the notion of a "device pixel" is screen-specific; when printing, the analogue to "device pixel" is the "dot", the smallest drop of ink that can be placed at a particular location. Printers not only have much smaller dots than screens have pixels, and in a larger range (consumer printers can vary from 300dpi to more than 1000dpi), they also vary based on color; black-and-white dpi is often much higher than color dpi. Sizing anything based on "device pixels" will either be wildly unpredictable on printers, or else it'll be a "virtual device pixel" that doesn't correspond to anything physical anyway.
And all this isn't even getting into screens that simply more exotic than the "rectilinear grid of hardware pixels" design that this sort of feature assumes. Some screens layer their pixels in a non-square grid. Some have differently-sized pixels, layered in separate rectilinear grids. Some have different grids for each subpixel color. All in all, the notion of a "device pixel" as something you can naively size things in is already nonsensical for many output devices, and the situation is likely to only get worse as time goes on.
Why not a device-pixel rounding function?
In #3715 @florian and I talk about a function that rounds a length to the nearest number of device pixels, as a compromise that delivers many of the benefits of device-pixel sizing without as many of the downsides. On further consideration, I don't think we want this either.
For the case of hairline borders, a device-rounding function achieves basically the same thing as a 'hairline' value, just in a slightly more complicated way. Users have to know to write border-width: dp-round(.5px)
or what-have-you, rather than border-width: hairline;
. The only theoretical benefit of dp-round()
over hairline
is that it lets you distinguish between whether you really want .33px or .5px, when that distinction might matter for visibility, but in practice that's a wash anyway, since a 3x screen will still probably choose .33px over .67px if you say dp-round(.5px)
, and a 2x screen will still use .5px if you say dp-round(.33px)
, and many devices will end up still choosing 1px anyway, so the theoretical gain in precision is lost in practice. In a theoretical future with super high-res screens, it might become relevant; we can revisit the question at that point. ^_^
For any other case, rounding to the device pixel doesn't do anything meaningful unless you're also rounding the position. Otherwise it's possible that the box is exactly 200dp tall, but it's offset 150.2dp from the top of the screen, so it's still fuzzy. And unless we offer position-rounding as some sort of separate property switch, you have to instead use dp-round() on your ancestors, too, all the way up to the root. (And depending on how you're being laid out, that still might not be enough; flexbox alignment has no way to force a rounding on the gap between flex items, for example.)
So, dp-round() is overkill complexity for hairlines, and doesn't do anything useful for most non-hairline cases.