{"id":9033,"date":"2026-03-23T13:57:05","date_gmt":"2026-03-23T18:57:05","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9033"},"modified":"2026-03-24T18:23:49","modified_gmt":"2026-03-24T23:23:49","slug":"two-circles-one-arrow-and-anchor-positioning","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/two-circles-one-arrow-and-anchor-positioning\/","title":{"rendered":"Two Circles, One Arrow, and Anchor Positioning"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In a previous series of articles, we studied a classic use case of anchor positioning: <a href=\"https:\/\/frontendmasters.com\/blog\/perfectly-pointed-tooltips-a-foundation\/\">Tooltips<\/a>. In this article, we will explore a less common use case that is a great example of the power of this new feature and modern CSS in general.<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">At the time of writing, only Chrome and Edge fully support the features we will be using.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s start with a demo. Drag both circles and see how the arrow follows the movement. You will also <strong>get the distance between the two circles inside the arrow shape.<\/strong> And if the circles are too close, the arrow shape changes!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_wBWWKxP\" src=\"\/\/codepen.io\/anon\/embed\/wBWWKxP?height=550&amp;theme-id=1&amp;slug-hash=wBWWKxP&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed wBWWKxP\" title=\"CodePen Embed wBWWKxP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Except for the drag feature, everything else is controlled using CSS. The position and shape of the arrow, the distance calculation, collision\/proximity detection, etc. I know it\u2019s hard to believe, but CSS has evolved a lot to make this possible!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">&#8220;Why CSS?!&#8221;<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The classic question that always emerges in such situations. You might think this use case is not suitable for a CSS-only approach, and I agree. There are plenty of tools\/libraries that rely on JavaScript and\/or SVG, which do the job perfectly. So use those if you need this kind of feature.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My CSS-only demo can be seen as an experiment or an exploration of new CSS features. The goal is not to build that demo, but the steps I followed and the tricks I used to build it. Pushing the limit of CSS and creating things that sound \u201cimpossible\u201d is the best way to learn CSS. I say \u201cimpossible\u201d because there is plenty of stuff that I was not able to build, but when I succeed, I am here to write an article about it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you are eager to learn cool CSS tricks and discover some modern features, you are in the right place!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Initial Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The main idea is that one element can be anchored to multiple elements (two in our case).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-pseudo\">:nth-child(1<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-class\">.circle<\/span>) {\n  <span class=\"hljs-attribute\">anchor-name<\/span>: --c1;\n}\n<span class=\"hljs-selector-pseudo\">:nth-child(2<\/span> <span class=\"hljs-selector-tag\">of<\/span> <span class=\"hljs-selector-class\">.circle<\/span>) {\n  <span class=\"hljs-attribute\">anchor-name<\/span>: --c2;\n}\n<span class=\"hljs-selector-class\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">top<\/span>:    <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 top),   <span class=\"hljs-built_in\">anchor<\/span>(--c2 top));\n  <span class=\"hljs-attribute\">left<\/span>:   <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 left),  <span class=\"hljs-built_in\">anchor<\/span>(--c2 left));\n  <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 bottom),<span class=\"hljs-built_in\">anchor<\/span>(--c2 bottom));\n  <span class=\"hljs-attribute\">right<\/span>:  <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 right), <span class=\"hljs-built_in\">anchor<\/span>(--c2 right));\n  <span class=\"hljs-attribute\">outline<\/span>: <span class=\"hljs-number\">5px<\/span> solid <span class=\"hljs-number\">#000<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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\">The above code creates a rectangle around both circles. It\u2019s actually the smallest rectangle that can hold both circles. Drag the circles in the demo below and see how it works.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_wBzJdzL\/7a7d5bde999fc3e47fb4b0cc649f3cf4\" src=\"\/\/codepen.io\/anon\/embed\/wBzJdzL\/7a7d5bde999fc3e47fb4b0cc649f3cf4?height=550&amp;theme-id=1&amp;slug-hash=wBzJdzL\/7a7d5bde999fc3e47fb4b0cc649f3cf4&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed wBzJdzL\/7a7d5bde999fc3e47fb4b0cc649f3cf4\" title=\"CodePen Embed wBzJdzL\/7a7d5bde999fc3e47fb4b0cc649f3cf4\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Each side of the rectangle will consider the minimum value from both circles. To better understand the trick, reason through each side individually. For example, the top value needs to match the highest circle&#8217;s top value, and that circle will logically have the smallest top value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can still optimize the code a little and use the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">top<\/span>:    <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\n  <span class=\"hljs-attribute\">left<\/span>:   <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\n  <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\n  <span class=\"hljs-attribute\">right<\/span>:  <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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\">The value <code>inside<\/code> is a special value that refers to the same side as the one where it\u2019s used. With top, it\u2019s equal to top; with left, it&#8217;s equal to left, and so on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This gives us the same value for all the properties that we can turn into a variable:<\/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\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">--p<\/span>: <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\n  <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-built_in\">var<\/span>(--p);\n  <span class=\"hljs-attribute\">left<\/span>: <span class=\"hljs-built_in\">var<\/span>(--p);\n  <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-built_in\">var<\/span>(--p);\n  <span class=\"hljs-attribute\">right<\/span>: <span class=\"hljs-built_in\">var<\/span>(--p);\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\">Or simply use the <code>inset<\/code> property:<\/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-class\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside), <span class=\"hljs-built_in\">anchor<\/span>(--c2 inside));\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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ZYpeKJL\/9a847044d0ed06c7c7fd9a7a9328a138\" src=\"\/\/codepen.io\/anon\/embed\/ZYpeKJL\/9a847044d0ed06c7c7fd9a7a9328a138?height=550&amp;theme-id=1&amp;slug-hash=ZYpeKJL\/9a847044d0ed06c7c7fd9a7a9328a138&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ZYpeKJL\/9a847044d0ed06c7c7fd9a7a9328a138\" title=\"CodePen Embed ZYpeKJL\/9a847044d0ed06c7c7fd9a7a9328a138\" 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 update the inset property and make the rectangle start at the center of the circles:<\/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-class\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-built_in\">min<\/span>(anchor(--c1 inside) + <span class=\"hljs-built_in\">anchor-size<\/span>(--c1)\/<span class=\"hljs-number\">2<\/span>,\n             <span class=\"hljs-built_in\">anchor<\/span>(--c1 inside) + <span class=\"hljs-built_in\">anchor-size<\/span>(--c2)\/<span class=\"hljs-number\">2<\/span>);\n}<\/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\">By adding half the distance to each side, we are decreasing the size of the rectangle, and we get the following:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_raMymGm\/5ace25ac1389d5afef53d26cb8602b18\" src=\"\/\/codepen.io\/anon\/embed\/raMymGm\/5ace25ac1389d5afef53d26cb8602b18?height=550&amp;theme-id=1&amp;slug-hash=raMymGm\/5ace25ac1389d5afef53d26cb8602b18&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raMymGm\/5ace25ac1389d5afef53d26cb8602b18\" title=\"CodePen Embed raMymGm\/5ace25ac1389d5afef53d26cb8602b18\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Do you start to see where we are going? With barely two or three CSS properties, we can already see a link between the two circles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Linking the Circles<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that I have a rectangle that follows the position of both circles, the first idea that came to my mind was to \u201cshape\u201d that rectangle to make it look like an arrow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We have <code>clip-path<\/code> so let\u2019s start with a simple shape:<\/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\">.arrow<\/span> {\n  <span class=\"hljs-attribute\">clip-path<\/span>: <span class=\"hljs-built_in\">polygon<\/span>(<span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">15px<\/span> <span class=\"hljs-number\">0<\/span>,<span class=\"hljs-number\">100%<\/span> calc(<span class=\"hljs-number\">100%<\/span> - <span class=\"hljs-number\">15px<\/span>), <span class=\"hljs-number\">100%<\/span> <span class=\"hljs-number\">100%<\/span>, <span class=\"hljs-built_in\">calc<\/span>(<span class=\"hljs-number\">100%<\/span> - <span class=\"hljs-number\">15px<\/span>) <span class=\"hljs-number\">100%<\/span>,<span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">15px<\/span>);\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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNwqWaG\/a5c0a8adcdc366d5b0f45fafe7f3f465\" src=\"\/\/codepen.io\/anon\/embed\/bNwqWaG\/a5c0a8adcdc366d5b0f45fafe7f3f465?height=550&amp;theme-id=1&amp;slug-hash=bNwqWaG\/a5c0a8adcdc366d5b0f45fafe7f3f465&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNwqWaG\/a5c0a8adcdc366d5b0f45fafe7f3f465\" title=\"CodePen Embed bNwqWaG\/a5c0a8adcdc366d5b0f45fafe7f3f465\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Not bad at first glance, but once you start dragging the circles around, it looks messy.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1001\" height=\"271\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/DeesSK9Q.png?resize=1001%2C271&#038;ssl=1\" alt=\"Three sets of colored circles and lines with labels: 'Good' in green, 'not bad' in gray, and 'Ugly!' in red.\" class=\"wp-image-9061\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/DeesSK9Q.png?w=1001&amp;ssl=1 1001w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/DeesSK9Q.png?resize=300%2C81&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/DeesSK9Q.png?resize=768%2C208&amp;ssl=1 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The shape I created will link to the top-left corner of the rectangle with the bottom-right corner, but this is not ideal, as we can have four different positions (as illustrated below) and this is only good for two of them (A and D).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"597\" height=\"491\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O5sLWpZ.png?resize=597%2C491&#038;ssl=1\" alt=\"\" class=\"wp-image-9062\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O5sLWpZ.png?w=597&amp;ssl=1 597w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O5sLWpZ.png?resize=300%2C247&amp;ssl=1 300w\" sizes=\"auto, (max-width: 597px) 100vw, 597px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We need to adjust the shape when we get into the B and C positions. The question is: <em>How do we know in which position we are?<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In theory, we can do the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--x<\/span>: <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">anchor<\/span>(<span class=\"hljs-selector-tag\">--c1<\/span> <span class=\"hljs-selector-tag\">left<\/span>) <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-tag\">anchor<\/span>(<span class=\"hljs-selector-tag\">--c2<\/span> <span class=\"hljs-selector-tag\">left<\/span>));\n<span class=\"hljs-selector-tag\">--y<\/span>: <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">anchor<\/span>(<span class=\"hljs-selector-tag\">--c1<\/span> <span class=\"hljs-selector-tag\">top<\/span>) <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-tag\">anchor<\/span>(<span class=\"hljs-selector-tag\">--c2<\/span> <span class=\"hljs-selector-tag\">top<\/span>));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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\">When we are at position A, the first circle will have a smaller left value and a smaller top value, hence <code>--x<\/code> will be equal to <code>-1<\/code> and <code>--y<\/code> equal to <code>-1<\/code>. For position B, we get <code>1<\/code> and <code>-1<\/code>, and so on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Based on the values of <code>--x<\/code> and <code>--y<\/code>, we can conditionally have a different shape per position, but unfortunately, it is not possible because the <code>anchor()<\/code> function can only be used with the inset properties. This will probably change in a future (I hope so), but until then we need another solution.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The only solution I have found is to have an element per position, hence the HTML code of the arrow will look like below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" 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\">\"arrow\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">b<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">b<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">c<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">c<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">d<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">d<\/span>&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-8\"><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\">Then, we define each position as follows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.arrow<\/span> &gt; * {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">--_x<\/span>: <span class=\"hljs-built_in\">calc<\/span>(anchor(--c1 inside) + <span class=\"hljs-built_in\">anchor-size<\/span>(--c1)\/<span class=\"hljs-number\">2<\/span>);\n  <span class=\"hljs-attribute\">--_y<\/span>: <span class=\"hljs-built_in\">calc<\/span>(anchor(--c2 inside) + <span class=\"hljs-built_in\">anchor-size<\/span>(--c2)\/<span class=\"hljs-number\">2<\/span>);\n}\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-pseudo\">:is(a<\/span>,<span class=\"hljs-selector-tag\">b<\/span>) {<span class=\"hljs-attribute\">top<\/span>:  <span class=\"hljs-built_in\">var<\/span>(--_x); <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-built_in\">var<\/span>(--_y)}\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-pseudo\">:is(a<\/span>,<span class=\"hljs-selector-tag\">c<\/span>) {<span class=\"hljs-attribute\">left<\/span>: <span class=\"hljs-built_in\">var<\/span>(--_x); <span class=\"hljs-attribute\">right<\/span>:  <span class=\"hljs-built_in\">var<\/span>(--_y)}\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-pseudo\">:is(c<\/span>,<span class=\"hljs-selector-tag\">d<\/span>) {<span class=\"hljs-attribute\">top<\/span>:  <span class=\"hljs-built_in\">var<\/span>(--_y); <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-built_in\">var<\/span>(--_x)}\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-pseudo\">:is(b<\/span>,<span class=\"hljs-selector-tag\">d<\/span>) {<span class=\"hljs-attribute\">left<\/span>: <span class=\"hljs-built_in\">var<\/span>(--_y); <span class=\"hljs-attribute\">right<\/span>:  <span class=\"hljs-built_in\">var<\/span>(--_x)}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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\">We no longer need to use <code>min()<\/code> but instead, we use both values that I am defining as <code>--_x<\/code> and <code>--_y<\/code>. The code may look complex at first glance, but if you refer to the previous figure, you will understand its logic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Positions A and B share the same vertical position for both circles, so they have the same top and bottom value. A and C share the same horizontal position, hence the same left and right value, etc.<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">But, will we have four elements visible at the same time?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Not really. For example, the <code>&lt;a&gt;<\/code> element has the following inset values:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* pseudo-code *\/<\/span>\n<span class=\"hljs-selector-tag\">a<\/span> {\n  <span class=\"hljs-attribute\">top<\/span>: top_of_c1;\n  <span class=\"hljs-attribute\">bottom<\/span>: bottom_of_c2;\n  <span class=\"hljs-attribute\">left<\/span>: left_of_c1;\n  <span class=\"hljs-attribute\">right<\/span>: right_of_c2;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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\">If we are at position A, it will be visible, but if we are at position B, you won\u2019t see it because <code>left_of_c1<\/code> will be bigger than the <code>right_of_c2<\/code>. When the right value is bigger than the left value the browser makes them equal, which means the element has a width equal to <code>0<\/code> hence invisible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I think a demo is worth a thousand words. If you drag the circles, you can see four different colors for each element, <strong>and only one color is visible at a time.<\/strong><\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LERWyvX\/e943ba53f15ff9dd70c149a6bce0cac0\" src=\"\/\/codepen.io\/anon\/embed\/LERWyvX\/e943ba53f15ff9dd70c149a6bce0cac0?height=550&amp;theme-id=1&amp;slug-hash=LERWyvX\/e943ba53f15ff9dd70c149a6bce0cac0&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LERWyvX\/e943ba53f15ff9dd70c149a6bce0cac0\" title=\"CodePen Embed LERWyvX\/e943ba53f15ff9dd70c149a6bce0cac0\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Another observation we can make is that each position can be defined as a flip of another one. If we flip A vertically, we get B. If we flip it horizontally, we get C, and if we flip it both horizontally and vertically, we get D. This means we are not going to write too much code. We do the job once, and then we flip!<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.arrow<\/span> &gt; * { <span class=\"hljs-comment\">\/* the same code for all *\/<\/span> }\n\n<span class=\"hljs-comment\">\/* We flip the other positions *\/<\/span>\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-tag\">b<\/span> {<span class=\"hljs-attribute\">scale<\/span>: -<span class=\"hljs-number\">1<\/span>  <span class=\"hljs-number\">1<\/span>} <span class=\"hljs-comment\">\/* vertically *\/<\/span>\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-tag\">c<\/span> {<span class=\"hljs-attribute\">scale<\/span>:  <span class=\"hljs-number\">1<\/span> -<span class=\"hljs-number\">1<\/span>} <span class=\"hljs-comment\">\/* horizontally *\/<\/span>\n<span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-tag\">d<\/span> {<span class=\"hljs-attribute\">scale<\/span>: -<span class=\"hljs-number\">1<\/span> -<span class=\"hljs-number\">1<\/span>} <span class=\"hljs-comment\">\/* horizontally &amp; vertically *\/<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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\">Let\u2019s try again with the previous <code>clip-path<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dPpvWxr\/02b431fbdaf0aeedb2c6dbe47a6cf841\" src=\"\/\/codepen.io\/anon\/embed\/dPpvWxr\/02b431fbdaf0aeedb2c6dbe47a6cf841?height=550&amp;theme-id=1&amp;slug-hash=dPpvWxr\/02b431fbdaf0aeedb2c6dbe47a6cf841&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dPpvWxr\/02b431fbdaf0aeedb2c6dbe47a6cf841\" title=\"CodePen Embed dPpvWxr\/02b431fbdaf0aeedb2c6dbe47a6cf841\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Much better: the shape links&nbsp;the two circles, regardless of their positions. Now let\u2019s work on improving that shape and creating a perfect arrow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Arrow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Using <code>clip-path<\/code> to create the arrow is the route to follow, but it won\u2019t be enough alone. I made a first attempt with only <code>clip-path<\/code>, and it worked, but it was spaghetti code with a lot of math functions. I tried to optimize and came up with better ideas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Starting here, I will focus only on position A. We can ignore the others, since they are a flipped version of position A, as I explained previously.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"443\" height=\"373\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/BWUV4G12.png?resize=443%2C373&#038;ssl=1\" alt=\"\" class=\"wp-image-9064\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/BWUV4G12.png?w=443&amp;ssl=1 443w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/BWUV4G12.png?resize=300%2C253&amp;ssl=1 300w\" sizes=\"auto, (max-width: 443px) 100vw, 443px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Now, instead of trying to shape the <code>&lt;a&gt;<\/code> element (as well as <code>&lt;b&gt;<\/code>, <code>&lt;c&gt;<\/code>, and <code>&lt;d&gt;<\/code>), I will consider its pseudo-element. The element is a rectangle sized with anchor positioning, so I can make it a container and work \u201cinside\u201d it by using its width\/height, thanks to container query units.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Knowing the dimensions of the rectangle will make it a lot easier to create and position the arrow. We cannot rely on <code>anchor()<\/code> and <code>anchor-size()<\/code> to get them, so we will use container queries! That\u2019s why I&#8217;m using the pseudo-element, as I cannot retrieve the values from the container itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, we start by creating an arrow, and we position it at the bottom of the rectangle:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_emdvVZz\/bbbaceb4936a8fa205ca78c7b18021e2\" src=\"\/\/codepen.io\/anon\/embed\/emdvVZz\/bbbaceb4936a8fa205ca78c7b18021e2?height=550&amp;theme-id=1&amp;slug-hash=emdvVZz\/bbbaceb4936a8fa205ca78c7b18021e2&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed emdvVZz\/bbbaceb4936a8fa205ca78c7b18021e2\" title=\"CodePen Embed emdvVZz\/bbbaceb4936a8fa205ca78c7b18021e2\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">I will skip the creation of the shape, as it\u2019s a basic usage of <code>clip-path<\/code> with a few variables to control the shape.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"496\" height=\"171\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O1VQISTQ.png?resize=496%2C171&#038;ssl=1\" alt=\"A graphic illustrating a horizontal arrow shape labeled 'before' with dimensions of 254.6 x 52 pixels.\" class=\"wp-image-9065\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O1VQISTQ.png?w=496&amp;ssl=1 496w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O1VQISTQ.png?resize=300%2C103&amp;ssl=1 300w\" sizes=\"auto, (max-width: 496px) 100vw, 496px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The next step is to rotate the arrow so that it points to the first circle:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1016\" height=\"382\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O4OoDHhg.png?resize=1016%2C382&#038;ssl=1\" alt=\"Diagram comparing two geometric shapes, with the left side showing a diagonal line between two circles and the right side illustrating dimensions labeled as height, width, and angle.\" class=\"wp-image-9066\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O4OoDHhg.png?w=1016&amp;ssl=1 1016w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O4OoDHhg.png?resize=300%2C113&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/O4OoDHhg.png?resize=768%2C289&amp;ssl=1 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The angle of rotation is equal to the inverse tangent of the height divided by the width, which translates into the following CSS code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">atan<\/span>(100<span class=\"hljs-selector-tag\">cqh<\/span>\/100<span class=\"hljs-selector-tag\">cqw<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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\">Then we adjust the arrow&#8217;s width. We need to make it taller so that it reaches the center of the first circle.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"977\" height=\"373\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/R17aMXK1.png?resize=977%2C373&#038;ssl=1\" alt=\"Comparison of two shapes: left shows a turquoise circle and an orange circle within dashed rectangles, with a diagonal line representing distance; right illustrates height and width measurements from corners of rectangles.\" class=\"wp-image-9067\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/R17aMXK1.png?w=977&amp;ssl=1 977w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/R17aMXK1.png?resize=300%2C115&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/R17aMXK1.png?resize=768%2C293&amp;ssl=1 768w\" sizes=\"auto, (max-width: 977px) 100vw, 977px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The value we are looking for is simply the diagonal we can find using the CSS hypotenuse function.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">width<\/span>: <span class=\"hljs-selector-tag\">hypot<\/span>(100<span class=\"hljs-selector-tag\">cqh<\/span>, 100<span class=\"hljs-selector-tag\">cqw<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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\">Our arrow is now perfect!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_qEaroBQ\/6c6c5795469fd59c8187117269e0ed92\" src=\"\/\/codepen.io\/anon\/embed\/qEaroBQ\/6c6c5795469fd59c8187117269e0ed92?height=550&amp;theme-id=1&amp;slug-hash=qEaroBQ\/6c6c5795469fd59c8187117269e0ed92&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed qEaroBQ\/6c6c5795469fd59c8187117269e0ed92\" title=\"CodePen Embed qEaroBQ\/6c6c5795469fd59c8187117269e0ed92\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Oops, it\u2019s not really perfect. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you drag the circles, you will see a good arrow pointing between both circles, but two others are also visible. Don\u2019t forget that we have four elements for each position and are using four pseudo-elements. The elements can have their size equal to <code>0<\/code>, but since we are defining the size of the pseudo-elements, they will remain visible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To fix this, I will play with their opacity using the following code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">opacity<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">sign<\/span>(1<span class=\"hljs-selector-tag\">cqh<\/span>)*<span class=\"hljs-selector-tag\">sign<\/span>(1<span class=\"hljs-selector-tag\">cqw<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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\">If either the width or the height of the element is <code>0<\/code>, it means that either <code>1cqh<\/code> or <code>1cqw<\/code> will be <code>0<\/code>. <code>sign()<\/code> of <code>0<\/code> is also <code>0<\/code> hence we get <code>opacity: 0<\/code> (invisible).<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XJjMEmq\/3abdcfa60ab86db6d83b0017c3c4b7a5\" src=\"\/\/codepen.io\/anon\/embed\/XJjMEmq\/3abdcfa60ab86db6d83b0017c3c4b7a5?height=550&amp;theme-id=1&amp;slug-hash=XJjMEmq\/3abdcfa60ab86db6d83b0017c3c4b7a5&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XJjMEmq\/3abdcfa60ab86db6d83b0017c3c4b7a5\" title=\"CodePen Embed XJjMEmq\/3abdcfa60ab86db6d83b0017c3c4b7a5\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now we are good. Only one arrow is visible at a time and is perfectly sized and placed!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If we want the arrow to be at the edge of the circles instead of the centers, we can adjust the padding and change the reference of <code>clip-path<\/code> to be content-box<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">padding-inline<\/span>: 85<span class=\"hljs-selector-tag\">px<\/span> 50<span class=\"hljs-selector-tag\">px<\/span>;\n<span class=\"hljs-selector-tag\">clip-path<\/span>: <span class=\"hljs-selector-tag\">polygon<\/span>() <span class=\"hljs-selector-tag\">content-box<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNwqvzo\/25ad2d3582c32de065f45aef3d080ed7\" src=\"\/\/codepen.io\/anon\/embed\/bNwqvzo\/25ad2d3582c32de065f45aef3d080ed7?height=550&amp;theme-id=1&amp;slug-hash=bNwqvzo\/25ad2d3582c32de065f45aef3d080ed7&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNwqvzo\/25ad2d3582c32de065f45aef3d080ed7\" title=\"CodePen Embed bNwqvzo\/25ad2d3582c32de065f45aef3d080ed7\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">If you inspect the pseudo-element, you will notice that the padding is nothing but half the size of the circles. By changing the reference of the <code>clip-path<\/code> to <code>content-box<\/code> I am excluding that area from the shape.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"646\" height=\"336\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/d55_ybqM.png?resize=646%2C336&#038;ssl=1\" alt=\"A graphic illustrating two overlapping circles, one in light blue and one in light orange, connected by a translucent arrow with a directional indicator, alongside a label displaying dimensions.\" class=\"wp-image-9068\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/d55_ybqM.png?w=646&amp;ssl=1 646w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/d55_ybqM.png?resize=300%2C156&amp;ssl=1 300w\" sizes=\"auto, (max-width: 646px) 100vw, 646px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The only drawback here is that I am hardcoding the <code>padding<\/code>, as I don\u2019t have another alternative to get the circle sizes. For this reason, in the original demo, I am adding the sizes as attributes so I can easily control everything from the HTML code. I am doing the same with the anchor names, by the way.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" 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\">\"circle\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"--c1\"<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"170px\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"circle\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"--c2\"<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"100px\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"arrow\"<\/span> <span class=\"hljs-attr\">x<\/span>=<span class=\"hljs-string\">\"--c1\"<\/span> <span class=\"hljs-attr\">y<\/span>=<span class=\"hljs-string\">\"--c2\"<\/span> <span class=\"hljs-attr\">size_x<\/span>=<span class=\"hljs-string\">\"170px\"<\/span> <span class=\"hljs-attr\">size_y<\/span>=<span class=\"hljs-string\">\"100px\"<\/span>&gt;<\/span>\n ...\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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<h2 class=\"wp-block-heading\">Transforming the Arrow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you get back to the initial demo, you will notice that the arrow changes to something else when both circles get closer, and the shape disappears completely when both circles touch each other.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"759\" height=\"261\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/ozZ8IJf3.png?resize=759%2C261&#038;ssl=1\" alt=\"\" class=\"wp-image-9069\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/ozZ8IJf3.png?w=759&amp;ssl=1 759w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/ozZ8IJf3.png?resize=300%2C103&amp;ssl=1 300w\" sizes=\"auto, (max-width: 759px) 100vw, 759px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To achieve this, we need to apply conditions based on the distance between the circles, which also determines the arrow&#8217;s width.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">hypot<\/span>(100<span class=\"hljs-selector-tag\">cqh<\/span>, 100<span class=\"hljs-selector-tag\">cqw<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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\">We create a variable like the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--_m0<\/span>: <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">hypot<\/span>(100<span class=\"hljs-selector-tag\">cqh<\/span>,100<span class=\"hljs-selector-tag\">cqw<\/span>) <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-tag\">value<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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\">The variable will be equal to either <code>1<\/code> if the distance is greater than the value, <code>-1<\/code> if the distance is smaller, and <code>0<\/code> if they are equal. Then we can use <code>if()<\/code> to define two different shapes based on that variable.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">clip-path<\/span>: <span class=\"hljs-selector-tag\">if<\/span>(<span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--_m0<\/span> = 1):\n    <span class=\"hljs-comment\">\/* we draw the arrow *\/<\/span>\n <span class=\"hljs-selector-tag\">else<\/span>:\n    <span class=\"hljs-comment\">\/* we draw another shape *\/<\/span>\n  );<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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 won\u2019t get into the details of the \u201cvalue\u201d or the code of the shapes because it doesn\u2019t really matter. You can view it as a media query with a specific width at which your layout will switch. You create two shapes and decide the distance at which we switch between them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As for the collision detection, we need a precise value, which is half the sum of the circle sizes. So we define another variable:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">--_m1<\/span>: <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">hypot<\/span>(100<span class=\"hljs-selector-tag\">cqh<\/span>,100<span class=\"hljs-selector-tag\">cqw<\/span>) <span class=\"hljs-selector-tag\">-<\/span> (<span class=\"hljs-selector-tag\">Size_c1<\/span> + <span class=\"hljs-selector-tag\">Size_c2<\/span>)\/2);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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\"><code>--_m1<\/code> is either <code>0<\/code> or <code>-1<\/code> when both circles are touching each other, and when it\u2019s the case, we hide the arrow completely. We are already using opacity for a similar thing so we can update it like below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">opacity<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">sign<\/span>(1<span class=\"hljs-selector-tag\">cqw<\/span>)*<span class=\"hljs-selector-tag\">sign<\/span>(1<span class=\"hljs-selector-tag\">cqh<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_m1<\/span>))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><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\">If the element&#8217;s width or height is 0, or the circles overlap, we hide the pseudo-element.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_PwNrNvP\" src=\"\/\/codepen.io\/anon\/embed\/PwNrNvP?height=550&amp;theme-id=1&amp;slug-hash=PwNrNvP&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed PwNrNvP\" title=\"CodePen Embed PwNrNvP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">You have probably noticed that I am using <code>=<\/code> inside <code>if()<\/code> and not <code>:<\/code>. If you are wondering why, read the following post: <a href=\"https:\/\/css-tip.com\/if-trick\/\">The Hidden Trick of Style Queries and if()<\/a>. It\u2019s one of those little tricks that you\u2019d better learn early before getting headaches trying to figure out why your code isn\u2019t working.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Showing the Distance<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I am sure you are thinking this part is going to be tricky, but it\u2019s actually the easiest part. We have used one pseudo-element to create the arrow, and we will use the second one to show the distance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The relevant code is the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.arrow<\/span> <span class=\"hljs-selector-pseudo\">:is(a<\/span>,<span class=\"hljs-selector-tag\">b<\/span>,<span class=\"hljs-selector-tag\">c<\/span>,<span class=\"hljs-selector-tag\">d<\/span>)<span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">counter<\/span>(d);\n  <span class=\"hljs-attribute\">counter-reset<\/span>: d <span class=\"hljs-built_in\">calc<\/span>((hypot(<span class=\"hljs-number\">100<\/span>cqh,<span class=\"hljs-number\">100<\/span>cqw) - value)\/<span class=\"hljs-number\">1px<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><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 am again using the hypotenuse to calculate the diagonal, then I subtract a specific value from it. The result will be a length that we divide by 1px to get an integer. That integer will serve as a reset value for a counter we display within the content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s all. Now the pseudo-element contains the distance value, and we add some styling to position it above the arrow. I will reuse many of the CSS properties I used for the arrow, like opacity to show\/hide it when needed, and rotation so it follows the arrow&#8217;s orientation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As for the \u201cvalue\u201d, it depends on what you want to show. You may decide to show the distance between the center of the circles or the distance between their edge. It doesn\u2019t change the main logic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is again the full demo with all the stuff together:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_wBWWKxP\" src=\"\/\/codepen.io\/anon\/embed\/wBWWKxP?height=550&amp;theme-id=1&amp;slug-hash=wBWWKxP&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed wBWWKxP\" title=\"CodePen Embed wBWWKxP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The final demo includes a few things I didn\u2019t mention, but you should be able to understand them easily. I focused on the main tricks, and everything else is a matter of preference and visual styling.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Graph Theory<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Linking two circles together is good, but can we do more? Of course, we can! It\u2019s pretty straightforward because all you have to do is add as many circles\/arrows you want in the HTML code.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LEZZGxx\" src=\"\/\/codepen.io\/anon\/embed\/LEZZGxx?height=550&amp;theme-id=1&amp;slug-hash=LEZZGxx&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LEZZGxx\" title=\"CodePen Embed LEZZGxx\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Thanks to the use of attributes combined with <code>attr()<\/code>, I don&#8217;t need to touch the CSS, regardless of the number of elements we want to have. I can specify everything on the HTML side:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- we give each circle a name and a size  --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"circle\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"--a\"<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"150px\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"circle\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"--b\"<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"100px\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"circle\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"--c\"<\/span> <span class=\"hljs-attr\">size<\/span>=<span class=\"hljs-string\">\"80px\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!--  each arrow will receive two names and two sizes --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"arrow\"<\/span> <span class=\"hljs-attr\">x<\/span>=<span class=\"hljs-string\">\"--a\"<\/span> <span class=\"hljs-attr\">y<\/span>=<span class=\"hljs-string\">\"--b\"<\/span> <span class=\"hljs-attr\">size_x<\/span>=<span class=\"hljs-string\">\"150px\"<\/span> <span class=\"hljs-attr\">size_y<\/span>=<span class=\"hljs-string\">\"100px\"<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"arrow\"<\/span> <span class=\"hljs-attr\">x<\/span>=<span class=\"hljs-string\">\"--a\"<\/span> <span class=\"hljs-attr\">y<\/span>=<span class=\"hljs-string\">\"--c\"<\/span> <span class=\"hljs-attr\">size_x<\/span>=<span class=\"hljs-string\">\"150px\"<\/span> <span class=\"hljs-attr\">size_y<\/span>=<span class=\"hljs-string\">\"80px\"<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"arrow\"<\/span> <span class=\"hljs-attr\">x<\/span>=<span class=\"hljs-string\">\"--b\"<\/span> <span class=\"hljs-attr\">y<\/span>=<span class=\"hljs-string\">\"--c\"<\/span> <span class=\"hljs-attr\">size_x<\/span>=<span class=\"hljs-string\">\"100px\"<\/span> <span class=\"hljs-attr\">size_y<\/span>=<span class=\"hljs-string\">\"80px\"<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><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\">We go fancier and study <a href=\"https:\/\/css-tip.com\/graph-theory\/\">graph theory<\/a> by implementing a shortest path algorithm!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_YPWMmOP\" src=\"\/\/codepen.io\/anon\/embed\/YPWMmOP?height=550&amp;theme-id=1&amp;slug-hash=YPWMmOP&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed YPWMmOP\" title=\"CodePen Embed YPWMmOP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In this demo, I am relying on a trick I am detailing in a previous article that I highly recommend you read: <a href=\"https:\/\/frontendmasters.com\/blog\/how-to-get-the-width-height-of-any-element-in-only-css\/\">How to Get the Width\/Height of Any Element in Only CSS<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I am using scroll-driven animation to retrieve all the distances between the nodes and make them available at root level. Inside the <code>&lt;arrow&gt;<\/code> element, you will find a new element <code>&lt;e&gt;<\/code> that will serve as my size calculator for each link.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For each link, I will define two variables that contain the width and height (e.g., <code>--sax<\/code> and <code>--say<\/code>). Then, I calculate the diagonal, which is the distance I need (e.g., &#8211;sa). Having all the distances at hand, I calculate the different paths (in our case, four are considered), and we identify the minimum among them. Finally, we conditionally do stuff based on that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is part of the code to see the logic:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-pseudo\">:root<\/span> {\n   <span class=\"hljs-comment\">\/* all the distances *\/<\/span>\n  <span class=\"hljs-attribute\">--sa<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--sax),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--say));\n  <span class=\"hljs-attribute\">--sb<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--sbx),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--sby));\n  <span class=\"hljs-attribute\">--ac<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--acx),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--acy));\n  <span class=\"hljs-attribute\">--ad<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--adx),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--ady));\n  <span class=\"hljs-attribute\">--bc<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--bcx),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--bcy));\n  <span class=\"hljs-attribute\">--bd<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--bdx),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--bdy));\n  <span class=\"hljs-attribute\">--ce<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--cex),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--cey));\n  <span class=\"hljs-attribute\">--de<\/span>: <span class=\"hljs-built_in\">hypot<\/span>(<span class=\"hljs-number\">1<\/span>\/var(--dex),<span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-built_in\">var<\/span>(--dey));\n\n   <span class=\"hljs-comment\">\/* the different paths *\/<\/span>\n  <span class=\"hljs-attribute\">--sace<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--sa) + <span class=\"hljs-built_in\">var<\/span>(--ac) + <span class=\"hljs-built_in\">var<\/span>(--ce));\n  <span class=\"hljs-attribute\">--sade<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--sa) + <span class=\"hljs-built_in\">var<\/span>(--ad) + <span class=\"hljs-built_in\">var<\/span>(--de));\n  <span class=\"hljs-attribute\">--sbce<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--sb) + <span class=\"hljs-built_in\">var<\/span>(--bc) + <span class=\"hljs-built_in\">var<\/span>(--ce));\n  <span class=\"hljs-attribute\">--sbde<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--sb) + <span class=\"hljs-built_in\">var<\/span>(--bd) + <span class=\"hljs-built_in\">var<\/span>(--de));\n  <span class=\"hljs-comment\">\/* we get the min value *\/<\/span>\n  <span class=\"hljs-attribute\">--min<\/span>: <span class=\"hljs-built_in\">min<\/span>(var(--sace),<span class=\"hljs-built_in\">var<\/span>(--sade),<span class=\"hljs-built_in\">var<\/span>(--sbce),<span class=\"hljs-built_in\">var<\/span>(--sbde));\n}\n<span class=\"hljs-keyword\">@container<\/span> style(--sace = var(--min)) {\n  <span class=\"hljs-comment\">\/* if S-A-C-E is the shortest path we style the relevant links *\/<\/span>\n}\n<span class=\"hljs-keyword\">@container<\/span> style(--sade = var(--min)) {\n  <span class=\"hljs-comment\">\/* if S-A-D-E is the shortest path we style the relevant links *\/<\/span>\n}\n<span class=\"hljs-keyword\">@container<\/span> style(--sbce = var(--min)) {\n  <span class=\"hljs-comment\">\/* if S-B-C-E is the shortest path we style the relevant links *\/<\/span>\n}\n<span class=\"hljs-keyword\">@container<\/span> style(--sbde = var(--min)) {\n  <span class=\"hljs-comment\">\/* if S-B-D-E is the shortest path we style the relevant links *\/<\/span>\n}\n<span class=\"hljs-comment\">\/* the pseudo element of the header will show the shortest path and the distance *\/<\/span>\n<span class=\"hljs-selector-tag\">h3<\/span><span class=\"hljs-selector-pseudo\">::after<\/span> {\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">if<\/span>(\n    style(--sace = var(--min)):<span class=\"hljs-string\">\"S-A-C-E\"<\/span>;\n    style(--sade = var(--min)):\"S-A-D-E\";\n    style(--sbce = var(--min)):\"S-B-C-E\";\n    style(--sbde = var(--min)):\"S-B-D-E\";\n  )  \" (\" counter(d)  \")\";\n  <span class=\"hljs-attribute\">counter-reset<\/span>: d <span class=\"hljs-built_in\">round<\/span>(var(--min));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><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\">The implementation is, of course, tailored to a particular HTML structure, and we have to adjust the CSS if we want another graph, but it\u2019s more of a proof of concept and a demo to illustrate that it\u2019s indeed possible. In the future, we may have more features that allow us to have a more dynamic implementation that works with any HTML code (loops in CSS, maybe?)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It was too much for CSS, right? I agree, and again, the main goal was not to create those demos and use them everywhere. It was more about the features and tricks we have learned that will be useful in other situations. We covered a lot of anchor positioning stuff, container queries, <code>if()<\/code> conditions, various calculations, <code>attr()<\/code>, etc.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Drawing an line with arrows pointing to the center of two arbitrary elements measuring and displaying the distance between them doesn&#8217;t seem like it would be possible in CSS alone&#8230; but&#8230; <\/p>\n","protected":false},"author":12,"featured_media":9046,"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_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[121,7,468,467],"class_list":["post-9033","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-anchor","tag-css","tag-geometry","tag-math"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/03\/distance-css.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=9033"}],"version-history":[{"count":18,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9033\/revisions"}],"predecessor-version":[{"id":9093,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9033\/revisions\/9093"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9046"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}