惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
小众软件
小众软件
云风的 BLOG
云风的 BLOG
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
量子位
S
Secure Thoughts
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
Google DeepMind News
Google DeepMind News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
S
Security Affairs
Help Net Security
Help Net Security
博客园 - 三生石上(FineUI控件)
AWS News Blog
AWS News Blog
博客园 - 叶小钗
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V2EX - 技术
V2EX - 技术
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
H
Heimdal Security Blog
W
WeLiveSecurity
C
Check Point Blog

Ahmad Shadeed

Better fluid sizing with round() Use Cases for Field Sizing The Basics of Anchor Positioning Item Flow CSS Relative Colors Balancing Text In CSS Should masonry be part of CSS grid? CSS display contents CSS Grid Areas CSS Cap Unit An Interactive Guide to CSS Container Queries CSS :has() Interactive Guide CSS Nesting UX in DevTools CSS Nesting Future CSS: State Container Queries Rebuilding a comment component with modern CSS Conditional CSS with :has and :nth-last-child CSS Text balancing with text-wrap:balance CSS Masking Do we need CSS flex-wrap detection? My CSS Wishlist Conditional CSS CSS Style Queries Inside the mind of a frontend developer: Article layout Inside the mind of a frontend developer: Hero section CSS container queries are finally here The CSS behind Figma First Look At The CSS object-view-box Property Learn CSS Subgrid CSS :has Parent Selector Aligning Content In Different Wrappers Flexbox Dynamic Line Separator Hello, CSS Cascade Layers Building UI Components With SVG and CSS A Deep CSS Dive Into Radial And Conic Gradients Defensive CSS Building Real-life Components: Facebook Messenger Conditional Border Radius In CSS CSS Container Query Units Aligning a Button Label Vertically Comparing Design Mockups To Code Result Using HSL Colors In CSS Custom Scrollbars In CSS Let CSS Container Queries For Designers The State of CSS Cross-Browser Development Overflow Issues In CSS Inspect Element As A Way To Increase Your Curiosity Handling Text Over Images in CSS Digging Into CSS Logical Properties Clipping Scrollable Areas On The inline-start Side Understanding Clip Path in CSS The Art of Building Real-life Components Handling Short And Long Content In CSS CSS Scroll Snap A Deep Dive Into CSS Grid minmax() CSS Variables 101 Finding The Root Cause of a CSS Bug Learn CSS centering How to detect browser support for Flexbox Gap CSS Mistakes While On Autopilot Digging Into the Flex Property Understanding CSS Multiple Backgrounds Aligning Logo Images in CSS Grid for layout, Flexbox for components Colors in CSS Thinking About The In-between Design Cases min(), max(), and clamp() CSS Functions Image Techniques On The Web Everything About Auto in CSS Learn Box Alignment Let Learn CSS Positioning Intrinsic Sizing In CSS CSS Grid Template Areas In Action Hiding Elements On The Web Creating a Variable Color Font From Scratch Building a Football Ticket With CSS and SVG Blending Modes in CSS CSS Variables With Inline Styles Implementing Dark Mode For My Website Rebuilding Apple Music Header in HTML & CSS Accessible Checkbox Layout Flickering On Browser Resize Enhancing The Clickable Area Size Custom Underlines with SVG Part 3: The Process of Implementing A UI Design From Scratch Part 2: The Process of Implementing A UI Design From Scratch Building An Old Nav Design CSS Flexbox: 5 Real World Use Cases I Used CSS Inline Flex For The First Time The Process of Implementing A UI Design From Scratch Common CSS Issues For Front-End Projects Handling Long and Unexpected Content in CSS How to Build Web Form Layouts With CSS Grid Grid Layout Ah-ha Moment Enhancing Our Components with CSS :empty Building Resizeable Components with Relative CSS Units CSS Writing Mode The Journey of Learning Front End Web Development on a Daily Basis
Building A Stepper Component
Ahmad Shadeed · 2021-08-31 · via Ahmad Shadeed

The Layout Maestro

I spent years teaching CSS layout on this blog. I put everything I know into The Layout Maestro course: 70+ lessons and 150+ interactive examples that teach you how to think CSS layouts, not just memorize syntax.

Get the course

The other day, I got a question about how to build a responsive stepper component and how to take care of the separator lines. The demo I got from the developer was a bit complex and have lots of unnecessary CSS stuff. As a result, I got the idea of writing about it.

In this article, I will explore different designs for a stepping component and see the best way to implement each one in HTML and CSS. Are you ready? Let’s dive in.

Introduction

A stepper component can be used to make it easy to navigate through a long web page by dividing related elements and grouping them together.

