Building a Headless CMS with Fauna and Vercel Functions
Strategy

Building a Headless CMS with Fauna and Vercel Functions


This article introduces the concept of the headless CMS, a backend-only content management system that allows developers to create, store, manage and publish the content over an API using the Fauna and Vercel functions. This improves the frontend-backend workflow, that enables developers to build excellent user experience quickly.

In this tutorial, we will learn and use headless CMS, Fauna, and Vercel functions to build a blogging platform, Blogify🚀. After that, you can easily build any web application using a headless CMS, Fauna and Vercel functions.

Introduction

According to MDN, A content management system (CMS) is a computer software used to manage the creation and modification of digital content. CMS typically has two major components: a content management application (CMA), as the front-end user interface that allows a user, even with limited expertise, to add, modify, and remove content from a website without the intervention of a webmaster; and a content delivery application (CDA), that compiles the content and updates the website.

The Pros And Cons Of Traditional vs Headless CMS

Choosing between these two can be quite confusing and complicated. But they both have potential advantages and drawbacks.

Traditional CMS Pros

  • Setting up your content on a traditional CMS is much easier as everything you need (content management, design, etc) are made available to you.
  • A lot of traditional CMS has drag and drop, making it easy for a person with no programming experience to work easily with them. It also has support for easy customization with zero to little coding knowledge.

Traditional CMS Cons

  • The plugins and themes which the traditional CMS relies on may contain malicious codes or bugs and slow the speed of the website or blog.
  • The traditional coupling of the front-end and back-end definitely would more time and money for maintenance and customization.

Headless CMS Pros

  • There’s flexibility with choice of frontend framework to use since the frontend and backend are separated from each other, it makes it possible for you to pick which front-end technology suits your needs. It gives the freewill to choose the tools need to build the frontend—flexibility during the development stage.
  • Deploying works easier with headless CMS. The applications (blogs, websites, etc) built with headless CMS can be easily be deployed to work on various displays such as web device, mobile devices, AR/VR devices.

Headless CMS Cons

  • You are left with the worries of managing your back-end infrastructures, setting up the UI component of your site, app.
  • Implementation of headless CMS are known to be more costly against the traditional CMS. Building headless CMS application that embodies analytics are not cost-effective.

Fauna uses a preexisting infrastructure to build web applications without the usually setting up a custom API server. This efficiently helps to save time for developers, and the stress of choosing regions and configuring storage that exists among other databases; which is global/multi-region by default, are nonexistent with Fauna. All maintenance we need are actively taken care of by engineers and automated DevOps at Fauna. We will use Fauna as our backend-only content management system.

Pros Of Using Fauna

  • The ease to use and create a Fauna database instance from within development environment of the hosting platforms like Netlify or Vercel.
  • Great support for querying data via GraphQL or use Fauna’s own query language. Fauna Query Language (FQL), for complex functions.
  • Access data in multiple models including relational, document, graph and temporal.
  • Capabilities like built-in authentication, transparent scalability and multi-tenancy are fully available on Fauna.
  • Add-on through Fauna Console as well as Fauna Shell makes it easy to manage database instance very easily.

Vercel Functions, also known as Serverless Functions, according to the docs are pieces of code written with backend languages that take an HTTP request and provide a response.

Prerequisites

To take full advantage of this tutorial, ensure the following tools are available or installed on your local development environment:

  • Access to Fauna dashboard
  • Basic knowledge of React and React Hooks
  • Have create-react-app installed as a global package or use npx to bootstrap the project.
  • Node.js version >= 12.x.x installed on your local machine.
  • Ensure that npm or yarn is also installed as a package manager

Database Setup With Fauna

Sign in into your fauna account to get started with Fauna, or first register a new account using either email credentials/details or using an existing Github account as a new user. You can register for a new account here. Once you have created a new account or signed in, you are going to be welcomed by the dashboard screen. We can also make use of the fauna shell if you love the shell environment. It easily allows you to create
and/or modify resources on Fauna through the terminal.

Using the fauna shell, the command is:

npm install --global fauna-shell
fauna cloud-login

But we will use the website throughout this tutorial. Once signed in, the dashboard screen welcomes you:

Now we are logged in or have our accounts created, we can go ahead to create our Fauna. We’ll go through following simple steps to create the new fauna database using Fauna services. We start with naming our database, which we’ll use as our content management system. In this tutorial, we will name our database blogify.

With the database created, next step is to create a new data collection from the Fauna dashboard. Navigate to the Collection tab on the side menu and create a new collection by clicking on the NEW COLLECTION button.

