Using figures and Flex-box to align captions

As I learn how to be more flexible in working with art direction for web content, I’m documenting some of the cool things I’ve learned.

Because I’m using flexbox, I’m relying on CSS Trick’s A Complete Guide to Flexbox to reference the Flexbox aspects fo the elements.

Figures as flex containers

If we make a figure into a flex container we gain a lot of flexibility in how we place captions around the image.

This is the image that we’ll play with.

<h1><span class="highlight">Pablo Neruda</span> Facts</h1>

    alt="Pablo Neruda Portrait" />
    <p>Pablo Neruda, original name Neftalí Ricardo Reyes Basoalto, (born July 12, 1904, Parral, Chile — died September 23, 1973, Santiago), Chilean poet, diplomat, and politician who was awarded the Nobel Prize for Literature in 1971. He was perhaps the most important Latin American poet of the 20th century.</p>

This is the basic CSS that will place both the image and the caption in a row, the caption aligned to the top of the image.

We define the image as being 30vw (viewport width units) wide and having no margin at the bottom of the image.

We add a deep red bar on the left side of the figcaption element. The caption is placed at the top, flushed with the image.

We also make the caption 25vw wide as an initial value that we can play with it later.

figure {
  display: flex;
  flex-flow: row;
  justify-content: flex-start;

img {
  width: 30vw;
  margin-bottom: 0;

figcaption {
  align-self: flex-start;
  border-left: 1em solid rgb(169, 13, 94);
  padding-left: 1em;
  margin-left: 2em;
  width: 25vw;

You can see the result of this initial test in this pen

Vertical caption alignment

One of the first changes that I want to experiment with is changing the vertical placement of the caption to the side of the image.

We modify the style for the figcaption element to tell it where we want to place the caption relative to the image.

All we need to do is change the align-self attribute in the figcaption element. The code below will align the caption to the bottom of the image.

figcaption {
  align-self: flex-end;
  border-left: 1em solid rgb(169, 13, 94);
  padding-left: 1em;
  margin-left: 2em;
  width: 25vw;

This pen illustrates aligning to the bottom of the image

We can also center the caption related to the image by changing the align-self selector to center instead of flex-start or flex-end.

figcaption {
  align-self: center;
  border-left: 1em solid rgb(169, 13, 94);
  padding-left: 1em;
  margin-left: 2em;
  width: 25vw;

This pen shows vertical centering

Moving captions to the left of the image

Moving the caption to the left of the image helps with balancing the flow of text or just to make the images look different by playing with the captions in ways the reader might not expect.

To move the caption we have to change the flex-flow attribute to reverse the order in which the objects appear on the screen.

In the figure element we set the flex-flow to row-reverse meaning that the browser will display the content from right to left. Since the image is the first element in document order it will appear to the right with the caption to the left.

We must remember that because we flipped the order of the items we also have to change the way we justify the content, in this case to flex-end if we want the figure to stay on the left side of the screen.

figure {
  display: flex;
  flex-flow: row-reverse;
  justify-content: flex-end;

We also to change the border-, padding-, and margin- to right, moving the bar and space between the bar and the caption to the right side.

figcaption {
  align-self: flex-end;
  border-right: 1em solid rgb(169, 13, 94);
  padding-right: 1em;
  margin-right: 2em;
  width: 25vw;

The tricks for vertical alignment work on the right side too.

The example pen is here

Captions above or below the image

Changing the value of flex-flow we can place the caption above or below the image. The example below puts the caption text below the image.

The figure now flows vertically using flex-flow: column and has an explicit width that is as wide as the image inside.

figure {
  display: flex;
  flex-flow: column;
  width: 30vw;

We control the alignment of the caption element using the align-self attribute. In this case, the caption is left aligned.

We can add borders to highlight the caption. In the example, we added the same border at the top and bottom.

Controlling the width of the caption and the font size gives us a little more control over what we can do with the caption, as shown in this example Codepen

figcaption {
  align-self: flex-start;
  border-top: 2px solid rgb(169, 13, 94);
  border-bottom: 2px solid rgb(169, 13, 94);
  font-size: 80%;
  width: 20vw;

To place the caption above the image we need to make a single change; use flex-flow: column-reverse in the figure styles.

figure {
  display: flex;
  flex-flow: column-reverse;
  width: 30vw;

In the example pen you may want to adjust the bottom margin to create some space between the caption border/line and the image.

Adding icons to links based on the type of resource

For a long time I’ve seen icons attached to links to help users figure out what type of links they are.

This was particularly important in the days of plugins because users haad to have the right plugins to view different types of content.

Taking the following HTML fragment,

<h2 id="stats">
  Stats <span aria-hidden="true" 

We can use the following css to add an icon after the string Stats:

[data-icon]:after {
  font-family: icons;
  content: attr(data-icon);
  speak: none;

For modern browsers, we can use attribute selectors to change add the icon as a background image and then move it to where we need to. This works particularly well with SVG images

a[href$=".pdf"] {
  background: url('icons/pdf.png')
    0 50% no-repeat;
  padding-left: 20px;

We can also use other attribute selectors to tailor the way we display icons for links and what icon we want to display for each link.

We can use the [attribute^=value] selector to add a link to all external resources to differentiate them from internal links that jump to other parts of the same document or other documents on the same site.

Note that, in this case, we don’t care if the link is http or https; They both start with http.

a[href^=http] {
  background: url('icons/external.svg')
    0 50% no-repeat;
  padding-left: 20px;

We can also use the [attribute*=value] to match at least a single occurrence of value. In the examples below, we look for link attributes that match Twitter or Facebook and add the corresponding link.

a[href*=""] {
    0 50% no-repeat;

a[href*=""] {
    0 50% center;

Chrome DevTools Network Tab

The idea is that using this tab we can check how our page loading and troubleshoot loading issues as they happen with a visual representation of the loading process

  1. Make sure that you are running in Incognito Mode to prevent extensions from causing any problems
  2. Open DevTools (Command + Option + I on Macintosh or Control + Shift + I or F12 on Windows)
  3. Go to the Network tab

The image below shows the result of running the Network tab in Chrome 78 (Canary when the article was written).

Network Panel Showing Results of a run

The Network panel provides the following information for every resource loaded for the page:

  1. Method: HTTP method used to retrieve the resource; usually GET
  2. Status: The status code for the response. Usually 200 for successful responses.
  3. Protocol: The HTTP protocol of the server handling the request
  4. Type: What type of resource it is expressed by mime type
  5. Initiator: What triggered the loading of the resource
  6. Size: How big the resource is
  7. Time: How long did it take to load the resource
  8. Priority: The priority the browser used to fetch the resource
  9. Waterfall: shows different time metrics for the resource. We’ll revisit the waterfall in a later section

Things we can do

There are some additional things that we can do when in the network panel.

If we Disable Cache we will get a fresh download without pulling data from the cache, just like a user visiting the site for the first time.

Online is a pull-down menu that gives us the option to throttle our connection speed to one of the available presets or to customize the way the connection behaves.

The two arrows in the far right allow you to import (arrow pointing up) and export (arrow pointing down) HAR files, a cross-browser way to review performance data.

The result

Below the waterfall, we get several aggregate results for the page. These may give a first impression of why the page may be experiencing performance issues.

How many requests succeed and what’s the total number of requests for the page if they are different.

How much storage do the resources you needed to download for the page cost in terms of weight.

How much did all the resources for the page cost in terms of bandwidth weight. This number may be different than the weight of transferred resources because it includes the resources that the browser cached in prior visits.

The final three numbers are indicators of speed. Going from right to left:

  • finished indicates how long did the page take to load
  • DOMContentLoaded shows how long did the browser take before firing the DOMContentLoaded event. The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for subresources to finish loading
  • Load shows how long did the browser takes to fire the load event. This event fires when the whole page has loaded, including all dependent resources such as stylesheets images

The waterfall in detail

Rather than try to explain in detail what each possible element is, I’ll use Google Developers’ Network Analysis Reference section on Timing breakdown phases as an explanation:

  • Queueing. The browser queues a request when:
    • There are higher priority requests.
    • There are already six TCP connections open for this origin, which is the limit. Applies to HTTP/1.0 and HTTP/1.1 only.
    • The browser is briefly allocating space in the disk cache
  • Stalled. The request could be stalled for any of the reasons described in Queueing.
  • DNS Lookup. The browser is resolving the request’s IP address.
  • Proxy negotiation. The browser is negotiating the request with a proxy server.
  • Request sent. The request is being sent.
  • ServiceWorker Preparation. The browser is starting up the service worker.
  • Request to ServiceWorker. The request is being sent to the service worker.
  • Waiting (TTFB). The browser is waiting for the first byte of a response. TTFB stands for Time To First Byte. This timing includes 1 round trip of latency and the time the server took to prepare the response.
  • Content Download. The browser is receiving the response.
  • Receiving Push. The browser is receiving data for this response via HTTP/2 Server Push.
  • Reading Push. The browser is reading the local data previously received.

This is not everything you can do in DevTools’ network tab but it’s a good starting point.

Using material design for typography work

Material Design is Google’s Design System. It started as a Google-only system that you bought into the wholesale design system with little to no chance of customizing it.

The new iteration of Material Design does several things that the original did not do well or at all, including framework integrations.

The “new Material” also allows for easier customization and provides SASS/SCSS mixins and functions to do so.

In this post, we’ll look at the typography options available to Material Design, both standard and customized.

Setting up

There are two ways to set up a Material Design project. We’ll talk about them below.

The easy way

The easy way loads the entire library of CSS functions and scripts from a CDN.

We also load Roboto and Material Design Icons from Google Fonts.

<link rel="stylesheet" href="[email protected]/dist/material-components-web.min.css">
<!-- Fonts -->
<link rel="stylesheet" href="|Roboto+Mono|Material+Icons">

<script src="[email protected]/dist/material-components-web.min.js"></script>

This will load all of Material Design, whether we use it or not. We’ll have to decide if this is worth the extra download sizes.

This method will not let you customize individual packages so, unless you’re OK with the defaults this may not be the best option.

The hard way

The hard way uses WebPack and SASS. The typography module doesn’t use Javascript so we’ll skip it in this example.

The first step is to get WebPack and plugins configured. The plugins are:

The command too install the Node modules is:

npm install --save-dev webpack \
webpack-cli \
webpack-dev-server \
css-loader \
sass-loader \
node-sass \
extract-loader \
file-loader \

We then write a webpack.config.js to process the SCSS file that we’ll create later in the process.

const autoprefixer = require('autoprefixer');

module.exports = [{
  entry: './scss/app.scss',
    output: {
    // style-bundle.js is necessary
    // for webpack to compile but never used
    filename: 'style-bundle.js',
    module: {
      rules: [{
        test: /\.scss$/,
        use: [
            loader: 'file-loader',
            options: {
              name: 'bundle.css',
          { loader: 'extract-loader' },
          { loader: 'css-loader' },
          { loader: 'sass-loader' },

We are then ready to install the specific modules that we want to work with. In this case, typography.

npm install @material/typography

Finally, we need to import the component’s SCSS using the @import statement.

We can then add any additional styles that we need.

@import "@material/typography/mdc-typography";

An Example

This example covers the basic layout of the site

<body class="mdc-typography">
  <h1 class="mdc-typography--headline1">Towards the Splendid City</h1>

  <h2 class="mdc-typography--headline2">Pablo Neruda</h2>

  <h3 class="mdc-typography--headline3">Nobel Lecture, December 13, 1971</h3>

  <p class="mdc-typography--body1">My speech is going to be a long journey, a trip that I have taken through regions that are distant and antipodean, but not for that reason any less similar to the landscape and the solitude in Scandinavia.…</p>

All material design components are built with this typography already baked in. For elements that are not part of Material Design, we can use the following classes as I did in the above example.

Material Design Typography Classes
CSS Class Description
mdc-typography Sets the font to Roboto
mdc-typography--headline1 Sets font properties as Headline 1
mdc-typography--headline2 Sets font properties as Headline 2
mdc-typography--headline3 Sets font properties as Headline 3
mdc-typography--headline4 Sets font properties as Headline 4
mdc-typography--headline5 Sets font properties as Headline 5
mdc-typography--headline6 Sets font properties as Headline 6
mdc-typography--subtitle1 Sets font properties as Subtitle 1
mdc-typography--subtitle2 Sets font properties as Subtitle 2
mdc-typography--body1 Sets font properties as Body 1
mdc-typography--body2 Sets font properties as Body 2
mdc-typography--caption Sets font properties as Caption
mdc-typography--button Sets font properties as Button
mdc-typography--overline Sets font properties as Overline

Customizing Material Design Typography

Material design typography also offers you SCSS mixins to customize how typography will work in the document.

Mixin Description
mdc-typography-base Sets the font to Roboto
mdc-typography($style) Applies one of the typography styles, including setting the font to Roboto.
mdc-typography-overflow-ellipsis Truncates overflow text to one line with an ellipsis. Only works for content using display: block or display: inline-block
mdc-typography-baseline-top($distance) Sets the baseline height of a text element from top.
mdc-typography-baseline-bottom($distance) Sets the distance from text baseline to bottom. This mixin should be combined with mdc-typography-baseline-top when setting baseline distance to following text element.

Possible values for $style

  • headline1
  • headline2
  • headline3
  • headline4
  • headline5
  • headline6
  • subtitle1
  • subtitle2
  • body1
  • body2
  • caption
  • button
  • overline

This gives us one level of customization but it won’t change the defaults, it will only use the defaults for the elements we’re working with.

If you’re familiar with SASS you can use

All styles can be overridden using Sass global variables with the format $mdc-typography-styles-{style} before the component is imported.

The variable should contain a map that with all the properties we want to override for a particular style.

Assuming that the fonts are available on the system or loaded using @font-face we can use code like this to change the global font defaults for the document.

$mdc-typography-font-family: unquote("Arial, Helvetica, sans-serif");

// Imports and the rest of our code goes here

unquote is part of the SASS standard library.

We can also change the value of multiple elements on the same page. Assuming, again, that Marvin Vision was available on your system we can customize styles for both headline1 and headline2.

We can do the same thing with any style available.

$mdc-typography-styles-headline1: (
  font-family: unquote("MarvinVision, Helvetica, sans-serif")
$mdc-typography-styles-headline2: (
  font-family: unquote("Arial, Helvetica, sans-serif"),
  font-size: 3.25rem
// Imports and the rest of our code goes here

So far we’ve concentrated on how to use the built-in styles from Material Design. There is nothing to prevent us from mixing and matching material design typography with other design elements or art directing this kind of mixed application.

A full example of Material Design typography is in this Github repo

Another example is how Una Kravetz created a Material Design theme using variable fonts from Google Fonts

It’ll be interesting to see what we can do with Material Design moving forward.


Variable Fonts from Google Fonts

Developers who, like me, like Google Fonts have been frustrated by their not having Variable Fonts available.

That has changed in the last few weeks. Google has released an experimental API, available at

The API has a limited selection of fonts available and the syntax takes a little while getting used to it. Google font developers make the following assertion:

This version of the API isn’t completely stable. It’s best for experimental work while we document the new endpoint.

Syntax and Limitations

The new API announced in this codepen shows examples of how to use the new API and the difference between the new and the old API.

Once again, remember that the new API is not final and may change in unexpected ways. Until the API is finalized I’d advice against using it in production code.

The new endpoint is very strict about accepting requests.

  • List axes alphabetically
  • Axis value groups (i.e. tuples) need to be sorted numerically
  • Tuples can’t overlap or touch (e.g. wght 400..500 and 500..600)

The following examples are, as far as I understand them, how the new API works. They all use the Roboto font.

Emulating the old API

To load a single font without worrying about specifics, we can load it with the following command:

@import url('');

Indicating a Single Value

We can further refine the request by indicating what axis/value combination we want.

We do this by adding a colon and then indicating the axis name and value separated by an ampersand (@)

@import url('[email protected]');

Selecting multiple values from the same font

There are times when we want different values from the same axis and don’t want to add them separated by a semicolon (;)

@import url('[email protected];700');

Selecting mutliple font faces

Google Fonts gives us the choice of working with multiple styles for the same font. The syntax gets slightly more complex.

We first list the two axes in alphabetical order then use an ampersand (@) and then, for each axis we give the index of the axis and the value we want to use separated by a comma (,) and each comma-separated value separated by a semicolon (;).

@import url(',[email protected],700;1,700');

Working with variable fonts

The new API gives us the option of working with variable fonts and one or more axes available to the font.

Selecting a range of values from a single axis

Using multiple values of a single axis is similar too how we work a single axis but instead of putting the value as an index/value pair we use the values separated by two periods (..).

@import url('[email protected]');

Using ranges of values from multiple axes

This is the most intriguing part and what makes variable fonts so exciting to work with.

This example defines two axes and the range of values that we want to use for each.

@import url(',[email protected],200..900;1,200..900');

Adding display: swap

The font-display attribute allows developers to control how a font is displayed based on whether and when it is downloaded and ready to use.

To use font-display with Google fonts add the ?display= plus the value of font-display you want to use as the last element of the URL.

@import url(',[email protected],200..900;1,200..900?display=swap');

Available Variable Fonts

This is a list of the fonts available under the new API as of August 28, 2019.

Family Style Axis Min Max
Comfortaa normal wght 300 700
Crimson Pro normal wght 200 900
Crimson Pro italic wght 200 900
Dosis normal wght 200 800
Fira Code normal wght 300 700
Hepta Slab normal wght 1 900
Kreon normal wght 300 700
Literata normal wght 400 700
Literata italic wght 400 700
Markazi Text normal wght 400 700
Oswald normal wght 200 700
Quicksand normal wght 300 700

Closing Notes

While this is not a final API it gives us a lot of power in terms of we can use variable fonts in the Google Fonts API.

It’ll be interesting to see what additional fonts become available and what creative avenues it opens for typography on the web.