When I started learning CSS, this was a challenging thing for me, and I understand why some newcomers might feel the same too. In the next few sections, I will take case-by-case examples and try to simplify and explain how to solve each one.

Horizontal Stepper: Example 1

For this example, we have a horizontal stepper with a line between the items. Here are the expectations for the stepper:

  • Responsive
  • Easy to be resized
  • Magic numbers aren’t allowed
  • Work with both light and dark modes
<ol class="c-stepper">
  <li class="c-stepper__item">
    <h3 class="c-stepper__title">Step 1</h3>
    <p class="c-stepper__desc">Some desc text</p>
  </li>
  <!-- Other steps -->
</ol>

In CSS, we need to use flexbox to layout the items horizontally.

.c-stepper {
  display: flex;
  flex-wrap: wrap;
}

.c-stepper__item {
  flex: 1;
  display: flex;
  flex-direction: column;
  text-align: center;
}

.c-stepper__item:before {
  --size: 3rem;
  content: "";
  position: relative;
  z-index: 1;
  display: block;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  margin: 1rem auto 0;
}

With that, we will have the following result.

As you see, the content is centered and each item is equal to other siblings thanks to flex: 1. Next, we need to explore how to add a line between them.

.c-stepper__item:not(:last-child):after {
  content: "";
  height: 2px;
  background-color: #e0e0e0;
}

You might wonder how the pseudo-element is taking the full width when it doesn’t actually have an explicit width? Well, it’s stretching to fill the full horizontal space because it’s a flex item.

First, we need to move it to the top. Since it’s a flex item, we can get the benefit of the order property.

.c-stepper__item:not(:last-child):after {
  content: "";
  height: 2px;
  background-color: #e0e0e0;
  order: -1;
}

Now, we want to position it properly but without using position: absolute, since it’s not needed.

.c-stepper__item:not(:last-child):after {
  content: "";
  position: relative;
  top: 1.5rem;
  left: 50%;
  height: 2px;
  background-color: #e0e0e0;
  order: -1;
}

Here is an explanation of the above CSS:

  • position: relative to control the line without removing it from the document flow.
  • The top value is equal to half of the circle’s height.
  • left: 50% to make the line starts from the center of the circle all the way to the center of the next item’s circle. This will allow us to color each line as per the progress if needed.

A version where there is spacing before and after the line

We can either add a stroke around each circle with the same background underneath the items, or we can work a bit more with a better solution that works great for both dark and light modes.

Before diving into the solution, I want to show you that the separator lines we have are actually hidden under each circle. I reduced the opacity for the number circles so you can see them.

This is due to using left: 50% for the line. Let’s explore how to give it an offset from the left and right.

Since the line has left: 50%, it will start from the center of its parent.

We use calc() to add the circle’s radius, this will make the separator line start from the end of the circle.

By adding the spacing we want (In this case it’s 8px) to the calc() function, we end up with a space on the left side of the separator line.

Finally, we need to create the spacing on the other side. The width is 100%, so we deduct the circle width along with the left and right spacing values.

To make it better, we can get the benefit of CSS variables so we can alter the size without manually editing the values.

.c-stepper {
    --size: 3rem;
    --spacing: 0.5rem;
}

.c-stepper__item:not(:last-child):after {
    width: calc(100% - var(--size) - calc(var(--spacing) * 2));
    left: calc(50% + calc(var(--size) / 2 + var(--spacing)));
}

Check out the below video for a demonstration.

Finally, implementing the separator like that can make the stepper works in a dark mode without adding a border to each circle to fake the effect.

See the Pen Horizontal Stepper: Example 1 by Ahmad Shadeed (@shadeed) on CodePen.

Horizontal Stepper: Example 2

In this example, the separator line comes directly after the step title. The difference thing here is that the separator length varies based on the step title length.

<ol class="c-stepper">
  <li class="c-stepper__item">
    <h3 class="c-stepper__title">Step 1</h3>
  </li>
  <!-- Other steps -->
</ol>

Similar to the previous example, we’ll use flexbox to lay out the steps horizontally. Notice that we want to apply flex: 1 only to the first and second steps.

.c-stepper {
  display: flex;
  flex-wrap: wrap;
}

.c-stepper__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.c-stepper__item:not(:last-child) {
  flex: 1;
}

.c-stepper__item:before {
  --size: 3rem;
  content: "";
  display: block;
  flex: 0 0 var(--size);
  height: var(--size);
  border-radius: 50%;
}

The separator line will be added as a pseudo-element. It will have flex: 1 so it can fill the remaining space.

.c-stepper__item:not(:last-child):after {
  content: "";
  flex: 1;
  height: 2px;
  background-color: #e0e0e0;
  margin-inline-end: 0.5rem;
}

