Archive

Author Archive

Positioning Text Around Elements With CSS Offset

January 24th, 2025 No comments

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%; }
CodePen Embed Fallback

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%; }
  }
}
CodePen Embed Fallback

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;
  */
}
CodePen Embed Fallback

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;
}
CodePen Embed Fallback

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%;  }
}
CodePen Embed Fallback

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


Positioning Text Around Elements With CSS Offset originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

When DEI Fails: Is Diversity Just a Marketing Gimmick?

January 24th, 2025 No comments

DEI initiatives promise transformative change in creative industries but often devolve into performative actions that undermine trust and stifle innovation.

Categories: Designing, Others Tags:

Dropbox Unveils New Brand Identity Microsite

January 23rd, 2025 No comments

Dropbox has introduced a redesigned Brand Guidelines microsite, providing a comprehensive and interactive resource to explore its visual and verbal identity. Featuring tools like a variable type explorer, an icon slot machine, and detailed guidelines on color, typography, and more…

Categories: Designing, Others Tags:

Pinterest Unveils the 2025 Color Palette

January 22nd, 2025 No comments

Pinterest’s 2025 Color Palette introduces a vibrant mix of shades, including Butter Yellow, Cherry Red, and Dill Green, reflecting cultural trends and inspiring innovation across design fields. From branding and graphic design to interior spaces, this curated palette offers endless creative possibilities for designers.

Categories: Designing, Others Tags:

Product Hunt Launches 2024 Golden Kitty Awards

January 21st, 2025 No comments

Product Hunt has opened voting for the 2024 Golden Kitty Awards, celebrating the year’s most innovative products across 17 categories, including AI, design tools, and climate tech. Winners, determined by community votes, will receive the iconic Golden Kitty trophy and recognition for their groundbreaking contributions.

Categories: Designing, Others Tags:

Creating a “Starred” Feed

January 21st, 2025 No comments
Link on 1/6/2025

Chris wrote about “Likes” pages a long while back. The idea is rather simple: “Like” an item in your RSS reader and display it in a feed of other liked items. The little example Chris made is still really good.

CodePen Embed Fallback

There were two things Chris noted at the time. One was that he used a public CORS proxy that he wouldn’t use in a production environment. Good idea to nix that, security and all. The other was that he’d consider using WordPress transients to fetch and cache the data to work around CORS.

I decided to do that! The result is this WordPress block I can drop right in here. I’ll plop it in a

to keep things brief.

Open Starred Feed
Link on 1/15/2025

Learning HTML is the best investment I ever did

One of the running jokes and/or discussion I am sick and tired of is people belittling HTML. Yes, HTML is not a programming language. No, HTML should not just be a compilation target. Learning HTML is a solid investment and not hard to do.

I am not…

Link on 1/12/2025

Gotchas in Naming CSS View Transitions

I’m playing with making cross-document view transitions work on this blog.

Nothing fancy. Mostly copying how Dave Rupert does it on his site where you get a cross-fade animation on the whole page generally, and a little position animation on the page title specifically.

Link on 1/6/2025

The :empty pseudo-class

We can use the :empty pseudo-class as a way to style elements on your webpage that are empty.

You might wonder why you’d want to style something that’s empty. Let’s say you’re creating a todo list.

You want to put your todo items in a list, but what about when you don’t…

Link on 1/8/2025

CSS Wish List 2025

Back in 2023, I belatedly jumped on the bandwagon of people posting their CSS wish lists for the coming year.  This year I’m doing all that again, less belatedly! (I didn’t do it last year because I couldn’t even.  Get it?)

I started this post by looking at what I…

Link on 1/9/2025

aria-description Does Not Translate

It does, actually. In Firefox. Sometimes.

A major risk of using ARIA to define text content is it typically gets overlooked in translation. Automated translation services often do not capture it. Those who pay for localization services frequently miss content in ARIA attributes when sending text strings to localization vendors.

Content buried…

It’s a little different. For one, I’m only fetching 10 items at a time. We could push that to infinity but that comes with a performance tax, not to mention I have no way of organizing the items for them to be grouped and filtered. Maybe that’ll be a future enhancement!

The Chris demo provided the bones and it does most of the heavy lifting. The “tough” parts were square-pegging the thing into a WordPress block architecture and then getting transients going. This is my first time working with transients, so I thought I’d share the relevant code and pick it apart.

