Dribbble Designs in Code (part 2 — Editorial Exploration)

This is the second in a series of posts on coding designs from Dribbble. If you haven’t seen the previous one yet, here’s part 1 — Venue Landing Page.

Vlad Sabev
14 min readJun 28, 2018

It’s close to midnight on a Saturday and I’m halfway through implementing another Dribbble shot for this series. After getting increasingly bored with it for the past few hours, I start browsing around for more inspiration.

Ending up back on Dribbble again, I find myself staring at this design when I suddenly realize how much more I like it for part 2:

It has an attractive layout, overlapping images, the promise of a challenge, and is a smooth transition from part 1. Inevitably, a question comes to mind:

🤔 Should I abandon the hard work done in the past couple of hours for something new and exciting?

After a few moments of consideration, I give into the temptation and start a new post. Here it is.

🤹‍ Skills

In this post, I explore using CSS grid and CSS variables in more detail.

If this is your first time working with CSS grid, I recommend watching Morten Rand-Hendriksen’s talk: CSS Grid Changes Everything (About Web Layouts) — he does a great job of defining the basic terminology and gives an overview of the different applications of this tool.

If you don’t have a code editor set up, prefer an environment where you can start right away, or want to dive straight into the code, see the Codepen I’ve prepared.

📈 Analysis

Just like in part 1, we’ll go through the following steps:

  • Add content and wrap it with HTML elements
  • Add SVG icons
  • Style the content & icons with CSS
  • Adjust the sizing and spacing to be closer to the expected result

This design however is a bit more complex — it has overlapping images, more content, and upon closer look could potentially have several pages worth of vertical space. Is doing a little analysis beforehand in order?

📱 Mobile Layout

In part 1 we didn’t have a mobile design, and so didn’t bother implementing one. Let’s adopt a better practice starting now — do a linear mobile layout first, then progressively enhance it for larger resolutions. This way the page is usable even if the browser doesn’t support CSS grid (which is increasingly rarer these days).

📝 This approach is also something I picked up from Morten Rand-Hendriksen’s talk: CSS Grid Changes Everything (About Web Layouts) —if you haven’t seen it yet, it’s well worth the watch!

⬜ Grid Layout

At first look, we have 3 distinct sections:

  • Navigation — back icon on the left and an icon to show there’s more content below
  • Content in the middle, comprised of logo, article header section, and some text
  • Overlapping images and a button on the right

With multiple rows and columns that are aligned with each other, this looks like a great fit for CSS grid!

We can also consider how the content will behave when scrolling down the page — would it maybe make sense for the user to always see the navigation icons?

📐 CSS Units

The design in part 1 used the vw unit to scale content proportionally on any screen regardless of the resolution. In practice, this only worked for desktop browsers in landscape orientation. This time we’ll use the rem and em units.

Why not good old pixels, you might say? The short answer is accessibility — some users set a default font-size other than 16px for their browsers, and with pixels text and spacing will probably scale poorly for them.

Here’s a couple of articles that explain the implications in more detail:

A short excerpt (emphasis mine):

Set the root HTML font-size as a percentage. That’s a percentage of the user’s default browser font-size. A typical method is to set the HTML font-size to 62.5%. That’s because 62.5% of 16px (typical default browser font-size) is 10px. That would still make 1.6rem = 16px. This now means that if the user’s default browser font-size is changed to, for example, 20px, 1.6rem would now equal 20px. So if your user wants bigger fonts, let them. Happy designer. Happy developer. All numbers are still easy to work with.

💻 Implementation

🔠 Content

Our fictitious publication is called the “Travel Times”.

The text passage is from the exceptional work of Douglas Adams — The Hitchhiker’s Guide to the Galaxy.

We’ll wrap that in a main element and add distinct elements with classes that we can style later:

<main>
<span class="logo">Travel Times</span>
<header>
<h1 class="title">
Travel in time by sea
</h1>

<div class="author">
by Jane Walkinson
</div>

