8 May 2018

Grand Unified CSS Padding

Responsive, fluid and proportional padding on any screen in any orientation — with one line of CSS and no media queries. (!)

How is this possible, you might ask? The key is the "Weighted Viewport Average" unit (or the wva, if you're willing to take the leap with me). Okay, no, it's not an actual CSS unit, but I find it to be such a useful technique that it was begging for a name.

The backstory: like a lot of web developers, I've been looking for a better approach to CSS padding. I was sick of adding media query after media query to handle an ever-widening number of edge cases, and struggling to wrangle fluid viewport units into producing a consistently useable output.

Here was my wishlist:

  • Browser Support — 95%+ support on caniuse with good fallback options.
  • Responsive + Fluid — adapts to a wide range of screen sizes and shapes without the need for breakpoints.
  • Aspect Ratio-Proportional — vertical and horizontal padding are proportional to their respective screen dimensions. A mobile device held in portrait orientation needs less horizontal and more vertical padding, while a widescreen desktop monitor needs the opposite.
  • Simple — Doesn't require lots of media queries or dozens of lines of code.

Attempt #1:

Fixed Units 🙁

The old-school method. Basic px units and a bunch of media queries. Great if you need exact, to-the-pixel control or if your site needs to work in a prehistoric version of Internet Explorer.

Here in modern times though, fixed units aren't quite as useful as they used to be.

Code

.padding-normal{
  padding:20px;}
@media screen and (min-width:250px){
  .padding-normal{
    padding:30px;}
}
@media screen and (min-width:600px){
  .padding-normal{
    padding:40px;}
}

Pros + Cons: Fixed Units

  • Browser Support
    • ✓ Yes Supported by practically every browser in existence.

  • Responsive + Fluid
    • ✗ No Responsive-ish with enough media queries, but not fluid.

  • Proportional
    • ✗ No Proportional-ish only with more media queries.

  • Simple
    • ✗ No Not so simple once you add all those media queries.

Demo: Fixed Units

Hover to visualize / resize to test.

Note the way the padding jumps as you resize across the horizontal breakpoints, and doesn't respond to changes in the vertical size of the viewport.

Attempt #2:

Fluid Units 😕

On paper, viewport units are exactly what we're looking for: simple, fluid, and proportional. But there's a catch — by themselves vh and vw units are a little too proportional for use in padding. In very wide or very tall viewports, they're too extreme. The wides are too wide and the narrows are too narrow.*

Code

.padding-normal{
  padding:10vh 10vw;}

Pros + Cons: Fluid Units

  • Browser Support
  • Responsive + Fluid
    • ✓ Yes Completely, fluidly responsive.

  • Proportional
    • Yes, but... a little too proportional, doesn't look right in tall or wide viewports.*

  • Simple
    • ✓ Yes A single line of CSS.

Demo: Fluid units

Hover to visualize / resize to test.

Note how when the viewport gets very short, the vertical padding shrinks to almost nothing while the horizontal padding remains at full size (and vice versa).

*This is subjective, of course. If you think plain vh/vw units look fine, vaya con dios my friend.

Attempt #3:

Weighted Viewport Average units 😎

Enter the wva.

Since plain viewport units are a little too extreme, let's use calc() to temper them some. I started by averaging vh and vw together to create the equivalent of a "viewport average" unit:

calc((1vh + 1vw) / 2)

which can be reduced to:

/* Viewport Average (va) unit */
calc(.5vh + .5vw)

But a straight 50/50 average of both units wasn't quite right either, so I split the difference again and started playing with a ratio of around 2.2 main-axis units to 1 cross-axis unit...

...and that's when it all clicked into place.

/* Weighted Viewport Average (wva) unit pair  */
calc(.7vh + .3vw) calc(.3vh + .7vw)

This was it.

No matter what ridculous shapes I stretched my browser window into, the padding always looked right. As long as the ratio of main-axis units to cross-axis units is in the 2:1 to 3:1 range, it's pretty bulletproof.

By averaging vh and vw and weighting that average towards the main axis (vh for vertical y-values, vw for horizontal x-values) the proportionality is preserved. And because the vertical and horizontal padding are linked, it prevents their values from becoming too divergent. No tiny padding in one dimension and giant padding in the other.

Code

/* 10 wvas */
.padding-normal{
  padding:calc(7vh + 3vw) calc(3vh + 7vw);}

Pros + Cons: "Weighted Viewport Average" Units

  • Browser Support
  • Responsive + Fluid
    • ✓ Yes Completely responsive and fluid.

  • Proportional
    • ✓ Yes Pleasantly proportional on nearly any screen.

  • Simple
    • ✓ Yes A single line of CSS.

Demo: "wva" units

Hover to visualize / resize to test.

Conclusion

Depending on how excited you get about CSS, this might not seem like much — but to me this was like discovering magic.

Good-looking padding on any device in any orientation with one line of CSS? That's crazy talk. That's fantasy land. There has to be a catch, it must rely on a javascript plugin or some bleeding-edge non-standard browser feature that's five years away from adoption.

But there's no catch. Here it is, supported in ~95% of browsers at the time writing, ready to use — the wva unit.

Tips

  • Don't forget, the "weighted" part of the wva technique means that it has to be used as a pair of x/y values.

  • If you've read this far, you've probably already guessed this – the wva technique is useful for more than just padding. It plays nicely with any CSS property that has a dimensional aspect: margin, border, positioning controls (left, right, top, bottom), et cetera.

  • For my SASS users out there, here's a little function that does the ratio math automatically for you:

    /* SASS wva-pair function */
    @function wva-pair($multiplier) {
      $ratio: 2.2;
      $cross_axis_unit: 1 / ($ratio + 1); // solve for ratio(x) + x = 1
      $main_axis_unit: 1 - $cross_axis_unit;
      $wva-pair:
        calc(#{$multiplier * $main_axis_unit}vh + #{$multiplier * $cross_axis_unit}vw)
        calc(#{$multiplier * $cross_axis_unit}vh + #{$multiplier * $main_axis_unit}vw);
      @return $wva-pair;
    }
    // ex: "wva-pair(10)" evaluates to "calc(6.875vh + 3.125vw) calc(3.125vh + 6.875vw)"

Notes

  • Browser support is pretty solid for calc() and vh/vw (every modern browser is good except Opera Mini, basically) but websites without padding can get pretty unusable, so it's good to have a fallback just in case. Just add a sensible fixed-unit fallback, and then wrap the fancier stuff in a @supports feature query.
  • When the viewport height changes — say you tap a text input on mobile and the keyboard appears, for example — the padding will resize slightly. In most cases it won't cause any issues, but it might be an unexpected behavior for some people.
  • Thanks to Travis Bernard's vmax workaround for the inspiration to get creative with calc() and viewport units.