Viewport units have been around for several years now, with near-perfect support in the major browsers, but I keep finding new and exciting ways to use them. I thought it would be fun to review the basics, and then round-up some of my favorite use-cases.
What are viewport units?
Four new “viewport-relative” units appeared in the CSS specifications between 2011 and 2015, as part of the W3C’s CSS Values and Units Module Level 3. The new units – vw
, vh
, vmin
, and vmax
– work similarly to existing length units like px
or em
, but represent a percentage of the current browser viewport.
- Viewport Width (
vw
) – A percentage of the full viewport width.10vw
will resolve to 10% of the current viewport width, or48px
on a phone that is480px
wide. The difference between%
andvw
is most similar to the difference betweenem
andrem
. A%
length is relative to local context (containing element) width, while avw
length is relative to the full width of the browser window. - Viewport Height (
vh
) – A percentage of the full viewport height.10vh
will resolve to10%
of the current viewport height. - Viewport Minimum (
vmin
) – A percentage of the viewport width or height, whichever is smaller.10vmin
will resolve to10%
of the current viewport width in portrait orientations, and10%
of the viewport height on landscape orientations. - Viewport Maximum (
vmax
) – A percentage of the viewport width or height, whichever is larger.10vmax
will resolve to10%
of the current viewport height in portrait orientations, and10%
of the viewport width on landscape orientations. Sadly, and strangely,vmax
units are not yet available on Internet Explorer or Edge.
While these units are derived from viewport height or width, they can all be used everywhere lengths are accepted – from font-size
to positioning, margins, padding, shadows, borders, and so on. Let’s see what we can do!
Responsive Typography
It’s become very popular to use viewport units for responsive typography – establishing font-sizes that grow and shrink depending on the current viewport size. Using simple viewport units for font-size has an interesting (dangerous) effect. As you can see, fonts scale very quickly – adjusting from unreadably small to extra large in a very small range.
This direct scaling is clearly too dramatic for daily use. We need something more subtle, with minimums and maximums, and more control of the growth rate. That’s where calc()
becomes useful. We can combine a base size in more steady units (say 16px
) with a smaller viewport-relative adjustment (0.5vw
), and let the browser do the math: calc(16px + 0.5vw)
By changing the relationship between your base-size and viewport-relative adjustment, you can change how dramatic the growth-rate is. Use higher viewport values on headings, and watch them grow more quickly than the surrounding text. This allows for a more dynamic typographic scale on larger screens, while keeping fonts constrained on a mobile device – no media-queries required. You can also apply this technique to your line-height, allowing you to adjust leading at a different rate than the font-size
.
body {
// font grows 1px for every 100px of viewport width
font-size: calc(16px + 1vw);
// leading grows along with font,
// with an additional 0.1em + 0.5px per 100px of the viewport
line-height: calc(1.1em + 0.5vw);
}
For me, this is enough complexity. If I need to constrain the top-end for rapid-growth headings, I can do that with one single media-query wherever the text becomes too large:
h1 {
font-size: calc(1.2em + 3vw);
}
@media (min-width: 50em) {
h1 {
font-size: 50px;
}
}
Suddenly I wish there was a max-font-size
property.
Others have developed more complex calculations and Sass mixins to specify the exact text-size ranges at specific media-queries. There are several existing CSS-Tricks articles that explain the technique and provide snippets to help you get started:
I think that’s overkill in most cases, but your milage will absolutely vary.
Full-Height Layouts, Hero Images, and Sticky Footers
There are many variations on full-height (or height-constrained) layouts – from desktop-style interfaces to hero images, spacious designs, and sticky footers. Viewport-units can help with all of these.
In a desktop-style full-height interface, the page is often broken into sections that scroll individually – with elements like headers, footers, and sidebars that remains in place at any size. This is common practice for many web-apps these days, and vh
units make it much simpler. Here’s an example using the new CSS Grid syntax:
See the Pen Full-height CSS Grid by Miriam Suzanne (@mirisuzanne) on CodePen.
A single declaration on the body
element, height: 100vh
, constrains your application to the height of the viewport. Make sure you apply overflow
values on internal elements, so your content isn’t cut off. You can also achieve this layout using flexbox or floats.Note that full-height layouts can cause problems on some mobile browsers. There’s a clever fix for iOs Safari, that we use to handle one of the most noticeable edge-cases.
Sticky-footers can be created with a similar technique. Change your body height: 100vh
to min-height: 100vh
and the footer will stay in place at the bottom of your screen until it’s pushed down by content.
See the Pen Sticky-Footer with CSS Grid by Miriam Suzanne (@mirisuzanne) on CodePen.
Apply vh
units to the height
, min-height
, or max-height of various elements to create full-screen sections, hero images, and more. In the new OddBird redesign, we constrained our hero images with max-height: 55vh
so they never push headlines off the page. On my personal website, I went with max-height: 85vh
for a more image-dominated look. On other sites, I’ve applied min-height: 90vh
to sections.
Here’s an example showing both a max-height heroic kitten, and a min-height section. Combining all these tricks can give you some powerful control around how your content fills a browser window, and responds to different viewports.
Fluid Aspect Ratios
It can also be useful to constrain the height-to-width ratio of an element. This is especially useful for embeded content, like videos. Chris has written about this before. In the good-old-days, we would do that with %
-based padding on a container element, and absolute positioning on the inner element. Now we can sometimes use viewport units to achieve that effect without the extra markup.
If we can count on the video being full-screen, we can set our height relative to the full viewport width:
/* full-width * aspect-ratio */
.full-width {
width: 100vw;
height: calc(100vw * (9/16));
}
That math doesn’t have to happen in the browser with calc
. If you are using a pre-processor like Sass, it will work just as well to do the math there: height: 100vw * (9/16)
. If you need to constrain the max-width, you can constrain the max-height as well:
/* max-width * aspect-ratio */
.full-width {
width: 100vw;
max-width: 30em;
height: calc(100vw * (9/16));
max-height: calc(30em * (9/16));
}
Here’s a demonstration showing both options, with CSS custom properties (variables) to make the math more semantic. Play with the numbers to see how things move, keeping the proper ratio at all times:
See the Pen Fluid Ratios with Viewport Units by Miriam Suzanne (@mirisuzanne) on CodePen.
Chris takes this one step farther in his pre-viewport-units article, so we will too. What if we need actual HTML content to scale inside a set ratio – like presentation slides often do?
We can set all our internal fonts and sizes using the same viewport units as the container. In this case I used vmin
for everything, so the content would scale with changes in both container height and width:
See the Pen Fluid Slide Ratios with Viewport Units by Miriam Suzanne (@mirisuzanne) on CodePen.
Breaking the Container
For years now, it’s been popular to mix constrained text with full-width backgrounds. Depending on your markup or CMS, that can become difficult. How do you break content outside of a restricted container, so that it fills the viewport exactly?
Again, viewport units can come in handy. This is another trick we’ve used on the new OddBird site, where a static-site generator sometimes limits our control of the markup. It only takes a few lines of code to make this work.
.full-width {
margin-left: calc(50% - 50vw);
margin-right: calc(50% - 50vw);
}
There are more in-depth articles about the technique, both at Cloud Four and here on CSS Tricks.
Getting Weird
Of course, there’s much more you can do with viewport units, if you start experimenting. Check out this pure CSS scroll-indicator (made by someone named Mike) using viewport units on a background image:
See the Pen CSS only scroll indicator by Mike (@MadeByMike) on CodePen.
What else have you seen, or done with viewport units? Get creative, and show us the results!
Scroll Indicator is pure madness :)
Agree!
Agree!
Indeed
Great content. viewport is quite useful for responsive web design, helpful article. thanks
vw and vh is awsome! Although iOS 10* and the latest Facebook App Webview are currently having a few issues with 100vh being larger than the innerHeight, so full page webapps are getting lost off of the bottom of the screen.
This seems to do the trick to fix it : https://stackoverflow.com/questions/35421247/wrong-viewport-page-height-in-embedded-facebook-browser-in-ios-9-x
Can you give us an explanation of how the scroll indicator works? Is it just a triangle gradient that is covered by
body::before
, which is neatly sandwiched between the gradient and the content withz-index: -1
?That’s exactly right. The triangle is created like this:
100%
) minus the height of the window (100vh
) below the header (129px
). This constrains the triangle height a bit, since the scroll-bar is at the top of the page, and you can never really scroll to the bottom.Great article, Miriam. Here is what I’ve been doing with Viewport Units lately: Mixing them with linear equations and breakpoints: Poly Fluid Sizing (https://www.smashingmagazine.com/2017/05/fluid-responsive-typography-css-poly-fluid-sizing/)
On a related note, one downside to using Viewport units for
font-size
is that they aren’t accessible. Resizing your browser text size is ignored for anything using straight Viewport units. They will resize if you are using Viewport units in acalc()
, but their rate of change is dependent on your pixel value used in the equation.Other than that downside, Viewport Units are pretty great!
Great article! I’m so frustrated with browsers not keeping up with CSS/HTML specifications that have been around for years. Always have to wait years to implement the cool features and tools we need yesterday. Thank you Miriam, this will be bookmarked.
Can viewport units be used to create a background image parallax effect (in a similar way to the scroll indicator example)?
Hi Miriam,
Thanks for this article.
You mention calc or sass for fluid aspect ratios, but I think neither is needed for these calculations. Couldn’t we simply write the following?
Sure, that works as well. I always prefer to show my math, with as many semantic hints as possible (like variable names), rather than pasting in a random-looking number like
56.25
. I want the code to be meaningful and readable, in addition to working. Your comments do something similar – adding a few clues, and you could add more – but why not make the math visible directly in the code?Oh that makes sense. My logic was to avoid using
calc()
if not needed to make the code more accessible (poor Opera Mini users!) and performance (although I have no idea of the performance cost of using calc()…). But then as you say it does require the extra comments…Right now it seems like there is a bug in Safari 10.1 that does not support fluid resizing of font-size values that contain a calc() function with a viewport unit as a value in the function. Obviously this doesn’t affect mobile users but definitely sucks for those people who want to resize their browser window with Safari in macOS. I submitted this bug to Apple so we’ll see if there is anything that can be done about it
In you sticky footers example, you were set it up column value as follow:
grid-template-columns: minmax(auto, 12em) 5fr;
So, in this line, what is the meaning of 5fr unit? Because, if we set it up 1fr, doesn’t change anything!
Yeah it wouldn’t matter there as they are the only “fractions” in use. Could be 999999fr and be the same, because 999999/999999 = 1 just like 5/5 = 1 or 1/1 = 1
Ha, yep. Good catch. I think I was using
fr
units initially for the sidebar, and never changed the main width after moving toauto
andem
s.Most of these don’t work with IE or Edge so their usefulness is limited.
The usefulness of IE and Edge is limited too.
If you use viewport units for
font-size
, you override the browsers’ inbuilt ability to zoom the text. This is regardless of whether you use it in a calc()-function. This breaks the most basic web accessibility feature, and rids your users of control.The only way a user can zoom text with viewport units sizing is to change the viewport size. This seems random, and in many cases it will not work.
Please don’t break my zoom!
Please ignore my comment – I guess I misunderstood. If define
font-size
with a calc function with vw and px or em, then normal browser scaling works as a charm.“or 48px on a phone that is 480px wide”===》“or 48vx on a phone that is 480px wide”