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

推荐订阅源

大猫的无限游戏
大猫的无限游戏
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

CSS Articles by Temani Afif

Get Ready For the Powerful CSS border-shape Property! | CSS-Tricks Let’s Play With Gap Decorations! How to Control Infinite CSS Animations (Part 2 of 2) How to Control Infinite CSS Animations (Part 1 of 2) Two Circles, One Arrow, and Anchor Positioning Making a Responsive Pyramidal Grid With Modern CSS | CSS-Tricks How to Create a CSS-only Elastic Text Effect Making Complex CSS Shapes Using shape() | CSS-Tricks Responsive Hexagon Grid Using Modern CSS | CSS-Tricks Responsive List of Avatars Using Modern CSS (Part 2) | CSS-Tricks Perfectly Pointed Tooltips: To The Corners Perfectly Pointed Tooltips: All Four Sides Perfectly Pointed Tooltips: A Foundation Sequential linear() Animation With N Elements | CSS-Tricks Infinite Marquee Animation using Modern CSS Better CSS Shapes Using shape() — Part 4: Close and Move | CSS-Tricks Drawing CSS Shapes using corner-shape Better CSS Shapes Using shape() — Part 3: Curves | CSS-Tricks Better CSS Shapes Using shape() — Part 2: More on Arcs | CSS-Tricks Better CSS Shapes Using shape() — Part 1: Lines and Arcs | CSS-Tricks Creating Blob Shapes using clip-path: shape() Creating Flower Shapes using clip-path: shape() Custom progress element using the attr() function A CSS-Only Star Rating Component and More! (Part 2) | CSS-Tricks A CSS-Only Star Rating Component and More! (Part 1) | CSS-Tricks How to Create Wavy Boxes Using CSS Full-Bleed Layout with Modern CSS Fancy Menu Navigation Using Anchor Positioning | CSS-Tricks How to Create a Zig-Zag Box Using CSS How to Create Zig-Zag CSS Loaders Using One Element Custom Progress Element Using Anchor Positioning & Scroll-Driven Animations How to Create Filling CSS Loaders Using One Element How to Create Curved-Edge and Rounded-Edge Shapes Using CSS CSS Tricks That Use Only One Gradient | CSS-Tricks How to create Shapes with Inner Curves using CSS Mask Custom Range Slider Using Anchor Positioning & Scroll-Driven Animations How to Get the Width/Height of Any Element in Only CSS How Keyboard Navigation Works in a CSS Game How To Create Cut-Out Shapes using The clip-path property The Modern Guide For Making CSS Shapes — Smashing Magazine css-shape.com: The Ultimate Collection of CSS-only Shapes ⚡️ Sliding 3D Image Frames In CSS — Smashing Magazine CSS Tricks To Master The clip-path Property Creating Wavy Circles with Fancy Animations in CSS Modern CSS Tooltips And Speech Bubbles (Part 2) — Smashing Magazine Do you need a Tooltip or a Speech Bubble? I have created 100 using CSS 😲 Modern CSS Tooltips And Speech Bubbles (Part 1) — Smashing Magazine Creating Flower Shapes using CSS Mask & Trigonometric Functions CSS Shapes: The Triangle The Complex But Awesome CSS border-image Property — Smashing Magazine CSS Tricks to add 3D Effects to your Text CSS Responsive Multi-Line Ribbon Shapes (Part 2) — Smashing Magazine CSS Responsive Multi-Line Ribbon Shapes (Part 1) — Smashing Magazine I have made 100+ CSS-only Ribbon Shapes | The Perfect Collection 🎀 How to create a CSS-only infinite scroll animation Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 2) — Smashing Magazine Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1) — Smashing Magazine How to Create CSS Ribbon Shapes with a Single Element CSS Shapes: The Ribbon Revealing Images With CSS Mask Animations — Smashing Magazine css-loaders.com: The Biggest Collection of Loading Animations (more than 500 🤯) How To Define An Array Of Colors With CSS — Smashing Magazine CSS effects on images II Shines, Perspective, And Rotations: Fancy CSS 3D Effects For Images — Smashing Magazine What's your Lucky CSS Pattern? Check It Now! How to Add a CSS Reveal Animation to Your Images Modern Layouts using CSS Grid I created 100+ unique CSS patterns | The best collection 🤩 How to Create a Custom Range Slider Using CSS CSS Shapes: The Heart CSS Shapes: Polygon & Starburst A Text Reveal Animation using CSS CSS Tip: learn CSS the easy way! How to build a CSS-only accordion How to create Breadcrumb Navigation with CSS Different Ways to Get CSS Gradient Shadows | CSS-Tricks A CSS-only responsive Stepper component A Fancy Hover Effect For Your Avatar | CSS-Tricks How to make a zoom effect using CSS Creating a Custom Cursor using CSS CSS Infinite 3D Sliders | CSS-Tricks CSS Infinite Slider Flipping Through Polaroid Images | CSS-Tricks How to create a responsive sidebar menu using CSS CSS Infinite and Circular Rotating Image Slider | CSS-Tricks Making Static Noise From a Weird CSS Gradient Bug | CSS-Tricks CSS Grid and Custom Shapes, Part 3 | CSS-Tricks Fancy Image Decorations: Outlines and Complex Animations | CSS-Tricks Fancy Image Decorations: Masks and Advanced Hover Effects | CSS-Tricks Fancy Image Decorations: Single Element Magic | CSS-Tricks How to create an infinite image slider using CSS How to Create Wavy Shapes & Patterns in CSS | CSS-Tricks How I Made a Pure CSS Puzzle Game | CSS-Tricks How to create a Tooltip/Speech Bubble using CSS CSS Grid and Custom Shapes, Part 2 | CSS-Tricks CSS Grid and Custom Shapes, Part 1 | CSS-Tricks Zooming Images in a Grid Layout | CSS-Tricks How to create a CSS-only loader with one element Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers | CSS-Tricks How to create background pattern using CSS & conic-gradient Single Element Loaders: The Bars | CSS-Tricks
Responsive List of Avatars Using Modern CSS (Part 1) | CSS-Tricks
Temani Afif · 2025-12-15 · via CSS Articles by Temani Afif

