Positioning Text Around Elements With CSS Offset
When it comes to positioning elements on a page, including text, there are many ways to go about it in CSS — the literal position
property with corresponding inset-*
properties, translate
, margin
, anchor()
(limited browser support at the moment), and so forth. The offset
property is another one that belongs in that list.
The offset
property is typically used for animating an element along a predetermined path. For instance, the square in the following example traverses a circular path:
<div class="circle">
<div class="square"></div>
</div>
@property --p {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.square {
offset: top 50% right 50% circle(50%) var(--p);
transition: --p 1s linear;
/* Equivalent to:
offset-position: top 50% right 50%;
offset-path: circle(50%);
offset-distance: var(--p); */
/* etc. */
}
.circle:hover .square{ --p: 100%; }
A registered CSS custom property (--p
) is used to set and animate the offset distance of the square element. The animation is possible because an element can be positioned at any point in a given path using offset
. and maybe you didn’t know this, but offset
is a shorthand property comprised of the following constituent properties:
-
offset-position
: The path’s starting point -
offset-path
: The shape along which the element can be moved -
offset-distance
: A distance along the path on which the element is moved -
offset-rotate
: The rotation angle of an element relative to its anchor point and offset path -
offset-anchor
: A position within the element that’s aligned to the path
The offset-path
property is the one that’s important to what we’re trying to achieve. It accepts a shape value — including SVG shapes or CSS shape functions — as well as reference boxes of the containing element to create the path.
Reference boxes? Those are an element’s dimensions according to the CSS Box Model, including content-box
, padding-box
, border-box
, as well as SVG contexts, such as the view-box
, fill-box
, and stroke-box
. These simplify how we position elements along the edges of their containing elements. Here’s an example: all the small squares below are placed in the default top-left corner of their containing elements’ content-box
. In contrast, the small circles are positioned along the top-right corner (25%
into their containing elements’ square perimeter) of the content-box
, border-box
, and padding-box
, respectively.
<div class="big">
<div class="small circle"></div>
<div class="small square"></div>
<p>She sells sea shells by the seashore</p>
</div>
<div class="big">
<div class="small circle"></div>
<div class="small square"></div>
<p>She sells sea shells by the seashore</p>
</div>
<div class="big">
<div class="small circle"></div>
<div class="small square"></div>
<p>She sells sea shells by the seashore</p>
</div>
.small {
/* etc. */
position: absolute;
&.square {
offset: content-box;
border-radius: 4px;
}
&.circle { border-radius: 50%; }
}
.big {
/* etc. */
contain: layout; /* (or position: relative) */
&:nth-of-type(1) {
.circle { offset: content-box 25%; }
}
&:nth-of-type(2) {
border: 20px solid rgb(170 232 251);
.circle { offset: border-box 25%; }
}
&:nth-of-type(3) {
padding: 20px;
.circle { offset: padding-box 25%; }
}
}
Note: You can separate the element’s offset-positioned layout context if you don’t want to allocated space for it inside its containing parent element. That’s how I’ve approached it in the example above so that the paragraph text inside can sit flush against the edges. As a result, the offset positioned elements (small squares and circles) are given their own contexts using position: absolute
, which removes them from the normal document flow.
This method, positioning relative to reference boxes, makes it easy to place elements like notification dots and ornamental ribbon tips along the periphery of some UI module. It further simplifies the placement of texts along a containing block’s edges, as offset
can also rotate elements along the path, thanks to offset-rotate
. A simple example shows the date of an article placed at a block’s right edge:
<article>
<h1>The Irreplaceable Value of Human Decision-Making in the Age of AI</h1>
<!-- paragraphs -->
<div class="date">Published on 11<sup>th</sup> Dec</div>
<cite>An excerpt from the HBR article</cite>
</article>
article {
container-type: inline-size;
/* etc. */
}
.date {
offset: padding-box 100cqw 90deg / left 0 bottom -10px;
/*
Equivalent to:
offset-path: padding-box;
offset-distance: 100cqw; (100% of the container element's width)
offset-rotate: 90deg;
offset-anchor: left 0 bottom -10px;
*/
}
As we just saw, using the offset
property with a reference box path and container units is even more efficient — you can easily set the offset distance based on the containing element’s width or height. I’ll include a reference for learning more about container queries and container query units in the “Further Reading” section at the end of this article.
There’s also the offset-anchor
property that’s used in that last example. It provides the anchor for the element’s displacement and rotation — for instance, the 90 degree rotation in the example happens from the element’s bottom-left corner. The offset-anchor
property can also be used to move the element either inward or outward from the reference box by adjusting inset-*
values — for instance, the bottom -10px
arguments pull the element’s bottom edge outwards from its containing element’s padding-box
. This enhances the precision of placements, also demonstrated below.
<figure>
<div class="big">4</div>
<div class="small">number four</div>
</figure>
.small {
width: max-content;
offset: content-box 90% -54deg / center -3rem;
/*
Equivalent to:
offset-path: content-box;
offset-distance: 90%;
offset-rotate: -54deg;
offset-anchor: center -3rem;
*/
font-size: 1.5rem;
color: navy;
}
As shown at the beginning of the article, offset positioning is animateable, which allows for dynamic design effects, like this:
<article>
<figure>
<div class="small one">17<sup>th</sup> Jan. 2025</div>
<span class="big">Seminar<br>on<br>Literature</span>
<div class="small two">Tickets Available</div>
</figure>
</article>
@property --d {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.small {
/* other style rules */
offset: content-box var(--d) 0deg / left center;
/*
Equivalent to:
offset-path: content-box;
offset-distance: var(--d);
offset-rotate: 0deg;
offset-anchor: left center;
*/
transition: --d .2s linear;
&.one { --d: 2%; }
&.two { --d: 70%; }
}
article:hover figure {
.one { --d: 15%; }
.two { --d: 80%; }
}
Wrapping up
Whether for graphic designs like text along borders, textual annotations, or even dynamic texts like error messaging, CSS offset is an easy-to-use option to achieve all of that. We can position the elements along the reference boxes of their containing parent elements, rotate them, and even add animation if needed.
Further reading
-
The CSS
offset-path
property: CSS-Tricks, MDN -
The CSS
offset-anchor
property: CSS-Tricks, MDN - Container query length units: CSS-Tricks, MDN
-
The
@property
at-rule: CSS-Tricks, web.dev - The CSS Box Model: CSS-Tricks
- SVG Reference Boxes: W3C
Positioning Text Around Elements With CSS Offset originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.