{"id":9632,"date":"2026-05-13T08:27:06","date_gmt":"2026-05-13T13:27:06","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9632"},"modified":"2026-05-13T08:27:07","modified_gmt":"2026-05-13T13:27:07","slug":"callout-ui-with-css-offset-border","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/callout-ui-with-css-offset-border\/","title":{"rendered":"Callout UI with CSS Offset &amp; Border"},"content":{"rendered":"\n<p>A callout UI typically has a leader line (or &#8220;tail&#8221;) with a text box at one end. It is often used as a visual highlight and to add annotations in a casual layout. Let&#8217;s look at one way to design a callout like this using CSS <code>offset<\/code> and borders.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_yyaQEKx\" src=\"\/\/codepen.io\/anon\/embed\/yyaQEKx?height=650&amp;theme-id=1&amp;slug-hash=yyaQEKx&amp;default-tab=result\" height=\"650\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed yyaQEKx\" title=\"CodePen Embed yyaQEKx\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-layout\">The Layout<\/h2>\n\n\n\n<p>The HTML layout consists of an element that represents the callout, inside which is another element that\u2019ll carry the text.<\/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\">\"callout\"<\/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\">\"callout-text\"<\/span>&gt;<\/span><span class=\"hljs-comment\">&lt;!-- text --&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>&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<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\">.callout<\/span> {\n  <span class=\"hljs-attribute\">container-type<\/span>: size;\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>The outer element,\u00a0<code>.callout<\/code>, is established as a\u00a0<em>query container<\/em>\u00a0that tracks both its horizontal and vertical sizes (that&#8217;s what the value <code>size<\/code> does as opposed to <code>inline-size<\/code>). This later gives us the dimensions needed to place the text boxes at the desired positions over\u00a0<code>.callout<\/code>. The element will also receive borders later on to create the leader lines.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-text-box-offset\">The Text Box Offset<\/h2>\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\">.callout-text<\/span> {\n  <span class=\"hljs-attribute\">offset-path<\/span>: border-box;\n  <span class=\"hljs-attribute\">offset-anchor<\/span>: bottom;\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>The text boxes (<code>.callout-text<\/code>) are to be placed along the border reference box of&nbsp;<code>.callout<\/code>. The text box\u2019s bottom-center (center is default) is the point that attaches to the&nbsp;<code>.callout<\/code>\u2019s border.<\/p>\n\n\n\n<p class=\"learn-more\">The&nbsp;<code>offset-path<\/code>&nbsp;CSS property defines a track that an element can be placed on and animated along. The&nbsp;<code>offset-anchor<\/code>&nbsp;CSS property defines a point in that element that connects to the path.<\/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\">.callout-text<\/span> {\n  <span class=\"hljs-attribute\">offset-path<\/span>: border-box;\n  <span class=\"hljs-attribute\">offset-anchor<\/span>: bottom;\n  <span class=\"hljs-attribute\">offset-distance<\/span>: <span class=\"hljs-number\">100<\/span>cqw;\n  <span class=\"hljs-comment\">\/* shorthand would be: \n     offset: border-box 100cqw 0deg\/bottom; *\/<\/span>\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>If the leader line needs to extend from below the text box and towards the left, the text box needs to be at the opposite corner (the top right) of\u00a0<code>.callout<\/code>, which is the distance of the entire callout width:\u00a0<code>100cqw<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-leader-line\">The Leader Line<\/h2>\n\n\n\n<p>With the text box positioned, we now add a leader line. This is done by setting the right and bottom borders for\u00a0<code>.callout<\/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-class\">.callout<\/span> {\n  <span class=\"hljs-attribute\">container-type<\/span>: size;\n  <span class=\"hljs-attribute\">border<\/span>: dashed;\n  <span class=\"hljs-attribute\">border-width<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">2px<\/span> <span class=\"hljs-number\">2px<\/span> <span class=\"hljs-number\">0<\/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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_jEVqwgz\" src=\"\/\/codepen.io\/anon\/embed\/jEVqwgz?height=550&amp;theme-id=1&amp;slug-hash=jEVqwgz&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed jEVqwgz\" title=\"CodePen Embed jEVqwgz\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>A standard callout is ready, but let\u2019s tweak it a little more!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-designs\">The Designs<\/h2>\n\n\n\n<p>One <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">advantage of positioning text boxes using offset is that\u00a0<strong>the text box is now\u00a0<\/strong><\/span><strong>attached to the callout\u2019s border<\/strong>. If the border moves in any way, the text box moves along with it.<\/p>\n\n\n\n<p>To make the leader line slanted, we add skew to\u00a0<code>.callout<\/code>. To counter the effect of skew on the text box, it receives the same skew but in the opposite direction.<\/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\">.callout<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">--ang<\/span>: <span class=\"hljs-number\">20deg<\/span>;\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">skewX<\/span>(calc(-<span class=\"hljs-number\">1<\/span> * var(--ang)));\n}\n<span class=\"hljs-selector-class\">.callout-text<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">skewX<\/span>(var(--ang));\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_raWewQq\" src=\"\/\/codepen.io\/anon\/embed\/raWewQq?height=450&amp;theme-id=1&amp;slug-hash=raWewQq&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raWewQq\" title=\"CodePen Embed raWewQq\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Box shadows can also be used to provide leader lines.<\/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-class\">.callout<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">box-shadow<\/span>: <span class=\"hljs-number\">1px<\/span> -<span class=\"hljs-number\">1px<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">1px<\/span> maroon, \n              <span class=\"hljs-number\">2px<\/span> -<span class=\"hljs-number\">2px<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">2px<\/span> pink, \n              <span class=\"hljs-number\">3px<\/span> -<span class=\"hljs-number\">3px<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">3px<\/span> deeppink;\n}<\/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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_azBNwQM\" src=\"\/\/codepen.io\/anon\/embed\/azBNwQM?height=450&amp;theme-id=1&amp;slug-hash=azBNwQM&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed azBNwQM\" title=\"CodePen Embed azBNwQM\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can also take advantage of\u00a0<code>border-radius<\/code>\u00a0and\u00a0<code>corner-shape<\/code>\u00a0to affect the leader line&#8217;s shape.<\/p>\n\n\n\n<p class=\"learn-more\">The <code>corner-shape<\/code>\u00a0fallback is the default rounded border.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.callout<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">border-bottom-right-radius<\/span>: <span class=\"hljs-number\">30px<\/span>;\n  <span class=\"hljs-attribute\">corner-shape<\/span>: notch;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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_dPOMRwy\" src=\"\/\/codepen.io\/anon\/embed\/dPOMRwy?height=450&amp;theme-id=1&amp;slug-hash=dPOMRwy&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dPOMRwy\" title=\"CodePen Embed dPOMRwy\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This can all be animated, too.<\/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\">.callout<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">--ang<\/span>: <span class=\"hljs-number\">20deg<\/span>;\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">scaleY<\/span>(<span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-attribute\">transform-origin<\/span>: bottom left;\n}\n<span class=\"hljs-selector-class\">.callout-text<\/span> {\n  <span class=\"hljs-comment\">\/* etc. *\/<\/span>\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">scale<\/span>(<span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-attribute\">transform-origin<\/span>: bottom center;\n}\n<span class=\"hljs-selector-tag\">p<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> + <span class=\"hljs-selector-class\">.callout<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">skewX<\/span>(calc(-<span class=\"hljs-number\">1<\/span> * var(--ang))) <span class=\"hljs-built_in\">scaleY<\/span>(<span class=\"hljs-number\">1<\/span>);\n  <span class=\"hljs-attribute\">transition<\/span>: transform <span class=\"hljs-number\">0.3s<\/span>;\n}\n<span class=\"hljs-selector-tag\">p<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> + <span class=\"hljs-selector-class\">.callout<\/span> &gt; <span class=\"hljs-selector-class\">.callout-text<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">scale<\/span>(<span class=\"hljs-number\">1<\/span>) <span class=\"hljs-built_in\">skewX<\/span>(var(--ang));\n  <span class=\"hljs-attribute\">transition<\/span>: <span class=\"hljs-number\">0.3s<\/span> transform <span class=\"hljs-number\">0.3s<\/span>;\n}<\/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>First, the&nbsp;<code>.callout<\/code>&nbsp;scales up vertically, followed by the&nbsp;<code>.callout-text<\/code>&nbsp;scaling up in both axes.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_raWewoq\" src=\"\/\/codepen.io\/anon\/embed\/raWewoq?height=450&amp;theme-id=1&amp;slug-hash=raWewoq&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raWewoq\" title=\"CodePen Embed raWewoq\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The design variants can be many, arising from different combinations of these. In a nutshell, we use borders and shadows for the leader line&#8217;s type and color; use sizing, transforms, <code>border-radius<\/code>, etc., to change the line&#8217;s size and shape. Then style the text box as we would any element showcasing text.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We look at designing callout UI elements using CSS, incorporating leader lines and text boxes. It details setting up the HTML structure, utilizing CSS properties like offset-path and borders.<\/p>\n","protected":false},"author":20,"featured_media":9636,"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":[355,7,353],"class_list":["post-9632","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-corner-shape","tag-css","tag-offset-path"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/callouts.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9632","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\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=9632"}],"version-history":[{"count":5,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9632\/revisions"}],"predecessor-version":[{"id":9641,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9632\/revisions\/9641"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9636"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9632"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9632"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9632"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}