A list of rounded images that slightly overlap each other is a classic web design pattern.

Two rows of circular avatar images. The images overlap with one another. The first row has eight images; the second row has six images.

You are for sure wondering what the novelty we are bringing here is, right? It has been done countless times.

You are right. The main idea is not complex, but the new thing is the responsive part. We will see how to dynamically adjust the overlap between the images so they can fit inside their container. And we will make some cool animations for it along the way!

Here is a demo of what we are creating. You can resize the window and hover the images to see how they behave. And yes, the gap between the images is transparent!

The following demo is currently limited to Chrome and Edge, but will work in other browsers as the sibling-index() and sibling-count() functions gain broader support. You can track Firefox support in Ticket #1953973 and WebKit’s position in Issue #471.

We’ll get even deeper into things in a second article. For now, let’s re-create this demo!

Responsive List of Avatars Using Modern CSS

  1. Horizontal Lists (You are here!)
  2. Circular Lists

The initial setup

We start with the HTML, which is a set of image elements in a parent container:

<div class="container">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <!-- etc. -->
</div>

Declaring flexbox on the container is all we need to line the images up in a single row:

.container {
  display: flex;
}

We can make the images circles with border-radius and squish them close together with a little negative margin:

.container img {
  border-radius: 50%;
  margin-right: -20px;
}
.container img:last-child {
  margin: 0;
}

Nothing fancy so far. I am using an arbitrary value for the margin to create an overlap:

The cut-out effect

We’ll need the mask property to cut the images and create the transparent gap between them. Making the gap transparent is very important here as it makes the component look better — but it’s also more challenging to code since the cut-out needs to consider the next (or previous) element in a way that prevents one image from obscuring the other.