<p class="subtitle">
Both of the men had been trained for this moment, their lives had been a preparation for it.
</p>
</header>
<div class="images"></div> <section class="text">
<p>
Both of the men had been trained for this moment, their lives had been a preparation for it, they had been selected at birth as those who would witness the answer, but even so they found themselves gasping and squirming like excited children.
</p>
</section>
</main>

Which looks like this:

Something else we can do is link to the author:

<div class="author">
<img src="https://raw.githubusercontent.com/vdsabev/editorial-exploration/master/author.png" />&nbsp;by&nbsp;
<a href="#" rel="author">Jane Walkinson</a>
</div>

The rel attribute of a link denotes its relationship to the original document — in this case, the article’s author.

📝 I’ve hosted the images on GitHub so that you can easily use them while following along with this post instead of having to start a local server or upload files somewhere.

Notice we reserved an empty container for the images right between the header and text sections — since we’re doing the mobile layout first, it makes sense to show the images to the user before they’ve gone through all of the text, but after they’ve read the article’s title and subtitle so they know what the images represent:

<div class="images">
<img class="images__primary" src="https://raw.githubusercontent.com/vdsabev/editorial-exploration/master/thailand.jpg" />
<img class="images__secondary" src="https://raw.githubusercontent.com/vdsabev/editorial-exploration/master/jellyfish.jpg" /> <button type="button" class="images__more">
+ 4 images
</div>
</div>

That + is a placeholder for the icon we’ll insert next.

🖼 Icons

Once again the Material Design Icons website can help us find what we’re looking for:

  • plus-circle-outline
  • arrow-left
  • arrow-down

The arrow-left and arrow-down icons are part of the navigation and so will fit nicely at the top of the content next to the logo, which in this case is a simple text element.

Here’s what all of our content looks like if we (temporarily) add a basic styling workaround of img, svg { max-width: 10rem; } to make images and SVGs fit on one page:

Finished content with images and icons

Time to add some styling!

💅 Styling

First things first — let’s add Normalize.css into the head tag of our page:

<link href="https://necolas.github.io/normalize.css/8.0.0/normalize.css" rel="stylesheet">

This makes styles render consistently across different browsers, for example spacing around h1 elements.

🎨 Color Palette

Using a color picker tool (like in Photoshop or Paint.NET), we can pick out the basic colors from the design and define some CSS variables on the root element where they can be easily found and, if necessary, changed:

:root {
--foreground: #1d2c2f;
--foregroundLight: #384345;
--neutral: #adb2b3;
--neutralLight: #fcfcfc;
--primary: #f8f72c;
}

Naming colors according to their purpose instead of their value (e.g. primary instead of yellow) means we can easily modify that value later without also having to change the name.

And adding suffixes like light / lighter / lightest or dark / darker / darkest is a simple rule of thumb for maintaining a consistent color palette.

📝 It’s a personal preference of mine to use --camelCase when naming CSS variables.

One reason is it’s a bit easier to select and copy the whole name at once. Another is that I will often define CSS variables in an external script to make them reusable throughout the code or even in webpack plugins.

Feel free to use --kebab-case or any other naming convention you like, as long as you’re consistent with it.

🔠 Fonts

There are 2 distinct fonts — one serif for the text content and another sans serif for the logo, images button, and author. Unfortunately, the original design files were lost, so we’ll have to do a bit of improvising 😅

The ordinary Arial should work well enough for UI elements, and for the text we’ll once again use Google Fonts to find a suitable candidate like Merriweather:

<link href="https://fonts.googleapis.com/css?family=Merriweather:300" rel="stylesheet">

We can now add a couple more variables to the root element:

--fontText: 'Merriweather', serif;
--fontUI: 'Arial', sans-serif;

📱 Mobile Layout

Using Google Chrome’s developer tools, let’s switch to responsive mode and select a common mobile device like iPhone 6/7/8 to see how the page looks:

Finished content as seen in responsive mode in Chrome’s developer tools

Since we already have a color palette and fonts, we can use them to style the background, text, and buttons. It’s also time to remove the sizing hack for images and SVGs and set a new size. Finally, we’ll add some spacing between different elements, for example padding to the body element:

Styled mobile layout

The design is really shaping up now, except the down icon sticks out a bit too much! Let’s take care of that.

📜 Scrolling

In practice, I rarely see designers draw scrollbars —they may cut off some content to indicate overflow, but that doesn’t always give specific information about which elements are scrollable.

Showing how the page looks with additional content, for example several paragraphs of text, can be beyond a designer’s initial goal of creating a quick Dribbble shot. When a design is actually meant to be implemented in code rather than only shown, we need to think about the behavior of the page on different devices and resolutions.

One option for us is to use position: sticky to stick the back icon to the top, while the down icon, which indicates there is more content below, sticks to the bottom of the page. Since those icons will be hard to make out when displayed over content, we’ll also add a neutral background and some border radius:

.back-icon,
.down-icon {
position: sticky;
border-radius: 50%;
background: var(--neutralLight);
}
.back-icon {
top: 1rem;
}
.down-icon {
bottom: 1rem;
}

One caveat is that for an element to be positioned properly within its parent, it has to be at the beginning of the content to stick to the top, or at the end to stick to the bottom.

This means we should move the HTML for the down icon from before the article’s title to after the article’s text.

Here’s how that looks when the page is scrolled down:

Styled mobile layout with improved design for scrolling

🤙 Responsive Design with CSS Variables

In our case, the font-size and spacing from the original design wouldn’t have worked well on mobile devices, so we used smaller values. How do we increase them on wider resolutions?

One option is to write media queries targeting the classes whose properties we want to change. Something like this:

body {
padding: 3rem;
}
.title {
font-size: 5rem;
}
.subtitle {
font-size: 2rem;
}
@media (min-width: 48em) {
body {
padding: 3.6rem;
}
.title {
font-size: 7rem;
}
.subtitle {
font-size: 2.2rem;
}
}
@media (min-width: 64em) {
body {
padding: 6.8rem;
}
.title {
font-size: 10rem;
}
.subtitle {
font-size: 2.4rem;
}
}

This approach is fine in simple use cases, but doesn’t scale well for more complex ones, especially when multiple properties depend on each other like a certain element’s padding being half of another one’s.

An especially useful property of CSS variables is that their values cascade down to all children of an element. This means we can write maintainable and concise CSS while overriding variable values for different device resolutions:

/* First, define variables and responsive breakpoints */
:root {
--spacing: 3rem;
--titleFontSize: 5rem;
--subtitleFontSize: 2rem;
}
@media (min-width: 48em) {
:root {
--spacing: 3.6rem;
--titleFontSize: 7rem;
--subtitleFontSize: 2.2rem;
}
}
@media (min-width: 64em) {
:root {
--spacing: 6.8rem;
--titleFontSize: 10rem;
--subtitleFontSize: 2.4rem;
}
}
/* Now, use the variables in classes
without having to think about responsive breakpoints! */
body {
padding: var(--spacing);
}
.title {
font-size: var(--titleFontSize);
}
.subtitle {
font-size: var(--subtitleFontSize);
}

If we now use the spacing variable in multiple places (which we’ll do in a minute), we can define how its value changes on different resolutions from a single place in our code instead of having to dig around class definitions.

⬜ Grid Layout

Now that we got our page working on mobile devices and made some of the values responsive using CSS variables, how can we divide the layout into grid cells and position elements in them?

Let’s first draw some grid tracks on top of the original design:

Original design with grid tracks overlaid

We have the following rows:

  • Back icon and logo
  • Title, author, and subtitle
  • Article text

And the columns, separated by different horizontal space:

  • Navigation icons
  • Logo, header, content, and text
  • Images, spanning across the full height of the page

📝 It’s possible to divide the images into rows and columns and also fit them into the same grid, but that could behave in an unpredictable way if the article’s title, subtitle, or the images themselves have a completely different height.

After all we’re not just coding a drawing of a design here, but thinking about how this design would behave in a real-world online magazine or news publication.