We’ll then go ahead to give whatever name well suiting to our collection. Here we will call it blogify_posts.

Next step in getting our database ready is to create a new index. Navigate to the Indexes tab to create an index. Searching documents in Fauna can be done by using indexes, specifically by matching inputs against an index’s terms field. Click on the NEW INDEX button to create an index. Once in create index screen, fill out the form: selecting the collection we’ve created previously, then giving a name to our index. In this tutorial, we will name ours all_posts. We can now save our index.

After creating an index, now it’s time to create our DOCUMENT, this will contain the contents/data we want to use for our CMS website. Click on the NEW DOCUMENT button to get started. With the text editor to create our document, we’ll create an object data to serve our needs for the website.

The above post object represents the unit data we need to create our blog post. Your choice of data can be so different from what we have here, serving the purpose whatever you want it for within your website. You can create as much document you may need for your CMS website. To keep things simple, we just have three blog posts.

Now that we have our database setup complete to our choice, we can move on to create our React app, the frontend.

Create A New React App And Install Dependencies

For the frontend development, we will need dependencies such as Fauna SDK, styled-components and vercel in our React app. We will use the styled-components for the UI styling, use the vercel within our terminal to host our application. The Fauna SDK would be used to access our contents at the database we had setup. You can always replace the styled-components for whatever library you decide to use for your UI styling. Also use any UI framework or library you preferred to others.

npx create-react-app blogify
# install dependencies once directory is done/created
yarn add fauna styled-components
# install vercel globally
yarn global add vercel

The fauna package is Fauna JavaScript driver for Fauna. The library styled-components allows you to write actual CSS code to style your components. Once done with all the installation for the project dependencies, check the package.json file to confirm all installation was done
successfully.

Now let’s start an actual building of our blog website UI. We’ll start with the header section. We will create a Navigation component within the components folder inside the src folder, src/components, to contain our blog name, Blogify🚀.

import styled from "styled-components";
function Navigation() {
  return (
    <Wrapper>
      <h1>Blogify🚀</h1>
    </Wrapper>
  );
}
const Wrapper = styled.div`
  background-color: #23001e;
  color: #f3e0ec;
  padding: 1.5rem 5rem;
  & > h1 {
    margin: 0px;
  }
`;
export default Navigation;

After being imported within the App components, the above code coupled with the stylings through the styled-components library, will turn out to look like the below UI:

Now time to create the body of the website, that will contain the post data from our database. We structure a component, called Posts, which will contains our blog posts created on the backend.

import styled from "styled-components";
function Posts() {
  return (
    <Wrapper>
      <h3>My Recent Articles</h3>
      <div className="container"></div>
    </Wrapper>
  );
}
const Wrapper = styled.div`
  margin-top: 3rem;
  padding-left: 5rem;
  color: #23001e;
  & > .container {
    display: flex;
    flex-wrap: wrap;
  }
  & > .container > div {
    width: 50%;
    padding: 1rem;
    border: 2px dotted #ca9ce1;
    margin-bottom: 1rem;
    border-radius: 0.2rem;
  }
  & > .container > div > h4 {
    margin: 0px 0px 5px 0px;
  }
  & > .container > div > button {
    padding: 0.4rem 0.5rem;
    border: 1px solid #f2befc;
    border-radius: 0.35rem;
    background-color: #23001e;
    color: #ffffff;
    font-weight: medium;
    margin-top: 1rem;
    cursor: pointer;
  }
  & > .container > div > article {
    margin-top: 1rem;
  }
`;
export default Posts;

The above code contains styles for JSX that we’ll still create once we start querying for data from the backend to the frontend.

Integrate Fauna SDK Into Our React App

To integrate the fauna client with the React app, you have to make an initial connection from the app. Create a new file db.js at the directory path src/config/. Then import the fauna driver and define a new client.
The secret passed as the argument to the fauna.Client() method is going to hold the access key from .env file:

import fauna from 'fauna';
const client = new fauna.Client({
  secret: process.env.REACT_APP_DB_KEY,
});
const q = fauna.query;
export { client, q };

Inside the Posts component create a state variable called posts using useState React Hooks with a default value of an array. It is going to store the value of the content we’ll get back from our database using the setPosts function. Then define a second state variable, visible, with a default value of false, that we’ll use to hide or show more post content using the handleDisplay function that would be triggered by a button we’ll add later in the tutorial.

function App() {
  const [posts, setPosts] = useState([]);
  const [visible, setVisibility] = useState(false);
  const handleDisplay = () => setVisibility(!visible);
  // ...
}

