Jump to content

Blogger

Blog Bot
  • Joined

  • Last visited

    Never
  1. by: Temani Afif Wed, 17 Dec 2025 15:21:34 +0000 Ready for the second part? If you recall, last time we worked on a responsive list of overlapping avatar images featuring a cut-out between them. We are still creating a responsive list of avatars, but this time it will be a circular list. This design is less common than the horizontal list, but it’s still a good exercise to explore new CSS tricks. Let’s start with a demo. You can resize it and see how the images behave, and also hover them to get a cool reveal effect. 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. CodePen Embed Fallback We will rely on the same HTML structure and CSS base as the example we covered in Part 1: a list of images inside a container with mask-ed cutouts. This time, however, the positions will be different. Responsive List of Avatars Using Modern CSS Horizontal Lists Circular Lists (You are here!) Placing Images Around a Circle There are several techniques for placing images around a circle. I will start with my favorite one, which is less known but uses a simple code that relies on the CSS offset property. .container { display: grid; } .container img { grid-area: 1/1; offset: circle(180px) calc(100%*sibling-index()/sibling-count()) 0deg; } CodePen Embed Fallback The code doesn’t look super intuitive, but its logic is fairly straightforward. The offset property is a shorthand, so let’s write it the longhand way to see how breaks down: offset-path: circle(180px); offset-distance: calc(100%*sibling-index()/sibling-count()); offset-rotate: 0deg; We define a path to be a circle with a radius of 180px. All the images will “follow” that path, but will initially be on top of each other. We need to adjust their distance to change their position along the path (i.e., the circle). That’s where offset-distance comes into play, which we combine with the sibling-index() and sibling-count() functions to create code that works with any number of elements instead of working with exact numbers. For six elements, the values will be as follows: 100% x 1/6 = 16.67% 100% x 2/6 = 33.33% 100% x 3/6 = 50% 100% x 4/6 = 66,67% 100% x 5/6 = 83.33% 100% x 6/6 = 100% This will place the elements evenly around the circle. To this, we add a rotation equal to 0deg using offset-rotate to keep the elements straight so they don’t rotate as they follow the circular path. From there, all we have to do is update the circle’s radius with the value we want. That’s my preferred approach, but there is a second one that uses the transform property to combine two rotations with a translation: .container { display: grid; } .container img { grid-area: 1/1; --_i: calc(1turn*sibling-index()/sibling-count()); transform: rotate(calc(-1*var(--_i))) translate(180px) rotate(var(--_i)); } The translation contains the circle radius value and the rotations use generic code that relies on the sibling-* functions the same way we did with offset-distance. CodePen Embed Fallback Even though I prefer the first approach, I will rely on the second one because it allows me to reuse the rotation angle in more places. The Responsive Part Similar to the horizontal responsive list from the last article, I will rely on container query units to define the radius of the circle and make the component responsive. .container { --s: 120px; /* image size */ aspect-ratio: 1; container-type: inline-size; } .container img { width: var(--s); --_r: calc(50cqw - var(--s)/2); --_i: calc(1turn*sibling-index()/sibling-count()); transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i)); } Resize the container in the demo below and see how the images behave: CodePen Embed Fallback It’s responsive, but when the container gets bigger, the images are too spread out, and I don’t like that. It would be good to keep them as close as possible. In other words, we consider the smallest circle that contains all the images without overlap. Remember what we did in the first part: we added a maximum boundary to the margin for a similar reason. We will do the same thing here: --_r: min(50cqw - var(--s)/2, R); I know you don’t want a boring geometry lesson, so I will skip it and give you the value of R: S/(2 x sin(.5turn/N)) Written in CSS: --_r: min(50cqw - var(--s)/2,var(--s)/(2*sin(.5turn/sibling-count()))); Now, when you make the container bigger, the images will stay close to each other, which is perfect: CodePen Embed Fallback Let’s introduce another variable for the gap between images (--g) and update the formula slightly to keep a small gap between the images. .container { --s: 120px; /* image size */ --g: 10px; /* the gap */ aspect-ratio: 1; container-type: inline-size; } .container img { width: var(--s); --_r: min(50cqw - var(--s)/2,(var(--s) + var(--g))/(2*sin(.5turn/sibling-count()))); --_i: calc(1turn*sibling-index()/sibling-count()); transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i)); } CodePen Embed Fallback The Cut-Out Effect For this part, we will be using the same mask that we used in the last article: mask: radial-gradient(50% 50% at X Y, #0000 calc(100% + var(--g)), #000); With the horizontal list, the values of X and Y were quite simple. We didn’t have to define Y since its default value did the job, and the X value was either 150% + M or -50% - M, with M being the margin that controls the overlap. Seen differently, X and Y are the coordinates of the center point of the next or previous image in the list. That’s still the case this time around, but the value is trickier to calculate: The idea is to start from the center of the current image (50% 50%) and move to the center of the next image (X and Y). I will first follow segment A to reach the center of the big circle and then follow segment B to reach the center of the next image. This is the formula: X = 50% - Ax + Bx Y = 50% - Ay + By Ax and Ay are the projections of the segment A on the X-axis and the Y-axis. We can use trigonometric functions to get the values. Ax = r x sin(i); Ay = r x cos(i); The r represents the circle’s radius defined by the CSS variable --_r, and i represents the angle of rotation defined by the CSS variable --_i. Same logic with the B segment: Bx = r x sin(j); By = r x cos(j); The j is similar to i, but for the next image in the sequence, meaning we increment the index by 1. That gives us the following CSS calculations for each variable: --_i: calc(1turn*sibling-index()/sibling-count()); --_j: calc(1turn*(sibling-index() + 1)/sibling-count()); And the final code with the mask: .container { --s: 120px; /* image size */ --g: 14px; /* the gap */ aspect-ratio: 1; container-type: inline-size; } .container img { width: var(--s); --_r: min(50cqw - var(--s)/2,(var(--s) + var(--g))/(2*sin(.5turn/sibling-count()))); --_i: calc(1turn*sibling-index()/sibling-count()); --_j: calc(1turn*(sibling-index() + 1)/sibling-count()); transform: rotate(calc(-1*var(--_i))) translate(var(--_r)) rotate(var(--_i)); mask: radial-gradient(50% 50% at calc(50% + var(--_r)*(cos(var(--_j)) - cos(var(--_i)))) calc(50% + var(--_r)*(sin(var(--_i)) - sin(var(--_j)))), #0000 calc(100% + var(--g)), #000); } CodePen Embed Fallback Cool, right? You might notice two different implementations for the cut-out. The formula I used previously considered the next image, but if we consider the previous image instead, the cut-out goes in another direction. So, rather than incrementing the index, we decrement instead and assign it to a .reverse class that we can use when we want the cut-out to go in the opposite direction: .container img { --_j: calc(1turn*(sibling-index() + 1)/sibling-count()); } .container.reverse img { --_j: calc(1turn*(sibling-index() - 1)/sibling-count()); } The Animation Part Similar to what we did in the last article, the goal of this animation is to remove the overlap when an image is hovered to fully reveal it. In the horizontal list, we simply set its margin property to 0, and we adjust the margin of the other images to prevent overflow. This time, the logic is different. We will rotate all of the images except the hovered one until the hovered image is fully visible. The direction of the rotation will depend on the cut-out direction, of course. To rotate the image, we need to update the --_i variable, which is used as an argument for the rotate function. Let’s start with an arbitrary value for the rotation, say 20deg. .container img { --_i: calc(1turn*sibling-index()/sibling-count()); } .container:has(:hover) img { --_i: calc(1turn*sibling-index()/sibling-count() + 20deg); } .container.reverse:has(:hover) img { --_i: calc(1turn*sibling-index()/sibling-count() - 20deg); } Now, when an image is hovered, all of images rotate by 20deg. Try it out in the following demo. CodePen Embed Fallback Hmm, the images do indeed rotate, but the mask is not following along. Don’t forget that the mask considers the position of the next or previous image defined by --_j and the next/previous image is rotating — hence we need to also update the --_j variable when the hover happens. .container img { --_i: calc(1turn*sibling-index()/sibling-count()); --_j: calc(1turn*(sibling-index() + 1)/sibling-count()); } .container.reverse img { --_j: calc(1turn*(sibling-index() - 1)/sibling-count()); } .container:has(:hover) img { --_i: calc(1turn*sibling-index()/sibling-count() + 20deg); --_j: calc(1turn*(sibling-index() + 1)/sibling-count() + 20deg); } .container.reverse:has(:hover) img { --_i: calc(1turn*sibling-index()/sibling-count() - 20deg); --_j: calc(1turn*(sibling-index() - 1)/sibling-count() - 20deg); } That’s a lot of redundant code. Let’s optimize it a little by defining additional variables: .container img { --_a: 20deg; --_i: calc(1turn*sibling-index()/sibling-count() + var(--_ii, 0deg)); --_j: calc(1turn*(sibling-index() + 1)/sibling-count() + var(--_jj, 0deg)); } .container.reverse img { --_i: calc(1turn*sibling-index()/sibling-count() - var(--_ii, 0deg)); --_j: calc(1turn*(sibling-index() - 1)/sibling-count() - var(--_jj, 0deg)); } .container:has(:hover) img { --_ii: var(--_a); --_jj: var(--_a); } Now the angle (--_a) is defined in one place, and I consider two intermediate variables to add an offset to the --_i and --_j variables. CodePen Embed Fallback The rotation of all the images is now perfect. Let’s disable the rotation of the hovered image: .container img:hover { --_ii: 0deg; --_jj: 0deg; } CodePen Embed Fallback Oops, the mask is off again! Do you see the issue? We want to stop the hovered image from rotating while allowing the rest of the images to rotate. Therefore, the --_j variable of the hovered image needs to update since it’s linked to the next or previous image. So we should remove --_jj: 0deg and keep only --_ii: 0deg. .container img:hover { --_ii: 0deg; } CodePen Embed Fallback That’s a little better. We fixed the cut-out effect on the hovered image, but the overall effect is still not perfect. Let’s not forget that the hovered image is either the next or previous image of another image, and since it’s not rotating, another --_j variable needs to remain unchanged. For the first list, it’s the variable of the previous image that should remain unchanged. For the second list, it’s the variable of the next image: /* select previous element of hovered */ .container:not(.reverse) img:has(+ :hover), /* select next element of hovered */ .container.reverse img:hover + * { --_jj: 0deg; } In case you are wondering how I knew to do this, well, I tried both ways and I picked the one that worked. It was either the code above or this: .container:not(.reverse) img:hover + *, .container.reverse img:has(+ :hover) { --_jj: 0deg; } CodePen Embed Fallback We are getting closer! All the images behave correctly except for one in each list. Try hovering all of them to identify the culprit. Can you figure out what we are missing? Think a moment about it. Our list is circular, but the HTML code is not, so even if the first and last images are visually placed next to each other, in the code, they are not. We cannot link both of them using the adjacent sibling selector (+). We need two more selectors to cover those edge cases: .container.reverse:has(:last-child:hover) img:first-child, .container:not(.reverse):has(:first-child:hover) img:last-child { --_jj: 0deg; } CodePen Embed Fallback Oof! We have fixed all the issues, and now our hover effect is great, but it’s still not perfect. Now, instead of using an arbitrary value for the rotation, we need to be accurate. We have to find the smallest value that removes the overlap while keeping the images as close as possible. We can get the value with some trigonometry. I’ll skip the geometry lesson again (we have enough headaches as it is!) and give you the value: --_a: calc(2*asin((var(--s) + var(--g))/(2*var(--_r))) - 1turn/sibling-count()); CodePen Embed Fallback Now we can say everything is perfect! Conclusion This one was a bit tough, right? Don’t worry if you got a bit lost with all the complex formulas. They are very specific to this example, so even if you have already forget about them, that’s fine. The goal was to explore some modern features and a few CSS tricks such as offset, mask, sibling-* functions, container query units, min()/max(), and more! Responsive List of Avatars Using Modern CSS Horizontal Lists Circular Lists (You are here!) Responsive List of Avatars Using Modern CSS (Part 2) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
  2. by: Sourav Rudra Wed, 17 Dec 2025 17:56:09 +0530 Rust-based editor combines fast performance with GUI-style shortcuts that just work.
  3. by: Abhishek Prakash Wed, 17 Dec 2025 15:11:01 +0530 Here are the operating system choices when you want to use Raspberry Pi in a desktop setup.
  4. by: Daniel Schwarz Tue, 16 Dec 2025 17:03:46 +0000 Welcome to the first ever What’s !important, a roundup of the best CSS and web development news from the last two weeks. If you’re pressed for time (who isn’t?) or you just can’t stand missing out on anything CSS-related, this is the place to recap: the best CSS/web dev articles from around the web, interesting announcements and quick-read links that we shared, the best of what Chrome, Firefox, and Safari have recently shipped, and more, probably. Let’s dig in! HTMHell Advent Calendar 2025 Manuel Matuzović has created this pretty hellish (all in good fun) HTMHell Advent Calendar 2025 made up of 24 HTML-focused articles — basically, there’s one every day until Christmas Day. Obviously, we’re already 16 fantastic articles in, so you’re in for a real treat with 8 more articles to come. The highlight for me so far is the article on Invoker Commands, which as of this week are supported in all web browsers, so it’s a double win! WebKit flags ::target-text WebKit did a little write-up on ::target-text. And I’m glad they did because I had never heard of it, even though Sunkanmi has an excellent write-up on it here at CSS-Tricks as well. Target text is the text that search engines sometimes highlight when taking you to a web page, but we can actually highlight text ourselves when sending links to other people. ::target-text selects this text so that we can style it, and it’s supported by all web browsers — who knew? Stop using JavaScript to solve CSS problems Chizaram Ken walks us through the latest CSS tricks that we can perform to reduce our reliance on JavaScript, and why we’d want to do that. With CSS growing faster than it’s ever grown before, I think we’ll be seeing a lot more of these articles. We have granular control over hyphenation @eva.town over at Bluesky said that it’d be nice to be able to set hyphenation rules preventing words like “lighter” from hyphenating as “light-er” — but @knowler.dev pointed out that we can, sort of. The hyphenate-limit-chars property (still waiting for Safari support) enables us to specify the preferred minimum number of characters on either side of the hyphen, which is good enough, I suppose? Which color format should we be using? @nadyavoynich.com asked this question, but honestly I still don’t know. Some very good points were made about human-readable formats and color depth, and I’ve kind of settled on oklch(). But some UI design tools don’t even support HSL, so? CSS Wrapped 2025 and State of HTML 2025 It’s that time of the year. We’ve had Spotify’s Wrapped, PlayStation’s Wrap-Up, Duolingo’s Year-in-Review — now it’s time for CSS Wrapped 2025, all of the latest and greatest things that Chrome did with CSS (and HTML) this year, and State of HTML 2025, which is about HTML in general. Cool conversations, fun demos, and new browser features In case you missed any of our Quick Hits, here are the latest CSS/web dev announcements and links that we’ve shared: This Bluesky post by @heyo53 got us thinking about whether it’s okay to style scrollbars (since people rarely interact with them physically these days, I personally just use scrollbar-width: none) Bramus put together a demo visualizing the different values for position-area and explained how to handle inside corners Finally, our top picks for the best CSS and HTML features shipped by web browsers in the last couple of weeks: Chrome 143 Anchored container queries, which I briefly talked about in an article on container queries Firefox 146 contrast-color(), which ensures optimal color contrast between two colors (or at least it will once it leverages CSS Color 6) text-decoration-inset, which enables control over the position and size of text decorations @scope, which makes defining new CSS scoping contexts a baseline feature (this, frankly, is the highlight of my year) @custom-media, which is basically custom properties but for media queries, is being trialed Safari 26.2 random() generates a random number in CSS, but unfortunately it’s not supported anywhere else yet. sibling-index() and sibling-count() get the position and number of siblings respectively (we’re only waiting for Firefox support now). field-sizing enables adaptive sizing for input fields (again, hurry up Firefox). command and commandfor, now baseline and my pick for best HTML feature of the year, are HTML attributes for invoking certain JavaScript events declaratively. hidden=until-found, also baseline now, for hiding elements until found by find-in-page. Also, we’ve never been able to set cursor on pseudo-elements such as ::before and ::after, but thankfully we can now! Remember, you can catch us again in two weeks — happy holidays! What’s !important #1: Advent Calendars, CSS Wrapped, Web Platform Updates, and More originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
  5. by: Sourav Rudra Tue, 16 Dec 2025 16:40:07 +0530 Proposed legislation would make it illegal to use VPNs for accessing geo-blocked content.
  6. by: Neville Ondara Tue, 16 Dec 2025 15:27:52 +0530 Classic Linux tools are always there, but eBPF is the natural upgrade as it looks directly in the kernel. Once you start using these tools, it’s hard to imagine managing Linux systems without them.
  7. by: Chris Coyier Mon, 15 Dec 2025 18:00:51 +0000 This is an appreciation post for the Cloud Four Blog. It’s one of those must-subscribes. I find myself adding almost every post they put out to my archive of posts to dig into. So since I’m overflowing with them a bit, and there is a hot-off-the-presses one, I figured I’d share that and a bunch of favorites. In Responsive Letter Spacing, Tyler Sticka covers a scenario where you want to tighten up letter-spacing, but be smart about it. At big font sizes, some tightening looks good (I put it on my own starter) but at smaller font sizes, it harms readability. Using em is a good start as it scales with the font size automatically, but then it’s better to reduce it as it gets smaller. Fortunately Tyler found a great way to do it with clamp(). * { letter-spacing: clamp( -0.05em, calc((1em - 1rem) / -10), 0em ); } Great minds think alike as I was thinking about the UI/UX pattern of one-time password inputs lately too. Tyler again here tackles it his own way in Simple One-Time Passcode Inputs. We agree: don’t actually make it multiple <input /> elements, as tempting as that is from a styling perspective. There is simply too much JavaScript required to make that work nicely (which can easily break down) and you otherwise get for free if you don’t do that. Instead, use the proper set of attributes on an input for it: <input type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="6"> Then just style it with CSS to make it look however you want. That is, apply the input mask with styles alone. I was just bemoaning the loss of JPEG XL the other day, but in On Container Queries, Responsive Images, and JPEG-XL a couple years back, Jason Grigsby was already in grief. Container Queries, one of the great CSS revelations of our time, is spiritually connected to responsive images in that they want to do the most-appropriate thing with the information available on how they are displayed on the page. You’d think they would get along and share information, but you’d be wrong. We could evolve the syntaxes to help, and probably should, but it’s intereseting that JPEG-XL, and image format, was actually somewhat designed to help with this and would have been a serendipitous gift for web developers, if we actually were able to use it. Speaking of images, I think we all know we’re not really supposed to be using GIF anymore as the performance sucks and we can replicate it with video. I tend to like the video approach, but there are significant drawbacks to be aware of: Tyler looked at alternatives and there are some really strong contenders in the form of newfangled image formats. But (surprise surprise) the browser support story is dicey. Gerardo Rodriguez talks about Testing HTML Light DOM Web Components: Easier Than Expected! Good to know, really. Testing here meaning load up the component in a browser-like environment and make sure what you expect to be there in the DOM is there and interacting with it does what you expect. It’s not like you can’t test Web Components that use the Shadow DOM, but in practice it’s an annoying barrier that you have to manually account for in many places, and other tools need to be aware of and work with as well. Nice to have so uch example code here to reference. OK fine one more. This is a nice demo of a common web app interaction. It’s a good reminder that we really need to implement “bulk actions” in more places on CodePen properly.
  8. by: Geoff Graham Mon, 15 Dec 2025 17:55:55 +0000 Andy Clarke with a brand-new resource. It generates the sort of fun typography that Andy commonly uses in his own work that’s geared towards cartoon headings. There are a number of configurable options for font, color, stroke, letter spacing, and shadows. And it spits out the CSS for you to copy-paste. What if you want to individually style each letter? Well, there’s no such thing as :nth-letter in CSS at the moment. There are, however, tools that will wrap each character in a separate <span>, the most tradition one being SplitText.js. But Andy decided to make a tool for that — called Splinter.js — as well because he saw opportunities to enhance the accessibility of the generated markup to help prevent some assistive tech from choking on the spans. (GSAP’s version also does a good job of this.) Som instead of: <h2> <span>H</span> <span>u</span> <span>m</span> <!-- etc. --> </h2> …we get ARIA-spiced markup: <h2 data-split="toon" aria-label="Hum Sweet Hum"> <span class="toon-char" aria-hidden="true">H</span> <span class="toon-char" aria-hidden="true">u</span> <span class="toon-char" aria-hidden="true">m</span> </h2> And it supports line breaks! Toon Title Text Generator originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
  9. by: Sourav Rudra Mon, 15 Dec 2025 22:27:43 +0530 Snapscope makes it easy to scan any Snap package for security issues.
  10. by: Temani Afif Mon, 15 Dec 2025 16:19:23 +0000 A list of rounded images that slightly overlap each other is a classic web design pattern. 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. CodePen Embed Fallback 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 Horizontal Lists (You are here!) Circular Lists (Coming this week) 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: CodePen Embed Fallback 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. CodePen Embed Fallback 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. CodePen Embed Fallback 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. CodePen Embed Fallback 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!! CodePen Embed Fallback 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. CodePen Embed Fallback 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: CodePen Embed Fallback 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); } CodePen Embed Fallback 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: CodePen Embed Fallback 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)); } CodePen Embed Fallback 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: CodePen Embed Fallback 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: CodePen Embed Fallback 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 Horizontal Lists (You are here!) Circular Lists (Coming this week) Responsive List of Avatars Using Modern CSS (Part 1) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
  11. by: Abhishek Prakash Mon, 15 Dec 2025 18:17:21 +0530 Over the span of the past 15 years, Ubuntu started several projects. Not all of them are active today. And yet, they live in our memory.
  12. by: Sourav Rudra Mon, 15 Dec 2025 12:20:21 +0530 New policy targets low-quality AI-generated code while still allowing AI as a learning tool.
  13. by: Roland Taylor Mon, 15 Dec 2025 10:22:39 +0530 If you recognized yourself in a few of these points, that doesn’t mean Linux isn’t for you. In fact, you can count it as an invitation. It just means you’ve spent a long time in an ecosystem that treats you more like a product than a participant.
  14. by: Theena Kumaragurunathan Sun, 14 Dec 2025 09:17:30 +0530 A moderation system that leans on automation just knocked legitimate tech tutorials and even entire channels offline. The appeals felt automated, too. Creators are powerless against opaque enforcement and the incentives that should favor craft and trust are tilting toward noise.
  15. by: Roland Taylor Sat, 13 Dec 2025 09:27:34 +0530 NTFSPlus is a fresh implementation of the classic in-kernel ntfs driver. Can it end the current NTFS woes for Linux users?

Important Information

Terms of Use Privacy Policy Guidelines We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.