mask: radial-gradient(50% 50% at calc(150% - 20px), #0000 100%, #000);

This mask creates a circular shape with the same dimensions as one of the images — a radius equal to 50% in both directions — and its center point will be the midpoint of the next element (calc(150% - 20px)). Without the overlap, the center of the next element is at 50% (center of the actual element) + 100%. But due to the overlap, the next image is closer, so we reduce the distance by 20px, which is the value used by the margin. This cut the image from the right side.

If we want the cut-out on the left side, we move the circle in the other direction: 50% - 100% + 20px.

Drag the slider in the next demo for a visualization of how this works in both directions. I am removing the border-radius from the center image to illustrate the circular shape.

We apply this to all the images, and we are good to go. Notice that I am using a couple of CSS variables to control the image size and gap between the images.

.container {
  --s: 120px; /* image size*/
  --g: 10px;  /* the gap */

  display: flex;
}
.container img {
  width: var(--s);
  border-radius: 50%;
  margin-right: -20px;
  /* Cut-out on the right side */
  mask: radial-gradient(50% 50% at calc(150% - 20px),
        #0000 calc(100% + var(--g)),#000);
}
/* Cut-out on the left side */
.container.reverse img {
  mask: radial-gradient(50% 50% at calc(-50% + 20px),
        #0000 calc(100% + var(--g)),#000);
}
.container img:last-child {
  margin: 0;
}
.container.reverse img:first-child,
.container:not(.reverse) img:last-child {
  mask: none;
}

Pay additional attention to the .reverse class. It switches the direction of the cut-out from right (the default) to left instead.

What we have is already good. It works fine and you can use it, but it could be more interactive. The overlap look nice but wouldn’t be better if we could enlarge it on smaller screens to help conserve space, or perhaps even remove it altogether on larger screens where there’s plenty of room to show the full images?

Let’s make this more interactive and responsive.

The responsive part

Let’s imagine the total size of the images exceeds the size of the .container. That results in an overflow, so we need to assign a negative margin to each image to absorb that space and ensure all the images fit in the container.

It looks like we need some JavaScript to calculate the excess of space and then divide it by the number of images to get the margin value. And probably put this logic inside a resize listener in case the container change its size.

I am kidding, of course! We can solve this using modern CSS that is small and maintainable.

If we were to express what we need mathematically, the formula of the margin should be equal to:

margin-right: (size_of_container - N x size_of_image)/(N - 1);

…where N is the number of images, and we are dividing by N - 1 because the last image doesn’t need a margin. We already have a variable for the image size (--s) and we know that the width of the container is 100%:

margin-right: (100% - N x var(--s))/(N - 1);

What need to solve for is N, the number of images. We could use a rigid magic number here, say 10, but what if we want fewer or more images in the container? We’d have to update the CSS each time. We want a solution that adapts to whatever number of images we throw at it.

That’s where the new sibling-count() function comes in real handy. It’s going to be the best approach moving forward since it automatically calculates the number of child elements within the container. So, if there are 10 images in the .container, the sibling-count() is 10.

margin-right: calc((100% - sibling-count() * var(--s))/(sibling-count() - 1));

Resize the container in the demo below and see how the images behave. Again, sibling-count() support is limited at the moment, but you can check it out in the latest Chrome or Safari Technology Preview.

It’s quite good! The images automatically adjust to fit in the container, but we can still improve this slightly. When the container size is large enough, the calculated value of margin will be positive and we get big spaces between the images. You probably want to keep that behavior, but in my case, I want the image to remain as close as possible.

To do this, we can set a maximum boundary to the margin value and make sure it doesn’t get any bigger than 0:

margin-right: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), 0px);

We can also re-use the the gap variable (--g) to maintain a space between items:

margin-right: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), var(--g));

If you’re wondering why I am using the min() function to define a max boundary, read this for a detailed explanation. In short: you’re effectively setting a maximum with min() and a minimum with max().

The responsive part is perfect now!!

What we’re missing is the cut-out effect we made with mask. For that, we can re-use the same margin value inside the mask.

Two images on separate rows on opposite ends of each row. The top row image is aligned right and the bottom row image is aligned left.

Oops, the images disappeared! We have the same code as the previous section, but instead of the arbitrary 20px value, we used the last formula.