Creating A Serverless Function By Writing Queries

Since our blog website is going to perform only one operation, that’s to get the data/contents we created on the database, let’s create a new directory called src/api/ and inside it, we create a new file called index.js. Making the request with ES6, we’ll use import to import the client and the query instance from the config/db.js file:

export const getAllPosts = client
  .query(q.Paginate(q.Match(q.Ref('indexes/all_posts'))))
    .then(response => {
      const expenseRef = response.data;
      const getAllDataQuery = expenseRef.map(ref => {
        return q.Get(ref);
      });
     return client.query(getAllDataQuery).then(data => data);
   })
   .catch(error => console.error('Error: ', error.message));
 })
 .catch(error => console.error('Error: ', error.message));

The query above to the database is going to return a ref that we can map over to get the actual results need for the application. We’ll make sure to append the catch that will help check for an error while querying the database, so we can log it out.

Next is to display all the data returned from our CMS, database—from the Fauna collection. We’ll do so by invoking the query getAllPosts from the ./api/index.js file inside the useEffect Hook inside our Posts component. This is because when the Posts component renders for the first time, it iterates over the data, checking if there are any post in the database:

useEffect(() => {
  getAllPosts.then((res) => {
    setPosts(res);
    console.log(res);
  });
}, []);

Open the browser’s console to inspect the data returned from the database. If all things being right, and you’re closely following, the return data should look like the below:

With these data successfully returned from the database, we can now complete our Posts components, adding all necessary JSX elements that we’ve styled using styled-components library. We’ll use JavaScript map to loop over the posts state, array, only when the array is not empty:

import { useEffect, useState } from "react";
import styled from "styled-components";
import { getAllPosts } from "../api";

function Posts() {
  useEffect(() => {
    getAllPosts.then((res) => {
      setPosts(res);
      console.log(res);
    });
  }, []);

  const [posts, setPosts] = useState([]);
  const [visible, setVisibility] = useState(false);
  const handleDisplay = () => setVisibility(!visible);

  return (
    <Wrapper>
      <h3>My Recent Articles</h3>
      <div className="container">
        {posts &&
          posts.map((post) => (
            <div key={post.ref.id} id={post.ref.id}>
              <h4>{post.data.post.title}</h4>
              <em>{post.data.post.date}</em>
              <article>
                {post.data.post.mainContent}
                <p style={{ display: visible ? "block" : "none" }}>
                  {post.data.post.subContent}
                </p>
              </article>
              <button onClick={handleDisplay}>
                {visible ? "Show less" : "Show more"}
              </button>
            </div>
          ))}
      </div>
    </Wrapper>
  );
}

const Wrapper = styled.div`
  margin-top: 3rem;
  padding-left: 5rem;
  color: #23001e;
  & > .container {
    display: flex;
    flex-wrap: wrap;
  }
  & > .container > div {
    width: 50%;
    padding: 1rem;
    border: 2px dotted #ca9ce1;
    margin-bottom: 1rem;
    border-radius: 0.2rem;
  }
  & > .container > div > h4 {
    margin: 0px 0px 5px 0px;
  }
  & > .container > div > button {
    padding: 0.4rem 0.5rem;
    border: 1px solid #f2befc;
    border-radius: 0.35rem;
    background-color: #23001e;
    color: #ffffff;
    font-weight: medium;
    margin-top: 1rem;
    cursor: pointer;
  }
  & > .container > div > article {
    margin-top: 1rem;
  }
`;

export default Posts;

With the complete code structure above, our blog website, Blogify🚀, will look like the below UI:

Deploying To Vercel

Vercel CLI provides a set of commands that allow you to deploy and manage your projects. The following steps will get your project hosted from your terminal on vercel platform fast and easy:

vercel login

Follow the instructions to login into your vercel account on the terminal

vercel

Using the vercel command from the root of a project directory. This will prompt questions that we will provide answers to depending on what’s asked.

vercel
? Set up and deploy “~/Projects/JavaScript/React JS/blogify”? [Y/n] 
? Which scope do you want to deploy to? ikehakinyemi
? Link to existing project? [y/N] n
? What’s your project’s name? (blogify) 
  # click enter if you don't want to change the name of the project
? In which directory is your code located? ./ 
  # click enter if you running this deployment from root directory
? ? Want to override the settings? [y/N] n

This will deploy your project to vercel. Visit your vercel account to complete any other setup needed for CI/CD purpose.

Conclusion