By using the gap on the .c-stepper__item and the margin-inline-end logical property for the separator line, we can ensure that the component will work on both LTR and RTL documents.

See the Pen Horizontal Stepper: Example 2 by Ahmad Shadeed (@shadeed) on CodePen.

Vertical Stepper: Example 3

This is similar to the initial example, but the direction is vertical. I will use flexbox to layout the items.

<ol class="c-stepper">
  <li class="c-stepper__item">
    <div class="c-stepper__content">
      <h3 class="c-stepper__title">Step 2</h3>
      <p>Some desc text</p>
    </div>
  </li>
  <!-- Other steps -->
</ol>
.c-stepper__item {
  display: flex;
  gap: 1rem;
}

.c-stepper__item:before {
  --size: 3rem;
  content: "";
  position: relative;
  z-index: 1;
  flex: 0 0 var(--size);
  height: var(--size);
  border-radius: 50%;
  background-color: lightgrey;
}

Now to the interesting part, which is the separator line. The line should be a bit taller than the content. How we can achieve that while avoid using fixed height or margin?

We can add a large padding-bottom for that purpose.

.c-stepper__item {
  position: relative;
  display: flex;
  gap: 1rem;
  padding-bottom: 4rem;
}

.c-stepper__item:not(:last-child):after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  transform: translateX(1.5rem);
  width: 2px;
  background-color: #e0e0e0;
}

To create a space between the separator line and the numbers, we can apply the same formula used in the horizontal stepper example or simply use top and bottom properties. Thanks to monochromer for pointing out about using the bottom property.

.c-stepper {
  --size: 3rem;
  --spacing: 0.5rem;
}

.c-stepper__item:not(:last-child):after {
  top: calc(var(--size) + var(--spacing));
  transform: translateX(calc(var(--size) / 2));
  bottom: var(--spacing);
}

With that, we can have spacing between the line and the circle.

See the Pen Vertical Stepper: Example 1 by Ahmad Shadeed (@shadeed) on CodePen.

Vertical Stepper: Example 4

This might not look like a stepper, it’s more of a timeline. Regardless of the naming, implementing this one can be done using flexbox.

First, the HTML markup looks like the below. Notice that the elements are ordered as per their semantics and importance, not as per the design mockup.

<ol class="c-timeline">
  <li class="c-timeline__item">
    <div class="c-timeline__content">
      <h3 class="c-timeline__title">Paris</h3>
      <p class="c-timeline__desc">On time</p>
    </div>
    <time class="c-timeline__time">10:03</time>
  </li>
  <!-- Other items -->
</ol>

Here is the basic CSS.

.c-timeline__item {
  display: flex;
  gap: 1.5rem;
}

.c-timeline__content {
  order: 1;
  /* Reorder the content as per the design. */
  padding-bottom: 3rem;
}

With that, we will have the following result. Good start!

Next, we want to decide on where to include the separator line and the little circle. I thought that the .c-timeline__content is perfect for that job. We can use two pseudo-elements for both the separator and circle.

/* The separator line */
.c-timeline__item:not(:last-child) .c-timeline__content:before {
  content: "";
  position: absolute;
  right: 100%;
  top: 0;
  height: 100%;
  width: 2px;
  background-color: #d3d3d3;
}

/* The circle */
.c-timeline__content:after {
  content: "";
  position: absolute;
  left: -12px;
  top: 0;
  width: 20px;
  height: 20px;
  background-color: #fff;
  z-index: 1;
  border: 2px solid #d3d3d3;
  border-radius: 50%;
}

Next, we need to limit the width of the time element. Also, the timeline content should fill the rest of the available space, thus we will use flex: 1.

.c-timeline__time {
  flex: 0 0 100px;
}

.c-timeline__content {
  flex: 1;
}

The next step is to align the c-timeline__time content to the right, or the end in the logical CSS world.

.c-timeline__time {
  flex: 0 0 100px;
  text-align: end;
}

I want to highlight an important bit regarding the time of each item. In some cases, we don’t have control over the content an author might add a very long content either on purpose or by mistake.

By default, flex items won’t shrink below their minimum content size. If the .c-timeline__time element has a very long content, it will look like this even though it has flex: 0 0 100px.

To fix that, we need to force flexbox to shrink below their minimum content size by adding min-width: 0 to the element, and also overflow-wrap to break long words.

.c-timeline__time {
  flex: 0 0 100px;
  text-align: end;
  min-width: 0;
  overflow-wrap: break-word;
  padding-bottom: 1rem;
}

See the Pen Vertical Stepper: Example 2 by Ahmad Shadeed (@shadeed) on CodePen.

I hope you enjoyed the article. Thanks for reading!