It’s all good, we’ll find a way to take care of the images later 😉

To achieve all of this, we’ll use a combination of empty columns and grid-template-areas, but only for resolutions wider than 56em (or 896px if the base font-size of the browser is 16px) to preserve the mobile-first design we started with:

@media (min-width: 56em) {
main {
display: grid;
grid-template-rows: auto auto 1fr;
grid-template-columns: auto calc(var(--spacing) / 2) 1fr var(--spacing) 37.5%;
grid-template-areas:
"back-icon . logo . images"
". . header . images"
"down-icon . text . images";
}
}

Notice how empty columns are defined in the grid-template-areas with a dot ., and in the grid-template-columns they take up spacing / 2 and spacing respectively.

The navigation icons take up as much space as they need (auto), the images are 37.5% of the grid’s width, and the central content takes up all the remaining space (1fr).

📝 I came up with these numbers by measuring the pixels between elements in the original design and approximating the ratio between them. Whenever possible, reach out to your design team — they might have more insights into the relationship between different elements!

Now we need to assign every element to an area in our grid, vertically center the back icon and logo, and push the down icon all the way to the end of its cell:

@media (min-width: 56em) {
main { ... } /* same as before */
.back-icon {
grid-area: back-icon;
align-self: center;
}
.logo {
grid-area: logo;
align-self: center;
}
.down-icon {
grid-area: down-icon;
align-self: flex-end;
}
header {
grid-area: header;
}
.text {
grid-area: text;
}
.images {
grid-area: images;
}
}

And voilà:

Styled content with CSS grid layout

We’re almost there!

⬜ Image Grid Layout

There are different ways to get elements to overlap — floats, negative margins, absolute positioning — since we already used CSS grid once, let’s do it again!

Here are the tracks:

Images with grid tracks overlaid
  • First, there’s the primary image which takes up one row and both columns.
  • Then the button occupies the second row and first column.
  • Finally, the secondary image overlaps the primary one, starting from the end of the second row and extending into the second column.

This is achieved with the following CSS:

.images {
grid-area: images; /* same as before */
display: grid;
grid-template-rows: auto auto 1fr;
grid-template-columns: 7fr 9fr;
grid-template-areas:
"primary primary "
"more secondary";
}
.images__primary {
grid-area: primary;
}
.images__secondary {
grid-row: primary / secondary;
grid-column: secondary;
align-self: flex-end;
}
.images__more {
grid-area: more;
}

Notice how we define the first two rows as auto, then add one more with size of 1fr to take up the remaining space. There are also other ways to achieve the same effect with CSS grid, for example by using grid-auto-rows: min-content.

Once again, the ratio of 7fr / 9fr between the columns was approximated by measuring the pixels in the design.

Something else to note is how we overlap the two elements— the secondary image takes up both rows and is pushed to the end of the container, so is displayed above the primary image and at the same time aligned with the bottom of the button.

🏁 Finish Line

A few more adjustments to spacing and sizing and we have our finished design:

Let’s see the original once again for comparison:

Some notable changes are that spacing, fonts, icons, and images are slightly different as the original design files were lost and we only had a PNG to work with. On the other hand, our design handles mobile devices and scrolling gracefully!

Here’s the actual code running on CodePen:

💭 Conclusion

Hey, you made it all the way to the end! Double points if you also read Dribbble Designs in Code (part 1 — Venue Landing Page) 😉

In this post:

  • We went through the process of analyzing a design.
  • After that, we added the content and icons, applied a mobile-first style, and improved its behavior when scrolling.
  • Finally, we progressively enhanced the design using CSS grid and even made “gridception” happen by putting a grid within a grid!

It took me a while to code this and even more to write the post, and I’m delighted with how it turned out!

The original design was never meant to be implemented in code so we had to make many decisions about how it should behave in different situations.

Was this post helpful? Would you like there to be another one? Anything different you want to see?

There was some great feedback on my previous post— I’d love to hear what you think of this one — say hello on Twitter or let me know in the comments right here 👇

--

--