I’m glad you followed the tutorial to this point, hope you’ve learnt how to use Fauna as Headless CMS. The combination of Fauna with Headless CMS concepts you can build great web application, from e-commerce application to Notes keeping application, any web application that needs data to be stored and retrieved for use on the frontend. Here’s the GitHub link to code sample we used within our tutorial, and the live demo which is hosted on vercel.



Source link

Fundamental specs and an example of odd spacing
Strategy

Design Implementation With App Development


Introduction

We’ve all been there when we have to manually redline our designs so developers know exactly what our perfect designs are actually showing; we have even tried sending over source files for our devs to use when implementing. But isn’t there a better way of sending over detailed design information? 

There are ways we can make markups easier and a way of disseminating this information quicker and more efficiently. This is of the utmost importance; if a developer doesn’t have all the information in front of them they will not spend precious time digging around for important information, a good handoff is a detailed project with information to hand or directly on top of the designs for quick implementation.

This is a full guide on how to detail everything important and ensure you give your developer every important piece of information that you can for them to implement your designs as best they can.

The main goal is to allow your developer to be as close to 100% independent of you as possible. If there are any decisions to be made during development then the developer can make them with appropriate confidence in getting the choice correct.

Many designers still only send source files or upload designs to Figma or Mockplus to let the dev check specs. There are, however, additional steps we can take to ensure a smooth handoff and implementation. 

Below, I have written about key areas in which designers need to concentrate on when preparing a handoff document. These touch on the main aspects of a design and at the core of this article, I preach spending more time making obvious but important markups that allow the developer to not search out for design information.

Fundamentals

  • Fonts, specific manual specs, distance, regional markups, color, user flows, spacings. Interactions. Reference sheet.

Common design details need to be consistent across your app or design. Spacings need to be consistent, different text styles need to be clear, the user flow has to be laid out clearly from the start. These are items we as designers come across frequently that are easy to overlook but need to be consistently marked across the app, otherwise, our finished product might look messy and be less impactful on target users. Not only for us in the designing stage, but we also need to clearly markup what common design choices we have made for our developer to carry that over to the developed app.

Fundamental specs and an example of odd spacing

Fundamental specs

Writing these down is especially important in helping to reduce the likelihood of errors, such as mistakes in the pixels, needing the dev to round up or down to a certain value for example.

Material Design font style example

Material Design font style example

Icons

  •  Meaning of icons and their actions.

To better help developers create the most useful and accessible app, include an icons page in your design to explain meanings and uses. For example, if there is an icon for “download” without text, explain this tag, and where it should be included, and reference the link page.

Including this page ensures the developer understands the importance of these icons and their addition to buttons creating a secondary action.

States

Page states can vary depending on what type of page is being shown. The loading page state, empty page state, error state, success state, interactive state (for inputting data), and more. In doing so this reduces the likelihood of the dev encountering such page states and as a result reduces the chances of a dev not implementing a proper solution to an empty page, for example. 

Button states are incredibly important parts of how a user interacts with an app or page. Whether this button is a hover or pressed button, or enable or disable—there need to be bindings to each button and the developer needs to know the bounds so they can distinguish each button’s behavior and consequent actions. This is especially important because states are high-level elements – features the user interacts with frequently and need to be implemented correctly.

A visual matrix that illustrates a range of button states for a set of primary and secondary buttons.

Every product is different, but having common states for the engineer to fall back on is useful to copy and paste consistent pages throughout the app. Working out every single page and button state is important so the developer can keep their work streamlined and not need to come back to you to figure out a new state. 

Colors and Meaning

  • Warnings, alerts, confirmation.

This is more than just a color palette; this is regarding the main “traffic lights” in a product. Every product needs a color system, whether that’s for positive, negative outcomes, or errors. There are common colors in a project and need to be explained to the developer in case there are other states that the designer hasn’t planned for and the developer can independently implement the correct color.

To further reduce errors in implementation, include your primary, secondary and tertiary colors. Include any dark mode variants. Rather than using hex units, specify in your design system the various colors you have, so for example rather than #FC8EAC, you have “Light Pink.”

Design System

This is related to other sections, but it’s important for every designer to utilize their design system to combat any misunderstandings or complex data—such as hex values. 

Similar to the hex values above, try to create shortcuts for any text label you have in your design system. Custom labels allow quick implementation. Create shortcuts and keywords for as much as you can to help reduce errors in copying names and labels of elements in the design system. Also, look to create common buttons and to put brand assets in the DS. 

Resources

Use a detailed PRD that links directly to your design so you can check your work against your PRD regularly. The PM will be happy to sign off if they see everything has been implemented correctly. The dev will also then be an

Another benefit of having a resource area where a PRD and other resources are kept is that important links are kept in one place. This is important for referencing resources such as a link to a specific component’s implementation. This can be done either in a reference tool or by commenting directly on the design with a link to the reference. 

Communication 

  • Commenting and task management.

 Task management on Mockplus

 Task management on Mockplus

No matter how hard you try to completely hand off your project to your dev, they will never be 100% independent. You will need to talk to them as new issues arise. That’s okay. The best you can do is use integrated commenting and task management systems where you both give each other specific tasks that are linked to artboards or comment directly on an aspect of design that needs clarification or a rethink.

This all goes back to consistency, ease of implementation, and communication. Having a free-flowing, integrated commenting and task management system, team members are able to efficiently continue working on the project while waiting for comments or tasks to be reviewed. 

Summary

All of these design details need to be communicated to the developer so you don’t destroy all of your hard work by not doing the handoff properly. A poor handoff can destroy a project. Remember, the developer isn’t designing the app; they are creating it, and your job is to ensure they can use your design to create it as best they can. 

If they are spending time frequently going over details with you, the project will be delayed and they will not implement everything as you intended. That’s when mistakes happen, either miscommunication through email or text, or simply wrong implementation. Spending the time to actually mark up important details that you want to be implemented correctly ensures more accurate implementation and a more successful product launch.



Source link

Equal Columns With Flexbox: It’s More Complicated Than You M...
Strategy

Equal Columns With Flexbox: It’s More Complicated Than You M…


You get a nice-looking design handed to you and it has this nice big hero section, followed by one of those three-up columns right beneath it. You know, like almost every other website you’ve ever worked on.

You bang through the hero section and get to work on the three-column section. It’s time to pull out our trusty friend flexbox! Except, you write display: flex and you get this mess instead of getting the three equal columns you’d expect.

This happens because of how flexbox calculates the base size of an element. You’ve probably read lots of flexbox tutorials, and many of them (including my own) are an overly simplistic example of what flexbox does without really digging into the complex things that flexbox does for us that we take for granted.

I’m positive you’ve seen examples and tutorials that look at something like this, where three divs shrink down around the content that’s inside them:

In other words, we get some block-level elements shrinking down and slotting next to one another. It feels like flex wants things to be as small as possible. But in reality, flexbox actually wants things to be as big as possible.

Wait, what? Flex shrinks things by default — that can’t be right! Right?

As awesome as flexbox is, what it’s doing under the hood is actually a little strange because, by default, it is doing two things at once. It first looks at the content size which is what we would get if by declaring width: max-content on an element. But on top of that, flex-shrink is also doing some work allowing the items to be smaller, but only if needed.

To really understand what’s going on, let’s break those two down and see how they work together.

Diving into max-content

max-content is a pretty handy property value in certain situations, but we want to understand how it works in this particular situation where we are trying to get three seemingly simple equal columns. So let’s strip away flexbox for a moment and look at what max-content does on its own.

MDN does a good job of explaining it:

The max-content sizing keyword represents the intrinsic maximum width of the content. For text content this means that the content will not wrap at all even if it causes overflows.

Intrinsic might throw you off here, but it basically means we’re letting the content decide on the width, rather than us explicitly setting a set width. Uri Shaked aptly describes the behavior by saying “the browser pretends it has infinite space, and lays all the text in a single line while measuring its width.”

So, bottom line, max-content allows the content itself to define the width of the element. If you have a paragraph, the width of that paragraph will be the text inside it without any line breaks. If it’s a long enough paragraph, then you end up with some horizontal scrolling.

Let’s revisit that overly-simplistic example of three block-level elements that shrink down and slot next to one another. That isn’t happening because of flex-shrink; it’s happening because that’s the size of those elements when their declared width is max-content. That’s literally as wide as they go because that’s as wide as the combined content inside each element.

Here, take a look at those elements without flexbox doing it’s flexbox stuff, but with a width: max-content on there instead:

So, when there’s just a small amount of text, the intrinsic max-content shrinks things down instead of flex-shrink. Of course, flexbox also comes in with it’s default flex-direction: row behavior which turns the flex items into columns, putting them right next to one another. Here’s another look but with the free space highlighted.

Adding flex-shrink to the equation

So we see that declaring display: flex pushes that max-content intrinsic size on flex items. Those items want to be as big as their content. But there is another default that comes in here as well, which is flex-shrink.