.container img {
  --_m: min((100% - sibling-count() * var(--s))/(sibling-count() - 1), var(--g));

  margin-right: var(--_m);
  mask: radial-gradient(50% 50% at calc(150% + var(--_m)),
        #0000 calc(100% + var(--g)),#000);
}

Can you guess what the issue is? Think a moment about it because it’s something you may face in other situations.

It’s related to percentages. With margin, the percentage refers to the container size, but inside mask, it considers another reference, which means the values aren’t equal. We need to retrieve the container size differently, using container query units instead.

First, we register the .container as a CSS “container”:

.container {
  container-type: inline-size;
}

Then, we can say that the container’s width is 100cqi (or 100cqw) instead of 100%, which fixes the layout issue:

Tada! The position and the mask adjust perfectly when the container is resized.

The animation part

The idea of the animation is to fully reveal an image on hover if there is an overlap between items, like this:

How do we remove the overlap? All we do is update the variable (--_m) we defined earlier to zero when an image is hovered:

.container img:hover {
  --_m: 0px;
}

That takes out the margin and removes the cut-out effect as well. We actually might want a little bit of margin between images, so let’s make --_m equal to the gap (--g) instead:

.container img:hover {
  --_m: var(--g);
}

Not bad! But we can do better. Notice how pushing one image away from another causes an image at the end to overflow the container. The bottom list (the row with the cut-out on the left) is not as good as the top list because the mask is a bit off on hover.

Let’s first fix the mask before tackling the overflow.

The issue is that I am using margin-right for the spacing while the cut-out effect is on the left. It works fine when we don’t need any animation but as you can see, it’s not quite good in the last demo. We need switch to a margin-left instead on the bottom row. In other words, we use margin-right when the cut-out is on the right, and margin-left when the cut-out is on the left.

.container:not(.reverse) img {
  mask: radial-gradient(50% 50% at calc(150% + var(--_m)),
        #0000 calc(100% + var(--g)), #000);
  margin-right: var(--_m);
}
.container.reverse img {
  mask: radial-gradient(50% 50% at calc(-50% - var(--_m)),
        #0000 calc(100% + var(--g)), #000);
  margin-left: var(--_m);
}
.container:not(.reverse) img:last-child,
.container.reverse img:first-child {
  mask: none;
  margin: 0;
}

Great, now the cut-out effect is much better and respects both the left and right sides:

Let’s fix the overflow now. Remember the previous formula where we split the excess of space across N - 1 elements?

(size_of_container - N x size_of_image)/(N - 1)

Now we need to exclude one more element in the equation, which means we replace the N with N - 1 and replace the N - 1 with N - 2:

(size_of_container - (N - 1) x size_of_image)/(N - 2)

However, that extra excluded element still takes up space inside the container. We need to account for its size and subtract it from the container size:

((size_of_container - (size_of_image + gap)) - (N - 1) x size_of_image)/(N - 2)

I am considering the size plus a gap because a margin that is equal to the gap is set on a hovered image, which is additional spacing we need to remove.

We simplify a bit:

(size_of_container - gap -  N x size_of_image)/(N - 2)

We know how to translate this into CSS, but where should we apply it?

It should be applied on all the images when one image is hovered (except the hovered image). This is a great opportunity to write a fancy selector using :has() and :not()!

/* Select images that are not hovered when the container contains a hovered image */
.container:has(:hover) img:not(:hover) {
  /**/
}

And we plug the formula into that:

.container:has(:hover) img:not(:hover) {
  --_m: min((100cqw - var(--g) - sibling-count()*var(--s))/(sibling-count() - 2), var(--g));
}

Check that out — no more overflow on hover in both directions! All we are missing now is the actual animation that smoothly transitions the spacing rather than snapping things into place. All we need is to add a little transition on the --_m variable:

transition: --_m .3s linear;

If we do that, however, the transition doesn’t happen. It’s because CSS doesn’t recognize the calculated value as a proper CSS length unit. For that, we need to formally register --_m as a custom property using the @property at-rule:

@property --_m {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px
}

There we go:

Cool, right? Having a smooth change for the mask and the position is quite satisfying. We still need to fix a small edge case. The last element in the top list and the first one in the bottom list don’t have any margin, and they are always fully visible, so we need to exclude them from the effect.

When hovering them, nothing should happen, so we can adjust the previous selector like below:

.container:not(.reverse):has(:not(:last-child):hover) img:not(:hover),
.container.reverse:has(:not(:first-child):hover) img:not(:hover) {
  --_m: min((100cqw - var(--g) - sibling-count()*var(--s))/(sibling-count() - 2),var(--g));
}

Instead of simply checking if the container has a hovered element, we restrict the selection to the elements that are not :last-child for the first list and not :first-child for the second list. Another cool selector using modern CSS!

Here is the final demo with all the adjustments made:

Conclusion

I hope you enjoyed this little exploration of some modern CSS features. We re-created a classic component, but the real goal was to learn a few CSS tricks and rely on new features that you will definitely need in other situations.

In the next article, we’ll add more complexity and cover even more modern CSS for an even more satisfying pattern! Stay tuned.

Responsive List of Avatars Using Modern CSS

  1. Horizontal Lists (You are here!)
  2. Circular Lists