{"id":10379,"date":"2026-07-13T12:34:02","date_gmt":"2026-07-13T17:34:02","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10379"},"modified":"2026-07-13T12:34:03","modified_gmt":"2026-07-13T17:34:03","slug":"in-n-out-animation-using-sibling-index","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/in-n-out-animation-using-sibling-index\/","title":{"rendered":"In-N-Out Animation using sibling-index()"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Chris shared a <a href=\"https:\/\/master.dev\/blog\/in-n-out-animations-dialogs-part-1-3\/\">nice series of articles<\/a> focused on animating elements in and out of view. The first two articles were about popups &amp; dialogs that required no JavaScript. CSS gives us native ways to open\/close those elements while having cool animations. The third article was about view transitions, and while it\u2019s a powerful CSS feature, some JavaScript code is still required to make it work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the original demo where you can add\/remove items and everything adjusts smoothly:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019d175c-474f-7b41-a28f-77396e88ef21\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019d175c-474f-7b41-a28f-77396e88ef21?height=450&amp;theme-id=1&amp;slug-hash=019d175c-474f-7b41-a28f-77396e88ef21&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019d175c-474f-7b41-a28f-77396e88ef21\" title=\"CodePen Embed 019d175c-474f-7b41-a28f-77396e88ef21\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">All the effects are managed with CSS, and the JavaScript is just the basic code to trigger the view transitions. Compared to some heavy JS libraries, it\u2019s pretty minimal, and that\u2019s cool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, I will explore another CSS-only idea that doesn\u2019t require JavaScript (except for the one to add\/remove DOM elements).<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\"><em>At the time of writing, only Chrome and Edge fully <a href=\"https:\/\/caniuse.com\/wf-sibling-count\">support the features<\/a> we will be using.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is my version of the same demo:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GgryErq\/5e77a90eaf535e1eb04d68a9c0968728\" src=\"\/\/codepen.io\/anon\/embed\/GgryErq\/5e77a90eaf535e1eb04d68a9c0968728?height=450&amp;theme-id=1&amp;slug-hash=GgryErq\/5e77a90eaf535e1eb04d68a9c0968728&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GgryErq\/5e77a90eaf535e1eb04d68a9c0968728\" title=\"CodePen Embed GgryErq\/5e77a90eaf535e1eb04d68a9c0968728\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s not exactly the same as the original demo, but the CSS code required to achieve that effect is pretty small and relies mainly on one feature: <code>sibling-index()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I suppose you are curious to know how it works, right? Let\u2019s get started!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Technique<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>sibling-index()<\/code> function in CSS returns the index of an element among its siblings. We generally consider the index to be static\/fixed, which is the case in most situations, but what if we add\/remove elements? The index gets updated to match the new HTML structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take the following example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"container\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>red<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>    <span class=\"hljs-comment\">&lt;!-- index = 1 --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>green<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>  <span class=\"hljs-comment\">&lt;!-- index = 2 --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>blue<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>   <span class=\"hljs-comment\">&lt;!-- index = 3 --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>yellow<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- index = 4 --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If we remove the \u201cgreen\u201d element, the structure becomes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"container\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>red<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>    <span class=\"hljs-comment\">&lt;!-- index = 1 --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>blue<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>   <span class=\"hljs-comment\">&lt;!-- index = 2 --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>yellow<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- index = 3 --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The index of \u201cblue\u201d and \u201cyellow\u201d have changed. If I use the index within a property and I apply a transition to that property, I will get an animation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a basic demo. Click <strong>remove<\/strong> and see what happens:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_rajpJQp\/370122f4a74e23105eb537b7cc33d6a3\" src=\"\/\/codepen.io\/anon\/embed\/rajpJQp\/370122f4a74e23105eb537b7cc33d6a3?height=450&amp;theme-id=1&amp;slug-hash=rajpJQp\/370122f4a74e23105eb537b7cc33d6a3&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed rajpJQp\/370122f4a74e23105eb537b7cc33d6a3\" title=\"CodePen Embed rajpJQp\/370122f4a74e23105eb537b7cc33d6a3\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The CSS code is as simple as:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.container<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: grid;\n}\n<span class=\"hljs-selector-class\">.container<\/span> <span class=\"hljs-selector-tag\">div<\/span> {\n  <span class=\"hljs-attribute\">grid-area<\/span>: <span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">150px<\/span>;\n  <span class=\"hljs-attribute\">translate<\/span>: <span class=\"hljs-built_in\">calc<\/span>((sibling-index() - <span class=\"hljs-number\">1<\/span>)*<span class=\"hljs-number\">160px<\/span>);\n  <span class=\"hljs-attribute\">transition<\/span>: .<span class=\"hljs-number\">4s<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">All the elements are placed on top of each other (using <code>grid-area: 1\/1<\/code>), then translated using their indexes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If an element is removed\/added, the indexes are updated, and the translate values are recalculated, creating a nice animation thanks to the transition. With that simple trick, we can create in-and-out animations!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Demo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s get back to our demo and understand what\u2019s going on. Here is the main CSS code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ul<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: grid;\n}\n<span class=\"hljs-selector-tag\">ul<\/span> &gt; <span class=\"hljs-selector-tag\">li<\/span> {\n  <span class=\"hljs-attribute\">--g<\/span>: .<span class=\"hljs-number\">5rem<\/span>; <span class=\"hljs-comment\">\/* gap between items *\/<\/span>\n  \n  <span class=\"hljs-attribute\">grid-area<\/span>: <span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">translate<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-built_in\">calc<\/span>((sibling-index() - <span class=\"hljs-number\">1<\/span>)*(<span class=\"hljs-number\">100%<\/span> + <span class=\"hljs-built_in\">var<\/span>(--g)));\n  <span class=\"hljs-attribute\">transition<\/span>: .<span class=\"hljs-number\">5s<\/span>;\n\n  @starting-style {\n    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translatex<\/span>(-<span class=\"hljs-number\">80%<\/span>);\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">I simply added a <code>@starting-style<\/code> declaration. That\u2019s what controls the entry effect when a new element is added to the DOM. I am using opacity combined with a left translation to match the demo created by Chris, but it can be anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a different entry effect using scale and opacity:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NPdXOXG\/7634f4b9270d492af0d0ce3ebe706640\" src=\"\/\/codepen.io\/anon\/embed\/NPdXOXG\/7634f4b9270d492af0d0ce3ebe706640?height=550&amp;theme-id=1&amp;slug-hash=NPdXOXG\/7634f4b9270d492af0d0ce3ebe706640&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NPdXOXG\/7634f4b9270d492af0d0ce3ebe706640\" title=\"CodePen Embed NPdXOXG\/7634f4b9270d492af0d0ce3ebe706640\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s all. With less than 10 lines of CSS, I managed to get a cool effect.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Drawbacks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now, let\u2019s talk about the drawbacks of this method. I am using <code>translate<\/code> to place the elements, which means we are working with out-of-flow content. In our case, the width of the list is good, but the height is not, and the items are overflowing their container. In some situations, it\u2019s fine, but in others, we may need to find some workarounds to avoid this overflow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">About the <code>height<\/code>, all the items need to have the same height, otherwise we cannot apply this technique. Even if the items don\u2019t have the same height, I am forcing that by using <code>grid-area: 1\/1<\/code>. By placing all the items in the same grid area, I will get an area with a height equal to the tallest item and all items will, by default, get stretched inside that area.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the previous demo where I made the second item bigger:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GgryxJp\/27943d53882ad010d20efc2457526c69\" src=\"\/\/codepen.io\/anon\/embed\/GgryxJp\/27943d53882ad010d20efc2457526c69?height=550&amp;theme-id=1&amp;slug-hash=GgryxJp\/27943d53882ad010d20efc2457526c69&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GgryxJp\/27943d53882ad010d20efc2457526c69\" title=\"CodePen Embed GgryxJp\/27943d53882ad010d20efc2457526c69\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">All the items will follow that height (unless you change the alignment). It\u2019s still a drawback, but at least the layout is not broken, and everything works fine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In our demo, it was about height, but in other demos, it can be width, so let\u2019s say there is a fixed size to respect (either the height, the width, or both).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last drawback is that we don\u2019t really have an out animation. Removing an element will adjust all the others perfectly, but the removed element has no animation. It\u2019s not really a drawback because we don\u2019t have a native CSS mechanism to do this like <code>@starting-style<\/code> (maybe an <code>@ending-style<\/code> in the future?). For this reason, Chris explored <a href=\"https:\/\/master.dev\/blog\/in-n-out-animations-view-transitions-part-3-3\/\">the view transition method<\/a> that allows us to achieve a real out animation.<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">That\u2019s a lot of drawbacks!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, but given the code we used (barely 6 lines of CSS), it\u2019s still a great CSS trick that can create many cool demos. Speaking about demos, let\u2019s have a look at some of them!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">More Examples<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a list of avatars within a container. For this one, we don\u2019t have any sizing drawbacks. The container height is good (it follows the image height), and we have a full-width configuration.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_myRpOdQ\/387d810b28a94f36fb7a3c5cf0181e22\" src=\"\/\/codepen.io\/anon\/embed\/myRpOdQ\/387d810b28a94f36fb7a3c5cf0181e22?height=450&amp;theme-id=1&amp;slug-hash=myRpOdQ\/387d810b28a94f36fb7a3c5cf0181e22&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed myRpOdQ\/387d810b28a94f36fb7a3c5cf0181e22\" title=\"CodePen Embed myRpOdQ\/387d810b28a94f36fb7a3c5cf0181e22\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Since the width of the container doesn\u2019t depend on its content, I made it a container and used <code>cqw<\/code> to create a responsive behavior where the image may overlap to always fit within the container, regardless of the total number.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"659\" height=\"377\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/u9xZHtrI.png?resize=659%2C377&#038;ssl=1\" alt=\"\" class=\"wp-image-10396\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/u9xZHtrI.png?w=659&amp;ssl=1 659w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/u9xZHtrI.png?resize=300%2C172&amp;ssl=1 300w\" sizes=\"auto, (max-width: 659px) 100vw, 659px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For that, I defined a slightly complex translate value that depends on the container size (<code>100cqw<\/code>) and the number of items (<code>sibling-count()<\/code>)<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">translate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-tag\">sibling-index<\/span>() <span class=\"hljs-selector-tag\">-<\/span> 1)*<span class=\"hljs-selector-tag\">min<\/span>(100% + <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--g<\/span>),100<span class=\"hljs-selector-tag\">cqw<\/span>\/<span class=\"hljs-selector-tag\">sibling-count<\/span>() <span class=\"hljs-selector-tag\">-<\/span> (100% <span class=\"hljs-selector-tag\">-<\/span> 100<span class=\"hljs-selector-tag\">cqw<\/span>\/<span class=\"hljs-selector-tag\">sibling-count<\/span>())\/(<span class=\"hljs-selector-tag\">sibling-count<\/span>() <span class=\"hljs-selector-tag\">-<\/span> 1))) 0;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Here is another idea with <a href=\"https:\/\/css-tip.com\/images-circle\/\">a circular list of avatars<\/a>. This time, I am not using <code>translate<\/code> but <code>offset<\/code> combined with <code>circle()<\/code>. For this demo, the circle expands to prevent the images from overlapping.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_jEbbLZb\" src=\"\/\/codepen.io\/anon\/embed\/jEbbLZb?height=600&amp;theme-id=1&amp;slug-hash=jEbbLZb&amp;default-tab=result\" height=\"600\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed jEbbLZb\" title=\"CodePen Embed jEbbLZb\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">And if you don\u2019t want the circle to get bigger, you can use a fixed radius value. Doing this gives us a similar behavior to the first example: an overlap between the images.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OPWzEZJ\/ce7914363ab89072e9d861e16efc7ab8\" src=\"\/\/codepen.io\/anon\/embed\/OPWzEZJ\/ce7914363ab89072e9d861e16efc7ab8?height=550&amp;theme-id=1&amp;slug-hash=OPWzEZJ\/ce7914363ab89072e9d861e16efc7ab8&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OPWzEZJ\/ce7914363ab89072e9d861e16efc7ab8\" title=\"CodePen Embed OPWzEZJ\/ce7914363ab89072e9d861e16efc7ab8\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s try with a grid of items. By knowing the container width (using <code>cqw<\/code>), and the item size, we can calculate the coordinate of each item within the grid and place them using translate:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.container<\/span> {\n  <span class=\"hljs-attribute\">--g<\/span>: <span class=\"hljs-number\">10px<\/span>; <span class=\"hljs-comment\">\/*gap *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">130px<\/span>; <span class=\"hljs-comment\">\/* image size *\/<\/span>\n  \n  <span class=\"hljs-attribute\">display<\/span>: grid;\n  <span class=\"hljs-attribute\">container-type<\/span>: inline-size;\n}\n<span class=\"hljs-selector-class\">.container<\/span> <span class=\"hljs-selector-tag\">img<\/span> {\n  <span class=\"hljs-attribute\">grid-area<\/span>: <span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-built_in\">var<\/span>(--s);\n  <span class=\"hljs-comment\">\/* the number of items that can fit inside one row *\/<\/span>\n  <span class=\"hljs-attribute\">--n<\/span>: <span class=\"hljs-built_in\">round<\/span>(down,(<span class=\"hljs-number\">100<\/span>cqw + var(--g))\/(<span class=\"hljs-built_in\">var<\/span>(--s) + <span class=\"hljs-built_in\">var<\/span>(--g)));\n  <span class=\"hljs-comment\">\/* the item coordiantes *\/<\/span>\n  <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-built_in\">round<\/span>(down,(sibling-index() - <span class=\"hljs-number\">1<\/span>)\/<span class=\"hljs-built_in\">var<\/span>(--n));\n  <span class=\"hljs-attribute\">--j<\/span>: <span class=\"hljs-built_in\">mod<\/span>(sibling-index() - <span class=\"hljs-number\">1<\/span>,<span class=\"hljs-built_in\">var<\/span>(--n));\n  <span class=\"hljs-attribute\">translate<\/span>: \n      <span class=\"hljs-built_in\">calc<\/span>(var(--j)*(<span class=\"hljs-number\">100%<\/span> + <span class=\"hljs-built_in\">var<\/span>(--g))) \n      <span class=\"hljs-built_in\">calc<\/span>(var(--i)*(<span class=\"hljs-number\">100%<\/span> + <span class=\"hljs-built_in\">var<\/span>(--g)));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In addition to the nice effect we get when adding\/removing items, we also get a nice transition when you resize the screen. Try it!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNgamKV\/1a2436f4e0f36d784f8d582fff845d6c\" src=\"\/\/codepen.io\/anon\/embed\/bNgamKV\/1a2436f4e0f36d784f8d582fff845d6c?height=950&amp;theme-id=1&amp;slug-hash=bNgamKV\/1a2436f4e0f36d784f8d582fff845d6c&amp;default-tab=result\" height=\"950\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNgamKV\/1a2436f4e0f36d784f8d582fff845d6c\" title=\"CodePen Embed bNgamKV\/1a2436f4e0f36d784f8d582fff845d6c\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">I was also able to overcome the height drawback by using a hidden <code>&lt;div&gt;<\/code> inside the grid as a last child. By placing it in the correct row, I ensure the container grows to accommodate all the elements. I won\u2019t get into the fine details of this hack, but it illustrates that, depending on the use case, we can find workarounds to address some of the drawbacks we listed previously.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Respect to <code>sibling-index()<\/code>! Now, you know the superpower of this feature. Getting the index of an element? Nah, making cool demos featuring in-and-out animations!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Don&#8217;t forget to check <a href=\"https:\/\/master.dev\/blog\/in-n-out-animations-dialogs-part-1-3\/\">the article series by Chris<\/a>, especially the third one. It would be a good exercise to implement my demos using the view transition technique and compare both methods.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Temani mimics a View Transition by placing items using their index and translations. The placement calculation change when the index changes, which is an animation opportunity! <\/p>\n","protected":false},"author":12,"featured_media":10389,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[100,7,250],"class_list":["post-10379","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-animation","tag-css","tag-sibling-index"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/circle-in-out.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10379","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=10379"}],"version-history":[{"count":7,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10379\/revisions"}],"predecessor-version":[{"id":10397,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10379\/revisions\/10397"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10389"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10379"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10379"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10379"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}