flex-shrink is basically looking at all the flex items in a flexible container to make sure they don’t overflow the parent. If the flex items can all fit next to each other without overflowing the flexible container, then flex-shrink won’t do anything at all… it’s job is already done.

But if the flex items do overflow the container (thanks to that max-content intrinsic width thing), the flex items are allowed to shrink to prevent that overflow because flex-shrink is looking out for that.

This is why flex-shrink has a default value of 1. Without it, things would get messy pretty quickly.

Here’s why the columns aren’t equal

Going back to our design scenario where we need three equal columns beneath a hero, we saw that the columns aren’t equal widths. That’s because flexbox starts by looking at the content size of each flex item before even thinking about shrinking them.

Using Firefox’s DevTools (because it’s got some unique flexbox visualizations that others don’t), we can actually see how flexbox is calculating everything.

For simplicity’s sake, as we dive deeper into this, let’s work with some nice round numbers. We can do this by declaring widths on our flex items. When we declare a width on a flex item, we throw that intrinsic size out the window, as we’ve now declared an explicit value instead. This makes figuring out what’s really going on a lot easier.

In the Pen below, we have a parent that’s a 600px wide flexible container (display: flex). I’ve removed anything that might influence the numbers, so no gap or padding. I’ve also switched out the border for an outline so we can still visualize everything easily.

The first and third flex items have a width: 300px and the middle one a width: 600px. If we add that all up, it’s a total of 1200px. That’s bigger than the the 600px available within the parent, so flex-shrink kicks in.

flex-shrink is a ratio. If everything has the same flex-shrink (which is 1 by default), they all shrink at the same rate. That doesn’t mean they all shrink to the same size or by the same amount, but they all shrink at the same rate.

If we jump back into Firefox DevTools, we can see the base size, the flex-shrink and the final size. In this case, the two 300px elements are now 150px, and the 600px one is now 300px.

The two elements that have a base width of 300px become 150px.
The larger element with a base width of 600px becomes 300px.

If we add up all the base sizes of all three flex items (the actual widths we declared on them), the total comes out to 1200px. Our flex container is 600px wide. If we divide everything by 2, it fits! They are all shrinking by the same rate, dividing their own widths by 2.

It’s not often that we have nice round numbers like that in the real world, but I think this does a nice job illustrating how flexbox does what it does when figuring out how big to make things.

Getting the columns to be equal

There are a few different ways to get the three columns we want to be equal in width, but some are better than others. For all the approaches, the basic idea is that we want to get all the columns base size to be the same. If they have an equal base size, then they will shrink (or grow, if we use flex-grow) at an equal rate when flexbox does it’s flex things, and in theory, that should make them the same size.

There are a few common ways to do this, but as I discovered while diving into all of this, I have come to believe those approaches are flawed. Let’s look at two of the most common solutions that I see used in the wild, and I’ll explain why they don’t work.

Method 1: Using flex: 1

One way we can try to get all the flex items to have the same base size is by declaring flex: 1 on all of them:

.flex-parent { display: flex; }
.flex-parent > * { flex: 1; }

In a tutorial I made once, I used a different approach, and I must have had 100 people asking why I wasn’t using flex: 1 instead. I replied by saying I didn’t want to dive into the flex shorthand property. But then I used flex: 1 in a new demo, and to my surprise, it didn’t work.

The columns weren’t equal.

The middle column here is larger than the other two. It’s not by a ton, but the whole design pattern I’m creating is just so you have perfectly equal columns every single time, regardless of the content.

So why didn’t it work in this situation? The culprit here is the padding on the component in the middle column.

And maybe you’ll say it’s silly to add padding to one of the items and not the others, or that we can nest things (we’ll get to that). In my opinion, though, I should be able to have a layout that works regardless of the content that we’re putting in it. The web is all about components that we can plug and play these days, after all.

When I first set this up, I was sure it would work, and seeing this issue pop up made me want to learn what was really going on here.

The flex shorthand does more than just set the flex-grow: 1. If you don’t already know, flex is shorthand for flex-grow, flex-shrink, and flex-basis.

The default values for those constituent properties are:

.selector {
  flex-grow: 0;
  flex-shrink: 1;
  flex-basis: auto;
}

I’m not going to deep dive flex-basis in this article, as that’s something Robin has already done well. For now, we can think of it like width to keep things simple since we aren’t playing with flex-direction.

We’ve already seen how flex-shrink works. flex-grow is the opposite. If the size of all the flex items along the main axis is smaller than the parent, they can grow to fill that space.

