In the challenge I was stuck with a suboptimal solution to little decorative dots. If you haven’t already read that, do that first. I’ll wait.
Right, you’re up to speed, let’s dig in.
A call to armspermalink
I was really impressed with the efforts folks contributed to this challenge, so I’ll list a couple of my favourites.
- Kevin had a good effort using grid and
fit-content
- Ryan had a go with an alternative grid approach
- Gary tried getting absolute position to work out
Thank you, as always, to the folks who contributed.
The secret is in the newest CSSpermalink
Roma linked me up with an article about the shrink-wrap problem they wrote. The solution was right there, in these further examples.
It’s a really clever solution because the anchor positioning system allows us to stick the start or the end of the dots to each side of the text, providing that much sought-after effect that I was after in the original challenge.
I’ve not read up much anchor position stuff quite yet because in my mind, it was a popover thing. How wrong was I?!
I riffed off Roma’s approach for the solution to this Front-End Challenges Clubpermalink
We’re using slightly different HTML this time. I wanted no extra elements, but that seems like an impossibility at this point. That’s cool because you gotta let go of these things to progress. It’s better to be flexible when working with the web.
- Code language
- html
<h2 class="eyebrow"> <span>Some reasonably long text goes here</span> </h2>
One less <span>
at least and most importantly, no empty elements, which I did have in my last attempt in the challenge.
Let’s now move on to CSS and start on the .eyebrow
block.
- Code language
- css
.eyebrow { --eyebrow-decoration-gutter: 1ch; font-size: var(--size-step-0); font-weight: 500; text-transform: uppercase; text-align: center; text-wrap: balance; font-feature-settings: "cpsp" on; max-width: unset; /* This is the relative parent for the dots */ position: relative; }
We start by determining what the gap between dots and text should be. I’ve opted for a nice 1ch
but it can be whatever you like.
Skipping past the visual typography settings, we set the position
as relative
. The dots will use this as their relative parent where anchor support is available later.
- Code language
- css
.eyebrow > span { position: relative; display: inline-block; }
To handle browsers with no anchor support, we are building with progressive enhancement. By setting the <span>
as a relative parent, we can fake anchoring with absolute positioning later.
- Code language
- css
.eyebrow > span::before, .eyebrow > span::after { content: ""; display: block; aspect-ratio: 1; height: 0.7ex; background-color: currentColor; border-radius: 100%; position: absolute; /* Attach to the anchor */ position-anchor: --eyebrow-target; inset-block-start: 50%; transform: translateY(-50%); }
This is very similar to my original shot at this pattern. We’re creating dots using aspect ratio and border radius, making them the same colour as the rendered text.
The next part, we create an anchor for our dots. We’re not doing this in a @supports
because there’s no point. The browser will ignore and carry on because CSS is a declarative programming language.
The last bit I’m not overly enthused about. I wish translate
had some logical versions because this pattern would work regardless of writing mode. A refactoring target, I guess.
- Code language
- css
.eyebrow > span::before { inset-inline-start: calc(var(--eyebrow-decoration-gutter) * -2); } .eyebrow > span::after { inset-inline-end: calc(var(--eyebrow-decoration-gutter) * -2); }
Again, for browsers with no anchor support, we’re using only absolute positioning to position our anchors. There’s not enough space with the --eyebrow-decoration-gutter
, so I’ve multiplied it by minus two to get a double negative version of our custom property. I can live with that, but you might instead prefer to create an --eyebrow-fallback-decoration-gutter
or something instead.
- Code language
- css
@supports (position-anchor: --eyebrow-target) { /* Not a relative parent, but an anchoring parent instead */ .eyebrow > span { anchor-name: --eyebrow-target; /* Undo the default experience */ display: revert; position: revert; } /* Anchor the end of the dot to the anchor. Margin inline provides the gutter */ .eyebrow > span::before { inset-inline-start: auto; inset-inline-end: anchor(start); margin-inline-end: var(--eyebrow-decoration-gutter); } /* Anchor the start of the dot to the anchor. Margin inline provides the gutter */ .eyebrow > span::after { inset-inline-end: auto; inset-inline-start: anchor(end); margin-inline-start: var(--eyebrow-decoration-gutter); } }
This is where the fun starts. We first check to see if anchor positioning is available to us. A quick note on @supports
: I like to test the actual value we are trying to apply. Feels like a good idea and improves readability as I see it.
The next thing we do is target the span and attach it to the anchor we created earlier with anchor-name: --eyebrow-target;
. Following this, we revert the positioning and display rules to the user agent stylesheet value.
Finally, because we have anchor support, we can now utilise it. For the ::before
element, we change the positioning values to stitch the end of the dot to the start of the anchor. We do the opposite for the ::after
element and stitch the start to the end of the anchor. We re-apply the gutter value with margin
too, as it was set with positioning before.
See the Pen My final solution to this challenge by piccalilli (@piccalilli) on CodePen.
Wrapping uppermalink
It’s been a bit of a different challenge this time around on Front-End Challenges Club. I hope you’ve enjoyed it! Next time, I promise it’ll be a simpler one.
Again, thank you to everyone who had a go at it!