Custom WordPress site specialists - web design, hosting and online growth

Viewport-relative, fluid, responsive web design

Buy Me a Coffee
CSS viewport-relative, fluid, responsive web design

Viewport-relative, fluid web design is fully dependent on the viewport's width. Container-based web design is based on content inside of a fixed width, which resizes in between a couple of CSS breakpoints.

If you want to make use of the whole canvas, then fluid web design is the way to go. However, within fluid web design, you have the choice to mix in absolute units (px, mm, cm, etc) or to only use relative units (%, vw, vh, em, etc).

In this article I will explain how to only use relative units to create a fully fluid, yet responsive web design. I will use the web design of this very website as an example.

A true viewport-relative, fluid web design makes for a truly responsive web design.

Pros and Cons

Let's start with the cons, because those are very important to know, before deciding whether to go for a viewport-relative web design or not.

Cons

Zoom has no effect below a certain percentage

If you zoom in on a desktop device, the viewport's CSS-width will decrease, while the actual viewport size remains the same. So, if a 1920x1080 viewport is zoomed in at 150%, then CSS will think the viewport's width is 1280 pixels wide. This will make it zoom.

But since the web design is viewport-relative, that doesn't do anything - unless the viewport's CSS width goes below laptop's breakpoint into tablet's breakpoint.

So, the user would have to zoom in to 175% on a 1920x1080 viewport to make everything larger. That would also mean that the web page shown is as shown on tablet devices.

Width of images varies for every pixel a viewport's width can change

In order to make images appear as sharp as possible, the image's intrinsic size should match it's rendered size, because otherwise the web browser will resize the image, which looks terrible as the browser has to do it quickly to prevent a delay in displaying images..

Since it is a viewport-relative web design, the width of images also change if the viewport's width changes by even 1 pixel. That's why it is important to provide the image in widths that correlate to it's rendered size as shown at common viewport widths.

Container-based web designs do not have this problem, since the width of images remain the same as long as the container's size doesn't change (which changes for breakpoints).

Integrating third-party modules requires you to overwrite absolute units

If you were to use a viewport-relative web design on a WordPress website, then it is likely that if you install a plugin for, let's say, a widget for third-party reviews, that you have to overwrite all appearances of absolute values that were applied by the plugin's CSS styling.

This is not such a big deal, because you will probably customize the appearance of elements added by plugins anyways. It's just made for a different web design system.

Pros

Consistency across devices within the same breakpoint

If you put full-width rows in a web design that used absolute units for font sizes, margins, and paddings, then it could look squished on laptops, while looking good on desktops.

Viewport-relative web designs look the same on devices within the same breakpoint, and only change for device categories: laptop/desktop, tablet/mobile landscape, and mobile.

What you see is what you get

Coming back to the previous point, this also means that you don't have to check whether a full-width row looks good on a laptop screen, or a smaller/larger smartphone screen.

What you see on a laptop screen, is what you see on a desktop screen; WYSIWYG.

Using the whole canvas

Now, because everything looks exactly the same within the same breakpoints, you can make use of the whole viewport without worrying about how it would look on laptop screens while designing on a desktop screen.

You should, however, make sure that the font size is big enough for the minimum width of your breakpoints. You can always add a breakpoint in between, if necessary.

What units to use?

All sizes are defined by using the vw-unit, except for the height of full-screen rows. Other relative units can be used as well, as long as it's suitable and relative to the viewport.[a]

vw
Unit equal to 1% of the viewport's width.[b]
vh
Unit equal to 1% of the viewport's height.[c]
%
Unit equal to a 100th of an element's parent's value for the same property.[d][e]
em
Unit relative to 100% of an element's font-size.

How does a viewport-relative web design translate to mobile viewports?

Obviously, scaling a web page that is designed for a desktop screen will get tiny on a mobile screen. To make it work for smaller devices, you simply use multipliers from a certain viewport width.

For example, you could provide a multiplier for font-sizes and margins/paddings:

/* Mobile */
@media (max-width: 575.98px) {
  :root {
    --fontSizeMultiplier: 4.3;
    --marpadMultiplier: 3.2;
  }
}

/* Mobile (Landscape) & Tablet */
@media (min-width: 576px) and (max-width: 1199.98px) {
  :root {
    --fontSizeMultiplier: 2;
    --marpadMultiplier: 2;
  }
}

/* Laptop & Desktop */
@media (min-width: 1200px) {
  :root {
    --fontSizeMultiplier: 1;
    --marpadMultiplier: 1;
  }
}

Then, use those multipliers in the calculation of the font-sizes and paddings/margins.

:root {
  --vw:			1vw;
  
  --marpadTiny:		calc(0.2 * var(--vw) * var(--marpadMultiplier));
  --marpadSmaller:	calc(0.5 * var(--vw) * var(--marpadMultiplier));
  --marpadSmall:	calc(1.3 * var(--vw) * var(--marpadMultiplier));
  --marpadNormal:	calc(2.0 * var(--vw) * var(--marpadMultiplier));
  --marpadBig:		calc(3.5 * var(--vw) * var(--marpadMultiplier));
  --marpadBigger:	calc(6.5 * var(--vw) * var(--marpadMultiplier));
  
  --bodyFontSize:	calc(1.0 * var(--vw) * var(--fontSizeMultiplier));
  --h1FontSize:		calc(2.5 * var(--vw) * var(--fontSizeMultiplier));
  --h2FontSize:		calc(2.1 * var(--vw) * var(--fontSizeMultiplier));
  --h3FontSize:		calc(1.8 * var(--vw) * var(--fontSizeMultiplier));
  --h4FontSize:		calc(1.6 * var(--vw) * var(--fontSizeMultiplier));
  --h5FontSize:		calc(1.5 * var(--vw) * var(--fontSizeMultiplier));
  --h6FontSize:		calc(1.3 * var(--vw) * var(--fontSizeMultiplier));
}

For now, all you have seen is CSS variables. This is the foundation for a flexible web design, which it is supposed to be. You will understand the power of CSS variables at the end of this article. I'll explain why I use a variable for CSS unit vw shortly after this.

To be honest, the CSS I just showed you, is not exactly how I wrote it for my web design. The CSS I wrote, for variables --marpadNormal and --bodyFontSize for example, is uglier.

:root {
  --vw: calc(1vw - var(--scrollbarWidth) / 100);
  --designedForWidth: 2560;
  --marpadNormal: calc(50 / var(--designedForWidth) * 100 * var(--vw) * var(--horizontalMarpadMultiplier));
  --bodyFontSize: calc(32 / var(--designedForWidth) * 100 * var(--vw) * var(--fontSizeMultiplier));
}

Here's the deal.. I designed my website in Adobe XD on a canvas that was 2560 pixels wide. I wrote down the font sizes and used it in a calculation that divides the font size by the canvas size (2560px) and multiplies that by the viewport width.

That was easier for me, as I could just write down the font size and I didn't have to calculate what the vw-value had to be. If you think it's ugly, I completely agree with you. That's why I will use vw-units in this article instead.

Also, here is the CSS-reset I've used:

html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{line-height:1}button{outline:0}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}q{display:inline;font-style:italic}q:before{content:'"';font-style:normal}q:after{content:'"';font-style:normal}textarea,input[type="text"],input[type="button"],input[type="submit"],input[type="reset"],input[type="search"],input[type="password"]{-webkit-appearance:none;appearance:none;border-radius:0}table{border-collapse:collapse;border-spacing:0}th,td{padding:var(--marpadSmallV) var(--marpadSmall)}big{font-size:120%}small,sup,sub{font-size:80%}sup{vertical-align:super}sub{vertical-align:sub}dd{margin-left:var(--marpadSmaller)}ins{text-decoration:underline}del,strike,s{text-decoration:line-through}dt{font-weight:bold}address,cite,var{font-style:italic}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}

Excluding scrollbar from viewport width

Unfortunately, the scrollbar is part of the viewport's width. This means that if the scrollbar is 500 pixels wide (for example's sake), that there is less available horizontal space within the document as opposed to if the scrollbar were 17 pixels wide.

Of course, no scrollbar will be 500 pixels wide. Default scrollbars vary from 12 pixels to 17 pixels wide for different browsers. Google Chrome's default scrollbar is 17 pixels wide.

CSS 100vw minus scrollbar width

There is no pure CSS solution for this, unfortunately. A little bit of JavaScript is required in order to calculate 100vw minus the scrollbar's width. Place the following inline JavaScript code all the way up in the <head>:

var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
if(scrollbarWidth > 0) {
  document.documentElement.style.setProperty (
    '--scrollbarWidth',
    scrollbarWidth + 'px'
  );
}

Now, add the following inline CSS to the <html>-element:

min-height: 101vh;
--scrollbarWidth: 0px;

The CSS above forces the <html>-element to show a scrollbar. The JavaScript calculates the scrollbar's width and puts the width as the value for CSS variable --scrollbarWidth.

You can now use variable --scrollbarWidth to override the default use of the vw-unit by turning it into a CSS variable called --vw. You might have come accross this CSS variable a little earlier in this article and might have wondered why. Well now you know!

Using flexbox for layout

If you want flexibility in your web design, then I suggest using flexbox layout techniques.

Building layout components

To create layouts, you need rows, columns and some utility classes to control the layout for different devices. Simple as that. So here's how.

:root {
  --defaultFlexDirection: row;
  --defaultFlexDirectionVertical: column;
  --defaultFlexDirectionReverse: row-reverse;
  --defaultFlexDirectionVerticalReverse: column-reverse;
  
  --containerWidth: calc(87.3 * var(--vw));
}

/* Mobile */
@media (max-width: 575.98px) {
  :root {
    --containerWidth: calc(100 * var(--vw));
  }
}

body {
  font-size: var(--bodyFontSize);
  background-color: white;
}
.col {
  padding: var(--marpadNormal);
  flex-grow: 1;
  flex-basis: 0px;
}
.col .col {
  padding: 0px;
}



.row {
  display: flex;
  width: 100%;
  
  --flexDirection: var(--defaultFlexDirection);
  --flexDirectionVertical: var(--defaultFlexDirectionVertical);
  
  flex-direction: var(--flexDirection);
}

.row.in-container {
  width: 100%;
  padding-left: calc((100% - var(--containerWidth)) / 2);
  padding-right: calc((100% - var(--containerWidth)) / 2);
  
  margin-top: var(--marpadBig);
}


.row.reverse-order {
  --flexDirection: var(--defaultFlexDirectionReverse);
  --flexDirectionVertical: var(--defaultFlexDirectionVerticalReverse);
}
.row.vertical {
  flex-direction: var(--flexDirectionVertical);
}

[class*='//'] {
  flex-grow: 0;
  width: calc(var(--a) / var(--b) * 100%);
  min-width: calc(var(--a) / var(--b) * 100%);
  max-width: calc(var(--a) / var(--b) * 100%);
  flex-basis: calc(var(--a) / var(--b) * 100%);
}

[class*='w-1//'] {
  --a: 1;
}
[class*='w-2//'] {
  --a: 2;
}
[class*='w-3//'] {
  --a: 3;
}
[class*='w-4//'] {
  --a: 4;
}
[class*='w-5//'] {
  --a: 5;
}
[class*='w-6//'] {
  --a: 6;
}
[class*='w-7//'] {
  --a: 7;
}
[class*='w-8//'] {
  --a: 8;
}
[class*='w-9//'] {
  --a: 9;
}
[class*='w-10//'] {
  --a: 10;
}
[class*='w-11//'] {
  --a: 11;
}
[class*='w-12//'] {
  --a: 12;
}

[class*='//1'] {
  --b: 1;
}
[class*='//2'] {
  --b: 2;
}
[class*='//3'] {
  --b: 3;
}
[class*='//4'] {
  --b: 4;
}
[class*='//5'] {
  --b: 5;
}
[class*='//6'] {
  --b: 6;
}
[class*='//7'] {
  --b: 7;
}
[class*='//8'] {
  --b: 8;
}
[class*='//9'] {
  --b: 9;
}
[class*='//10'] {
  --b: 10;
}
[class*='//11'] {
  --b: 11;
}
[class*='//12'] {
  --b: 12;
}

@media (max-width: 575.98px) {
  .row.in-container_mobile {
    width: 100%;
    padding-left: calc((100% - var(--containerWidth)) / 2);
    padding-right: calc((100% - var(--containerWidth)) / 2);
    
    margin-top: var(--marpadBig);
  }
  .row.reverse-order_mobile {
    --flexDirection: var(--defaultFlexDirectionReverse);
    --flexDirectionVertical: var(--defaultFlexDirectionVerticalReverse);
  }
  .row.vertical_mobile {
    flex-direction: var(--flexDirectionVertical);
  }
  .row:not(.vertical):not(.vertical_mobile) {
    align-items: flex-start;
  }
  
  
  .row.gap:not(.vertical):not(.vertical_mobile):not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child),
  .row.gap_mobile:not(.vertical):not(.vertical_mobile):not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical:not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child),
  .row.gap.vertical_mobile:not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child),
  .row.gap_mobile.vertical:not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child),
  .row.gap_mobile.vertical_mobile:not(.reverse-order):not(.reverse-order_mobile) > .col:not(:last-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  .row.gap:not(.vertical):not(.vertical_mobile).reverse-order > .col:not(:first-child),
  .row.gap:not(.vertical):not(.vertical_mobile).reverse-order_mobile > .col:not(:first-child),
  .row.gap_mobile:not(.vertical):not(.vertical_mobile).reverse-order > .col:not(:first-child),
  .row.gap_mobile:not(.vertical):not(.vertical_mobile).reverse-order_mobile > .col:not(:first-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical.reverse-order > .col:not(:first-child),
  .row.gap.vertical.reverse-order_mobile > .col:not(:first-child),
  .row.gap.vertical_mobile.reverse-order > .col:not(:first-child),
  .row.gap.vertical_mobile.reverse-order_mobile > .col:not(:first-child),
  .row.gap_mobile.vertical.reverse-order > .col:not(:first-child),
  .row.gap_mobile.vertical.reverse-order_mobile > .col:not(:first-child),
  .row.gap_mobile.vertical_mobile.reverse-order > .col:not(:first-child),
  .row.gap_mobile.vertical_mobile.reverse-order_mobile > .col:not(:first-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  [class*='/m/'] {
    flex-grow: 0;
    width: calc(var(--a) / var(--b) * 100%);
    min-width: calc(var(--a) / var(--b) * 100%);
    max-width: calc(var(--a) / var(--b) * 100%);
    flex-basis: calc(var(--a) / var(--b) * 100%);
  }
  
  [class*='w-1/m/'] {
    --a: 1;
  }
  [class*='w-2/m/'] {
    --a: 2;
  }
  [class*='w-3/m/'] {
    --a: 3;
  }
  [class*='w-4/m/'] {
    --a: 4;
  }
  [class*='w-5/m/'] {
    --a: 5;
  }
  [class*='w-6/m/'] {
    --a: 6;
  }
  [class*='w-7/m/'] {
    --a: 7;
  }
  [class*='w-8/m/'] {
    --a: 8;
  }
  [class*='w-9/m/'] {
    --a: 9;
  }
  [class*='w-10/m/'] {
    --a: 10;
  }
  [class*='w-11/m/'] {
    --a: 11;
  }
  [class*='w-12/m/'] {
    --a: 12;
  }
  
  [class*='/m/1'] {
    --b: 1;
  }
  [class*='/m/2'] {
    --b: 2;
  }
  [class*='/m/3'] {
    --b: 3;
  }
  [class*='/m/4'] {
    --b: 4;
  }
  [class*='/m/5'] {
    --b: 5;
  }
  [class*='/m/6'] {
    --b: 6;
  }
  [class*='/m/7'] {
    --b: 7;
  }
  [class*='/m/8'] {
    --b: 8;
  }
  [class*='/m/9'] {
    --b: 9;
  }
  [class*='/m/10'] {
    --b: 10;
  }
  [class*='/m/11'] {
    --b: 11;
  }
  [class*='/m/12'] {
    --b: 12;
  }
}

@media (min-width: 576px) and (max-width: 1199.98px) {
  .row.in-container_tablet {
    width: 100%;
    padding-left: calc((100% - var(--containerWidth)) / 2);
    padding-right: calc((100% - var(--containerWidth)) / 2);
    
    margin-top: var(--marpadBig);
  }
  .row.reverse-order_tablet {
    --flexDirection: var(--defaultFlexDirectionReverse);
    --flexDirectionVertical: var(--defaultFlexDirectionVerticalReverse);
  }
  .row.vertical_tablet {
    flex-direction: var(--flexDirectionVertical);
  }
  .row:not(.vertical):not(.vertical_tablet) {
    align-items: flex-start;
  }
  
  
  .row.gap:not(.vertical):not(.vertical_tablet):not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child),
  .row.gap_tablet:not(.vertical):not(.vertical_tablet):not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical:not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child),
  .row.gap.vertical_tablet:not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child),
  .row.gap_tablet.vertical:not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child),
  .row.gap_tablet.vertical_tablet:not(.reverse-order):not(.reverse-order_tablet) > .col:not(:last-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  .row.gap:not(.vertical):not(.vertical_tablet).reverse-order > .col:not(:first-child),
  .row.gap:not(.vertical):not(.vertical_tablet).reverse-order_tablet > .col:not(:first-child),
  .row.gap_tablet:not(.vertical):not(.vertical_tablet).reverse-order > .col:not(:first-child),
  .row.gap_tablet:not(.vertical):not(.vertical_tablet).reverse-order_tablet > .col:not(:first-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical.reverse-order > .col:not(:first-child),
  .row.gap.vertical.reverse-order_tablet > .col:not(:first-child),
  .row.gap.vertical_tablet.reverse-order > .col:not(:first-child),
  .row.gap.vertical_tablet.reverse-order_tablet > .col:not(:first-child),
  .row.gap_tablet.vertical.reverse-order > .col:not(:first-child),
  .row.gap_tablet.vertical.reverse-order_tablet > .col:not(:first-child),
  .row.gap_tablet.vertical_tablet.reverse-order > .col:not(:first-child),
  .row.gap_tablet.vertical_tablet.reverse-order_tablet > .col:not(:first-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  [class*='/t/'] {
    flex-grow: 0;
    width: calc(var(--a) / var(--b) * 100%);
    min-width: calc(var(--a) / var(--b) * 100%);
    max-width: calc(var(--a) / var(--b) * 100%);
    flex-basis: calc(var(--a) / var(--b) * 100%);
  }
  
  [class*='w-1/t/'] {
    --a: 1;
  }
  [class*='w-2/t/'] {
    --a: 2;
  }
  [class*='w-3/t/'] {
    --a: 3;
  }
  [class*='w-4/t/'] {
    --a: 4;
  }
  [class*='w-5/t/'] {
    --a: 5;
  }
  [class*='w-6/t/'] {
    --a: 6;
  }
  [class*='w-7/t/'] {
    --a: 7;
  }
  [class*='w-8/t/'] {
    --a: 8;
  }
  [class*='w-9/t/'] {
    --a: 9;
  }
  [class*='w-10/t/'] {
    --a: 10;
  }
  [class*='w-11/t/'] {
    --a: 11;
  }
  [class*='w-12/t/'] {
    --a: 12;
  }
  
  [class*='/t/1'] {
    --b: 1;
  }
  [class*='/t/2'] {
    --b: 2;
  }
  [class*='/t/3'] {
    --b: 3;
  }
  [class*='/t/4'] {
    --b: 4;
  }
  [class*='/t/5'] {
    --b: 5;
  }
  [class*='/t/6'] {
    --b: 6;
  }
  [class*='/t/7'] {
    --b: 7;
  }
  [class*='/t/8'] {
    --b: 8;
  }
  [class*='/t/9'] {
    --b: 9;
  }
  [class*='/t/10'] {
    --b: 10;
  }
  [class*='/t/11'] {
    --b: 11;
  }
  [class*='/t/12'] {
    --b: 12;
  }
}

@media (min-width: 1200px) {
  .row.in-container_mobile {
    width: 100%;
    padding-left: calc((100% - var(--containerWidth)) / 2);
    padding-right: calc((100% - var(--containerWidth)) / 2);
    
    margin-top: var(--marpadBig);
  }
  .row.reverse-order_desktop {
    --flexDirection: var(--defaultFlexDirectionReverse);
    --flexDirectionVertical: var(--defaultFlexDirectionVerticalReverse);
  }
  .row.vertical_desktop {
    flex-direction: var(--flexDirectionVertical);
  }
  .row:not(.vertical):not(.vertical_desktop) {
    align-items: flex-start;
  }
  
  
  .row.gap:not(.vertical):not(.vertical_desktop):not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child),
  .row.gap_desktop:not(.vertical):not(.vertical_desktop):not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical:not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child),
  .row.gap.vertical_desktop:not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child),
  .row.gap_desktop.vertical:not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child),
  .row.gap_desktop.vertical_desktop:not(.reverse-order):not(.reverse-order_desktop) > .col:not(:last-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  .row.gap:not(.vertical):not(.vertical_desktop).reverse-order > .col:not(:first-child),
  .row.gap:not(.vertical):not(.vertical_desktop).reverse-order_desktop > .col:not(:first-child),
  .row.gap_desktop:not(.vertical):not(.vertical_desktop).reverse-order > .col:not(:first-child),
  .row.gap_desktop:not(.vertical):not(.vertical_desktop).reverse-order_desktop > .col:not(:first-child) {
    margin-right: var(--marpadNormal);
  }
  .row.gap.vertical.reverse-order > .col:not(:first-child),
  .row.gap.vertical.reverse-order_desktop > .col:not(:first-child),
  .row.gap.vertical_desktop.reverse-order > .col:not(:first-child),
  .row.gap.vertical_desktop.reverse-order_desktop > .col:not(:first-child),
  .row.gap_desktop.vertical.reverse-order > .col:not(:first-child),
  .row.gap_desktop.vertical.reverse-order_desktop > .col:not(:first-child),
  .row.gap_desktop.vertical_desktop.reverse-order > .col:not(:first-child),
  .row.gap_desktop.vertical_desktop.reverse-order_desktop > .col:not(:first-child) {
    margin-bottom: var(--marpadNormal);
  }
  
  [class*='/d/'] {
    flex-grow: 0;
    width: calc(var(--a) / var(--b) * 100%);
    min-width: calc(var(--a) / var(--b) * 100%);
    max-width: calc(var(--a) / var(--b) * 100%);
    flex-basis: calc(var(--a) / var(--b) * 100%);
  }
  
  [class*='w-1/d/'] {
    --a: 1;
  }
  [class*='w-2/d/'] {
    --a: 2;
  }
  [class*='w-3/d/'] {
    --a: 3;
  }
  [class*='w-4/d/'] {
    --a: 4;
  }
  [class*='w-5/d/'] {
    --a: 5;
  }
  [class*='w-6/d/'] {
    --a: 6;
  }
  [class*='w-7/d/'] {
    --a: 7;
  }
  [class*='w-8/d/'] {
    --a: 8;
  }
  [class*='w-9/d/'] {
    --a: 9;
  }
  [class*='w-10/d/'] {
    --a: 10;
  }
  [class*='w-11/d/'] {
    --a: 11;
  }
  [class*='w-12/d/'] {
    --a: 12;
  }
  
  [class*='/d/1'] {
    --b: 1;
  }
  [class*='/d/2'] {
    --b: 2;
  }
  [class*='/d/3'] {
    --b: 3;
  }
  [class*='/d/4'] {
    --b: 4;
  }
  [class*='/d/5'] {
    --b: 5;
  }
  [class*='/d/6'] {
    --b: 6;
  }
  [class*='/d/7'] {
    --b: 7;
  }
  [class*='/d/8'] {
    --b: 8;
  }
  [class*='/d/9'] {
    --b: 9;
  }
  [class*='/d/10'] {
    --b: 10;
  }
  [class*='/d/11'] {
    --b: 11;
  }
  [class*='/d/12'] {
    --b: 12;
  }
}

The CSS shown above contains styling for rows, columns, body text, headings, paragraphs, links, and images. It also provides utility classes to control the layout:

  • Direction: horizontal/vertical (.vertical, .vertical_mobile, ...)
  • Reverse direction: horizontal/vertical (.reverse-order, .reverse-order_mobile, ...)
  • Gap between columns (.gap, .gap_mobile, ...)
  • Content width of the row (.in-container, .in-container_mobile, ...)
  • Width of column: w-(a)/(device)/(b), for instance: w-2/t/3 = 66.67%

To create a decent demo, you need some styling as well.

body {
  line-height: 1.5;
  font-family: Arial;
}
h1, h2, h3, h4, h5, h6 {
  margin-bottom: var(--marpadSmall);
  line-height: 1.2;
  font-family: Arial;
}
h1 {
  font-size: var(--h1FontSize);
}
h2 {
  font-size: var(--h2FontSize);
}
h3 {
  font-size: var(--h3FontSize);
}
h4 {
  font-size: var(--h4FontSize);
}
h5 {
  font-size: var(--h5FontSize);
}
h6 {
  font-size: var(--h6FontSize);
}

p:not(:last-child) {
  margin-bottom: var(--marpadSmall);
}

a {
  color: blue;
}

img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
}

img + p {
  margin-top: var(--marpadSmall);
}


.bg-whitesmoke {
  background-color: whitesmoke;
}
.bg-honeydew {
  background-color: honeydew;
}

I have created a demo for you: Demo - Viewport-relative, fluid, responsive web design.

If you want to add more flexibility, like reordering columns at different devices, then be sure to take a look around in the posts I wrote about unique CSS solutions.

Notes

  1. ^ When using the em unit, the font size that it's relative to, should be relative to the viewport.
  2. ^ Scrollbars do not affect the viewport's width, so the vw unit is not suitable for making sections take up the full width of the body, while inside a scroll container.
  3. ^ On desktop devices, the toolbar and anything else that is part of the operating system and web browser, are not part of the viewport's height and not part of the vh unit. On mobile devices, the toolbar is still part of the viewport's height. The latter creates a problem for if you want to use full-screen sections on a mobile device.
  4. ^ Often the percentage unit (%) is relative to the value of the same property of an element's parent, like for the width or height. However, there are exceptions to this:
    • Percentage units used for all properties of padding and margin are relative to the width of the parent element; even for properties controlling vertical spacing: padding-top, padding-bottom, margin-top, and margin-bottom.
  5. ^ Not all properties that use numeric values can use the percentage unit (%), as for some it may not make sense to refer to the parent's value for the same property. For instance, there is no useful default value for letter-spacing, as the value supplied for this property (which is 0 by default) is added on top of the font's default letter spacing.