So by default, flex items:

  • don’t grow;
  • if they would otherwise overflow the parent, they are allowed to shrink;
  • their width acts like max-content.

Putting that all together, the flex shorthand defaults to:

.selector {
  flex: 0 1 auto;
}

The fun thing with the flex shorthand is you can omit values. So, when we declare flex: 1, it’s setting the first value, flex-grow, to 1, which basically turns on flex-grow.

The strange thing here is what happens to the values that you omit. You’d expect them to stay at their defaults, but they don’t. Before we get to what happens, though, let’s first dive into what flex-grow even does.

As we’ve seen, flex-shrink allows elements to shrink if their base sizes add up to a computed value that’s bigger than the available width of the parent container. flex-grow is the opposite. If the grand total of the element base sizes is smaller than the value of the parent container’s width, then they will grow to fill the available space.

If we take that super basic example where we have three small divs next to one another and add flex-grow: 1, they grow to fill that leftover space.

But if we have three divs with unequal widths — like those ones we started with — adding flex-grow to them won’t do anything at all. They won’t grow because they’re already taking up all the available space —so much space, in fact, that flex-shrink needs to kick in and shrink them down to fit!

But, as folks have pointed out to me, setting flex: 1 can work to create equal columns. Well, sort of, as we saw above! In simple situations it does work though, as you can see below.

When we declare flex: 1 it works because, not only does this set the flex-grow to 1, but it also changes the flex-basis!

.selector {
  flex: 1;
  /* flex-grow: 1; */
  /* flex-shrink: 1; */
  /* flex-basis: 0%; Wait what? */
}

Yup, setting flex: 1 sets the flex-basis to 0%. This overwrites that intrinsic sizing we had before that behaved like max-content. All of our flex-items now want to have a base size of 0!

Looking at the expanded view of the flex: 1 shorthand in DevTools shows us that the flex-basis has changed to 0

So their base sizes are 0 now, but because of the flex-grow, they can all grow to fill up the empty space. And really, in this case, flex-shrink is no longer doing anything, as all the flex items now have a width of 0, and are growing to fill the available space.

FireFox’s DevTools showing an element with flex: 1 has a content size of 0 and is growing to fill the available space.

Just like the shrink example before, we’re taking the space that’s available, and letting all the flex items grow at an equal rate. Since they are all a base width of 0, growing at an equal rate means the available space is equally divided between them and they all have the same final size!

Except, as we saw, that’s not always the case…

The reason for this is because, when flexbox does all this stuff and distributes the space, whether it’s shrinking or growing a flex item, it’s looking at the content size of the element. If you remember back to the box model, we have the content size itself, then the padding, border, and margin outside of that.

And no, I didn’t forget * { box-sizing: border-box; }.

This is one of those strange quirks of CSS but it does make sense. If the browser looked at the width of those elements and included their padding and borders in the calculations, how could it shrink things down? Either the padding would also have to shrink or things are going to overflow the space. Both of those situations are terrible, so instead of looking at the box-size of elements when calculating the base size of them, it only looks at the content-box!

So, setting flex: 1 causes a problem in cases where you have borders or padding on some of your elements. I now have three elements that have a content-size of 0, but my middle one has padding on it. If we didn’t have that padding, we’d have the same math we did when we looked at how flex-shrink works.

A parent that is 600px and three flex items with a width of 0px. They all have a flex-grow: 1 so they grow at an equal rate, and they each end up 200px wide. But the padding mucks it all up. Instead, I end up with three divs with a content size of 0, but the middle one has padding: 1rem. That means it has a content size of 0, plus 32px padding as well.

We have 600 - 32 = 568px to divide equally, instead of 600px. All the divs want to grow at an equal rate, so 568 / 3 = 189.3333px.

And that’s what happens!

But… remember, that’s their content size, not the total width! That leaves us with two divs with a width of 189.333px, and another with a which of 189.333px + 32 = 221.333px. That’s a pretty substantial difference!

Method 2: flex-basis: 100%

I have always handled this like this:

.flex-parent {
  display: flex;
}

.flex-parent > * {
  flex-basis: 100%;
}

I thought this worked for the longest time. Actually, this was supposed to be my final solution after showing that flex: 1 doesn’t work. But while I was writing this article, I realized it also falls into the same problem, but it’s a lot less obvious. Enough so that I didn’t notice it with my eyes.

The elements are all trying to be 100% width, meaning they all want to match the width of the parent, which, again, is 600px (and in normal circumstances, is often much bigger, which further reduces the perceivable difference).