function fetch_and_store_data() {
  $transient_key = 'fetched_data';
  $cached_data = get_transient($transient_key);

  if ($cached_data) {
    return new WP_REST_Response($cached_data, 200);
  }

  $response = wp_remote_get('https://feedbin.com/starred/a22c4101980b055d688e90512b083e8d.xml');
  if (is_wp_error($response)) {
    return new WP_REST_Response('Error fetching data', 500);
  }

  $body = wp_remote_retrieve_body($response);
  $data = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA);
  $json_data = json_encode($data);
  $array_data = json_decode($json_data, true);

  $items = [];
  foreach ($array_data['channel']['item'] as $item) {
    $items[] = [
      'title' => $item['title'],
      'link' => $item['link'],
      'pubDate' => $item['pubDate'],
      'description' => $item['description'],
    ];
  }

  set_transient($transient_key, $items, 12 * HOUR_IN_SECONDS);

  return new WP_REST_Response($items, 200);
}

add_action('rest_api_init', function () {
  register_rest_route('custom/v1', '/fetch-data', [
    'methods' => 'GET',
    'callback' => 'fetch_and_store_data',
  ]);
});

Could this be refactored and written more efficiently? All signs point to yes. But here’s how I grokked it:

function fetch_and_store_data() {

}

The function’s name can be anything. Naming is hard. The first two variables:

$transient_key = 'fetched_data';
$cached_data = get_transient($transient_key);

The $transient_key is simply a name that identifies the transient when we set it and get it. In fact, the $cached_data is the getter so that part’s done. Check!

I only want the $cached_data if it exists, so there’s a check for that:

if ($cached_data) {
  return new WP_REST_Response($cached_data, 200);
}

This also establishes a new response from the WordPress REST API, which is where the data is cached. Rather than pull the data directly from Feedbin, I’m pulling it and caching it in the REST API. This way, CORS is no longer an issue being that the starred items are now locally stored on my own domain. That’s where the wp_remote_get() function comes in to form that response from Feedbin as the origin:

$response = wp_remote_get('https://feedbin.com/starred/a22c4101980b055d688e90512b083e8d.xml');

Similarly, I decided to throw an error if there’s no $response. That means there’s no freshly $cached_data and that’s something I want to know right away.

if (is_wp_error($response)) {
  return new WP_REST_Response('Error fetching data', 500);
}

The bulk of the work is merely parsing the XML data I get back from Feedbin to JSON. This scours the XML and loops through each item to get its title, link, publish date, and description:

$body = wp_remote_retrieve_body($response);
$data = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA);
$json_data = json_encode($data);
$array_data = json_decode($json_data, true);

$items = [];
foreach ($array_data['channel']['item'] as $item) {
  $items[] = [
    'title' => $item['title'],
    'link' => $item['link'],
    'pubDate' => $item['pubDate'],
    'description' => $item['description'],
  ];
}

“Description” is a loaded term. It could be the full body of a post or an excerpt — we don’t know until we get it! So, I’m splicing and trimming it in the block’s Edit component to stub it at no more than 50 words. There’s a little risk there because I’m rendering the HTML I get back from the API. Security, yes. But there’s also the chance I render an open tag without its closing counterpart, muffing up my layout. I know there are libraries to address that but I’m keeping things simple for now.

Now it’s time to set the transient once things have been fetched and parsed:

set_transient($transient_key, $items, 12 * HOUR_IN_SECONDS);

The WordPress docs are great at explaining the set_transient() function. It takes three arguments, the first being the $transient_key that was named earlier to identify which transient is getting set. The other two:

  • $value: This is the object we’re storing in the named transient. That’s the $items object handling all the parsing.
  • $expiration: How long should this transient last? It wouldn’t be transient if it lingered around forever, so we set an amount of time expressed in seconds. Mine lingers for 12 hours before it expires and then updates the next time a visitor hits the page.

OK, time to return the items from the REST API as a new response:

return new WP_REST_Response($items, 200);

That’s it! Well, at least for setting and getting the transient. The next thing I realized I needed was a custom REST API endpoint to call the data. I really had to lean on the WordPress docs to get this going:

add_action('rest_api_init', function () {
  register_rest_route('custom/v1', '/fetch-data', [
    'methods' => 'GET',
    'callback' => 'fetch_and_store_data',
  ]);
});

That’s where I struggled most and felt like this all took wayyyyy too much time. Well, that and sparring with the block itself. I find it super hard to get the front and back end components to sync up and, honestly, a lot of that code looks super redundant if you were to scope it out. That’s another story altogether.

Enjoy reading what we’re reading! I put a page together that pulls in the 10 most recent items with a link to subscribe to the full feed.


Creating a “Starred” Feed originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

The Iconic Visual Design of Squid Game: Lessons for Web Designers

January 21st, 2025 No comments