The thing is that 100% includes the padding in the computed values (because of * { box-size: border-box; }, which for the sake of this article, I’m assuming everyone is using). So, the outer divs end up with a content size of 600px, whereas the middle div ends up with a content size of 600 - 32 = 568px.

When the browser is working out how to evenly divide the space, it isn’t looking at how to evenly squish 1800px into a 600px space, but rather it’s looking at how to squish 1768px. Plus, as we saw earlier, flex items don’t shrink by the same amount, but at an equal pace! So, the element with padding shrinks slightly less in total than the others do.

This results in the .card having a final width of 214.483px while the others clock in at 192.75px. Again, this leads to unequal width values, though the difference is smaller than we saw with the flex: 1 solution.

Why CSS Grid is the better choice here

While all this is a little frustrating (and even a little confusing), it all happens for a good reason. If margins, padding, or borders changed sizes when flex gets involved, it would be a nightmare.

And maybe this means that CSS Grid might be a better solution to this really common design pattern.

I’ve long thought that flexbox was easier to pick up and start using than grid, but grid gives you more ultimate control in the long run, but that it’s a lot harder to figure out. I’ve changed my mind on that recently though, and I think not only does grid give us better control in this type of situation, but it’s actually more intuitive as well.

Normally, when we use grid, we explicitly declare our columns using grid-template-columns. We don’t have to do that though. We can make it behave a lot like flexbox does by using grid-auto-flow: column.

.grid-container {
  display: grid;
  grid-auto-flow: column;
}

Just like that, we end up with the same type of behavior as throwing display: flex on there. Like flex, the columns can potentially be unbalanced, but the advantage with grid is that the parent has total control over everything. So, rather than the content of the items having an impact like they would in flexbox, we only need one more line of code and we can solve the problem:

.grid-container {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}

I love that this is all on the parent selector, and that we don’t have to select the children to help get the layout that we are after!

The interesting thing here is how fr units work. They are literally called flex units in the spec, and they work just like flexbox does in dividing up space;. The big difference: they’re looking at the other tracks to determine how much space they have, paying no attention to the content inside those tracks.

That’s the real magic here. By declaring grid-auto-columns: 1fr, we are in essence saying, “by default, all my columns should have an equal width,” which is what we’ve been after from the start!

But what about at small screens?

What I love with this approach is we can keep it super simple:

.grid-container {
  display: grid;
  gap: 1em;
}

@media (min-width: 35em) {
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}

And just like that, it works perfectly. And by declaring display: grid from the start, we can include the gap to maintain equal spacing, whether the children are rows or columns.

I also find this to be a lot more intuitive than changing the flex-direction within a media query to get a similar result. As much as I love flexbox (and I really do still think it has great use cases), the fact that declaring flex-direction: column creates rows, and vice versa, is a little counter-intuitive at first glance.

And of course, if you prefer rolling without media queries, there is nothing stopping you from taking this to the next level with the help of auto-fit, which would be similar to setting something up with flex-wrap (though not exactly the same):

.grid-container {
  display: grid;
  gap: 1em;
  grid-template-columns: repeat(auto-fit, minmax(10em, 25em));
}

Making it work with flexbox

I realize that we can get around this with flexbox by nesting the element with the padding on it. We’ve done this since we started making layouts using floats, and Bootstrap really hammered home this type of design pattern.

<div class="container">
  <div class="row">
    <div class="col"> <div class="">... </div>
    <div class="col"> <div class="element-with-padding">...</div> </div>
    <div class="col"> ... </div>
  </div>
</div>

And there is nothing wrong with that. It works! But floats work too, and we’ve stopped using them for layouts as better solutions have been released in recent years.

One of the reasons that I love grid is because we can simplify our markup quite a bit. Just like we ditched floats for a better solution, I’d at least like to people to keep an open mind that maybe, just maybe, grid could be a better, and more intuitive solution for this type of design pattern.

Flexbox still has it’s place, of course

I still love flexbox, but I think its real power comes from times that we want to rely on the intrinsic width of the flex items, such as with navigations, or groups of elements, such as buttons or other items of varying width that you want to go next to one another.

In those situations, that behavior is an asset that makes things like even spacing between unequal items such a breeze! Flexbox is wonderful, and I have no plans to stop using.

In other situations though, when you find yourself fighting with how flexbox is trying to work, maybe you could turn to grid instead, even if it’s not a typical “2d” grid where you’re told you should be using it for.

People often tell me that they struggle to figure out grid because it’s too complicated, and while it can be, as we saw here, it doesn’t have to be.



Source link