Squid Game’s visual design is a masterclass in leveraging bold colors, minimalism, geometry, contrast, and mystery to evoke emotion and captivate audiences. For web designers, it offers powerful lessons on purposeful design that prioritizes storytelling and emotional impact over safe, conventional choices.

Categories: Designing, Others Tags:

3 Essential Design Trends, January 2025

January 20th, 2025 No comments

From circles to background text elements to high-drama imagery, 2025 is off to a great start with these website design trends.

Categories: Designing, Others Tags:

Fancy Menu Navigation Using Anchor Positioning

January 17th, 2025 No comments

You have for sure heard about the new CSS Anchor Positioning, right? It’s a feature that allows you to link any element from the page to another one, i.e., the anchor. It’s useful for all the tooltip stuff, but it can also create a lot of other nice effects.

In this article, we will study menu navigation where I rely on anchor positioning to create a nice hover effect on links.

CodePen Embed Fallback

Cool, right? We have a sliding effect where the blue rectangle adjusts to fit perfectly with the text content over a nice transition. If you are new to anchor positioning, this example is perfect for you because it’s simple and allows you to discover the basics of this new feature. We will also study another example so stay until the end!

Note that only Chromium-based browsers fully support anchor positioning at the time I’m writing this. You’ll want to view the demos in a browser like Chrome or Edge until the feature is more widely supported in other browsers.

The initial configuration

Let’s start with the HTML structure which is nothing but a nav element containing an unordered list of links:

<nav>
  <ul>
    <li><a href="#">Home</a></li>
    <li class="active"><a href="#">About</a></li>
    <li><a href="#">Projects</a></li>
    <li><a href="#">Blog</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

We will not spend too much time explaining this structure because it can be different if your use case is different. Simply ensure the semantic is relevant to what you are trying to do. As for the CSS part, we will start with some basic styling to create a horizontal menu navigation.

ul {
  padding: 0;
  margin: 0;
  list-style: none;
  display: flex;
  gap: .5rem;
  font-size: 2.2rem;
}

ul li a {
  color: #000;
  text-decoration: none;
  font-weight: 900;
  line-height: 1.5;
  padding-inline: .2em;
  display: block;
}

Nothing fancy so far. We remove some default styling and use Flexbox to align the elements horizontally.

CodePen Embed Fallback

Sliding effect

First off, let’s understand how the effect works. At first glance, it looks like we have one rectangle that shrinks to a small height, moves to the hovered element, and then grows to full height. That’s the visual effect, but in reality, more than one element is involved!

Here is the first demo where I am using different colors to better see what is happening.

CodePen Embed Fallback

Each menu item has its own “element” that shrinks or grows. Then we have a common “element” (the one in red) that slides between the different menu items. The first effect is done using a background animation and the second one is where anchor positioning comes into play!

The background animation

We will animate the height of a CSS gradient for this first part:

/* 1 */
ul li {
  background: 
    conic-gradient(lightblue 0 0)
    bottom/100% 0% no-repeat;
  transition: .2s;
}

/* 2 */
ul li:is(:hover,.active) {
  background-size: 100% 100%;
  transition: .2s .2s;
}

/* 3 */
ul:has(li:hover) li.active:not(:hover) {
  background-size: 100% 0%;
  transition: .2s;
}

We’ve defined a gradient with a 100% width and 0% height, placed at the bottom. The gradient syntax may look strange, but it’s the shortest one that allows me to have a single-color gradient.

Related: “How to correctly define a one-color gradient”

Then, if the menu item is hovered or has the .active class, we make the height equal to 100%. Note the use of the delay here to make sure the growing happens after the shrinking.

Finally, we need to handle a special case with the .active item. If we hover any item (that is not the active one), then the .active item gets the shirking effect (the gradient height is equal to 0%). That’s the purpose of the third selector in the code.

CodePen Embed Fallback

Our first animation is done! Notice how the growing begins after the shrinking completes because of the delay we defined in the second selector.

The anchor positioning animation

The first animation was quite easy because each item had its own background animation, meaning we didn’t have to care about the text content since the background automatically fills the whole space.

We will use one element for the second animation that slides between all the menu items while adapting its width to fit the text of each item. This is where anchor positioning can help us.

Let’s start with the following code:

ul:before {
  content:"";
  position: absolute;
  position-anchor: --li;
  background: red;
  transition: .2s;
}

ul li:is(:hover, .active) {
  anchor-name: --li;
}

ul:has(li:hover) li.active:not(:hover) {
  anchor-name: none;
}

To avoid adding an extra element, I will prefer using a pseudo-element on the ul. It should be absolutely-positioned and we will rely on two properties to activate the anchor positioning.

We define the anchor with the anchor-name property. When a menu item is hovered or has the .active class, it becomes the anchor element. We also have to remove the anchor from the .active item if another item is in a hovered state (hence, the last selector in the code). In other words, only one anchor is defined at a time.

Then we use the position-anchor property to link the pseudo-element to the anchor. Notice how both use the same notation --li. It’s similar to how, for example, we define @keyframes with a specific name and later use it inside an animation property. Keep in mind that you have to use the syntax, meaning the name must always start with two dashes (--).

CodePen Embed Fallback

The pseudo-element is correctly placed but nothing is visible because we didn’t define any dimension! Let’s add the following code:

ul:before {
  bottom: anchor(bottom);
  left: anchor(left);
  right: anchor(right);
  height: .2em;  
}

The height property is trivial but the anchor() is a newcomer. Here’s how Juan Diego describes it in the Almanac:

The CSS anchor() function takes an anchor element’s side and resolves to the where it is positioned. It can only be used in inset properties (e.g. top, bottom, bottom, left, right, etc.), normally to place an absolute-positioned element relative to an anchor.

Let’s check the MDN page as well:

The anchor() CSS function can be used within an anchor-positioned element’s inset property values, returning a length value relative to the position of the edges of its associated anchor element.

Usually, we use left: 0 to place an absolute element at the left edge of its containing block (i.e., the nearest ancestor having position: relative). The left: anchor(left) will do the same but instead of the containing block, it will consider the associated anchor element.

That’s all — we are done! Hover the menu items in the below demo and see how the pseudo-element slides between them.

CodePen Embed Fallback

Each time you hover over a menu item it becomes the new anchor for the pseudo-element (the ul:before). This also means that the anchor(...) values will change creating the sliding effect! Let’s not forget the use of the transition which is important otherwise, we will have an abrupt change.

We can also write the code differently like this:

ul:before {
  content:"";
  position: absolute;
  inset: auto anchor(right, --li) anchor(bottom, --li) anchor(left, --li);
  height: .2em;  
  background: red;
  transition: .2s;
}

In other words, we can rely on the inset shorthand instead of using physical properties like left, right, and bottom, and instead of defining position-anchor, we can include the anchor’s name inside the anchor() function. We are repeating the same name three times which is probably not optimal here but in some situations, you may want your element to consider multiple anchors, and in such cases, this syntax will make sense.

Combining both effects

Now, we combine both effects and, tada, the illusion is perfect!

CodePen Embed Fallback

Pay attention to the transition values where the delay is important:

ul:before {
  transition: .2s .2s;
}

ul li {
  transition: .2s;
}

ul li:is(:hover,.active) {
  transition: .2s .4s;
}

ul:has(li:hover) li.active:not(:hover) {
  transition: .2s;
}

We have a sequence of three animations — shrink the height of the gradient, slide the pseudo-element, and grow the height of the gradient — so we need to have delays between them to pull everything together. That’s why for the sliding of the pseudo-element we have a delay equal to the duration of one animation (transition: .2 .2s) and for the growing part the delay is equal to twice the duration (transition: .2s .4s).

Bouncy effect? Why not?!

Let’s try another fancy animation in which the highlight rectangle morphs into a small circle, jumps to the next item, and transforms back into a rectangle again!

CodePen Embed Fallback

I won’t explain too much for this example as it’s your homework to dissect the code! I’ll offer a few hints so you can unpack what’s happening.

Like the previous effect, we have a combination of two animations. For the first one, I will use the pseudo-element of each menu item where I will adjust the dimension and the border-radius to simulate the morphing. For the second animation, I will use the ul pseudo-element to create a small circle that I move between the menu items.

Here is another version of the demo with different coloration and a slower transition to better visualize each animation:

CodePen Embed Fallback

The tricky part is the jumping effect where I am using a strange cubic-bezier() but I have a detailed article where I explain the technique in my CSS-Tricks article “Advanced CSS Animation Using cubic-bezier().

Conclusion

I hope you enjoyed this little experimentation using the anchor positioning feature. We only looked at three properties/values but it’s enough to prepare you for this new feature. The anchor-name and position-anchor properties are the mandatory pieces for linking one element (often called a “target” element in this context) to another element (what we call an “anchor” element in this context). From there, you have the anchor() function to control the position.

Related: CSS Anchor Positioning Guide


Fancy Menu Navigation Using Anchor Positioning originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

The Dark Side of Reddit: How Communities Are Turning Toxic

January 17th, 2025 No comments

Reddit faces growing toxicity, with issues like hate speech, misinformation, and radicalization exacerbated by its anonymity and algorithms. The platform must balance free speech with effective moderation to address these challenges.

Categories: Designing, Others Tags: