15 Best New Fonts, September 2024
Welcome to our roundup of the best new fonts we’ve found on the web in the previous four weeks. In this month’s edition there’s plenty of hand-lettering charm, and some excellent historical revivals. Enjoy!
Welcome to our roundup of the best new fonts we’ve found on the web in the previous four weeks. In this month’s edition there’s plenty of hand-lettering charm, and some excellent historical revivals. Enjoy!
Welcome to our roundup of the best new fonts we’ve found on the web in the previous four weeks. In this month’s edition there’s plenty of hand-lettering charm, and some excellent historical revivals. Enjoy!
Kornel is a serif type family designed for long texts, featuring classic proportions and sharp details that work well on the web. Inspired by Renaissance typography, its simplified forms deliver a contemporary twist. There are seven weights and accompanying italics.
Tré is a unique typeface born from the logotype for Tré Seals, the founder of Vocal type. The forms are drawn from the scars left by Tré’s brain surgery. The self-titled font is a bold, confident, high-contrast stencil font with sharp angular strokes — exactly like surgical cuts.
Cin Cin is a charming hand-lettered font. It includes a very well balanced upper and lowercase, giving it the flexibility to be used as more than display type. It’s perfect for books, greeting cards, posters, and even logos. It injects fun and energy into any project it’s used for.
Caslonian is an English-inspired typeface originally designed by ZeCraft for Estée Lauder. It draws on exaggerated contrasts and proportions from historical Caslon specimens, offering a contemporary take on the style. Caslonian is a tribute to expressive headline faces, blending past and modern design influences.
Scotus Sans is a low-contrast sans-serif family inspired by the successful 15th-century Scotus Roman type. It offers excellent readability across web and print. It combines simplicity with tradition, ranging from thin to black weights, with a unique matching italic.
Gamuth is a versatile typeface family with serif and sans-serif variations designed for both web and print. Inspired by Dutch Baroque faces, it features narrow proportions, generous x-heights, and crisp detailing. Gamuth Sans excels in UI applications, offering clarity, flexible content hierarchy, and consistent metrics for seamless formatting.
De Gyubee is an elegant, modern serif typeface with a luxurious, minimalist style. Ideal for websites, logos, branding, and wedding stationery, it features capital letters along with unique, classy alternatives. Its clean design suits a wide range of creative projects.
Lineal is a free font family initiated by Frank Adebiaye and inspired by Gérard Manset’s song “2870.” Originally designed in 2010, it resembles a modular Futura without optical corrections. Since 2019, the character set, weights, and language support has been expanded.
Zeist is a geometric sans serif typeface by Luzi Types, blending early 20th-century modernism with customizable features. Inspired by Futura, Neuzeit Grotesk, and Avenir, it offers clean lines, consistent shapes, and dynamic axes for terminals, x-height, weight, and italics, making it highly versatile for designers.
French Aperitif is a hand-drawn font family featuring six unique fonts and fun alternate letters. Perfect for modern posters, wedding stationery, and content creation, it adds a creative, personal touch to designs. This quirky, one-of-a-kind font is 100% hand-drawn, making it truly special.
Brillante is a modern serif font inspired by vintage Italian Art Nouveau shop signs. It features thin horizontal serifs, vertical letters, and a modern x-height. With numerous ligatures, contextual alternates, and stylistic sets, it’s ideal for headings, posters, logotypes, and labels.
Rudnik is a script font family with regular and italic styles, inspired by vintage calligraphy and modern monoline fonts. Its even spacing creates rhythmical, balanced words with distinctive letters. Featuring ligatures and swashes, Rudnik suits posters, packaging, menus, and elegant designs.
Forzata began as a caps-only font, inspired by a hand-painted “no parking” sign in Florence. It evolved into a unique typeface with straight lines, round counters, and a lowercase set without ascenders or descenders, blending industrial engraving with a handmade, vernacular style.
Ringle is a retro serif typeface with a modern twist, featuring sharp italics and strong contrast. With 18 weights, it’s ideal for luxury branding projects. Highly versatile and readable, Ringle suits nostalgic designs like bold magazine images, wedding invitations, posters, logos, and more.
Amolla Raspers is a bubble-style handwritten font, ideal for various projects including logos, branding, packaging, mugs, quotes, posters, t-shirts, book covers, invitations, and greeting cards. It adds a playful, handwritten touch to designs, making it perfect for creative and special events.
The creator of CSS has said he originally envisaged CSS as the main web technology to control behavior on web pages, with scripting as a fallback when things weren’t possible declaratively in CSS. The rationale for a CSS-first approach was that “scripting is programming and programming is hard.” Since introducing the :hover
pseudo-class, CSS has been standardizing patterns developers create in JavaScript and “harvesting” them into CSS standards. When you think about it like that, it’s almost as if JavaScript is the hack and CSS is the official way.
We can, therefore, feel less dirty implementing script-like behavior with CSS, and we shouldn’t be surprised that something like the new scroll-timeline
feature has appeared with pretty good browser support. Too many developers implemented clever parallax scrolling websites, which has summoned the CSS feature genie we cannot put back in its bottle. If you don’t want janky main-thread animations for your next parallax-scrolling website, you must now come to the dark side of hacking CSS. Just kidding, there is also a new JavaScript API for scroll-linked animations if imperative programming better fits your use case.
It was satisfyingly simple to fork Chris Coyier’s pre-scroll-timeline
example of a scroll-linked animation by replacing the CSS Chris was using to control the animations with just one line of CSS and completely deleting the JavaScript!
body, .progress, .cube {
animation-timeline: scroll();
}
Using the scroll()
function without parameters sets up an “anonymous scroll progress timeline” meaning the browser will base the animation on the nearest ancestor that can scroll vertically if our writing mode is English. Unfortunately, it seems we can only choose to animate based on scrolling along the x or y-axis of a particular element but not both, which would be useful. Being a function, we can pass parameters to scroll()
, which provides more control over how we want scrolling to run our animation.
Even better is the scroll-scope
property. Applying that to a container element means we can animate properties on any chosen ancestor element based on any scrollable element that has the same assigned scope. That got me thinking… Since CSS Houdini lets us register animation-friendly, inheritable properties in CSS, we can combine animations on the same element based on multiple scrollable areas on the page. That opens the door for interesting instructional design possibilities such as my experiment below.
Scrolling the horizontal narrative on the light green card rotates the 3D NES console horizontally and scrolling the vertical narrative on the dark green card rotates the NES console vertically. In my previous article, I noted that my past CSS hacks have always boiled down to hiding and showing finite possibilities using CSS. What interests me about this scroll-based experiment is the combinatorial explosion of combined vertical and horizontal rotations. Animation timelines provide an interactivity in pure CSS that hasn’t been possible in the past.
The implementation details are less important than the timeline-scope
usage and the custom properties. We register two custom angle properties:
@property --my-y-angle {
syntax: "<angle>";
inherits: true;
initial-value: 0deg;
}
@property --my-x-angle {
syntax: "<angle>";
inherits: true;
initial-value: -35deg;
}
Then, we “borrow” the NES 3D model from the samples in Julian Garner’s amazing CSS 3D modeling app. We update the .scene
class for the 3D to base the rotation on our new variables like this:
.scene {
transform: rotateY(var(--my-y-angle)) rotateX(var(--my-x-angle));
}
Next, we give the element a
timeline-scope
with two custom-named scopes.
body {
timeline-scope: --myScroller,--myScroller2;
}
I haven’t seen anything officially documented about passing in multiple scopes, but it does work in Google Chrome and Edge. If it’s not a formally supported feature, I hope it will become part of the standard because it is ridiculously handy.
Next, we define the named timelines for the two scrollable cards and the axes we want to trigger our animations.
.card:first-child {
scroll-timeline-axis: x;
scroll-timeline-name: --myScroller;
}
.card:nth-child(2) {
scroll-timeline-axis: y;
scroll-timeline-name: --myScroller2;
}
And add the animations to the scene:
.scene {
animation: rotateHorizontal,rotateVertical;
animation-timeline: --myScroller,--myScroller2;
}
@keyframes rotateHorizontal {
to {
--my-y-angle: 360deg;
}
}
@keyframes rotateVertical {
to {
--my-x-angle: 360deg;
}
}
Since the 3D model inherits the x and y angles from the document body, scrolling the cards now rotates the model in combinations of vertical and horizontal angle changes.
When you think about it, this behavior isn’t just useful for scroll-driven animations. In the above experiment, we are using the scrollable areas more like sliders that control the properties of our 3D model. After getting it working, I went for a walk and was daydreaming about how cool it would be if actual range inputs could control animation timelines. Then I found out they can! At least in Chrome. Pure CSS CMS anyone?
While we’re commandeering 3D models from Julian Garner, let’s see if we can use range inputs to control his X-wing model.
It’s mind-boggling that we can achieve this with just CSS, and we could do it with an arbitrary number of properties. It doesn’t go far enough for me. I would love to see other input controls that can manipulate animation timelines. Imagine text fields progressing animations as you fill them out, or buttons able to play or reverse animations. The latter can be somewhat achieved by combining the :active
pseudo-class with the animation-play-state
property. But in my experience when you try to use that to animate multiple custom properties, the browser can get confused. By contrast, animation timelines have been implemented with this use case in mind and therefore work smoothly and exactly as I expected.
I’m not the only one who has noticed the potential for hacking this emergent CSS feature. Someone has already implemented this clever Doom clone by combining scroll-timeline with checkbox hacks. The problem I have is it still doesn’t go far enough. We have enough in Chrome to implement avatar builders using scrollbars and range inputs as game controls. I am excited to experiment with unpredictable, sophisticated experiences that are unprecedented in the era before the scroll-timeline feature. After all, if you had to explain the definition of a video game to an alien, wouldn’t you say it is just a hyper-interactive animation?
Slide Through Unlimited Dimensions With CSS Scroll Timelines originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Miriam Suzanne’s in the middle of a redesign of her personal website. It began in August 2022. She’s made an entire series out of the work that’s worth your time, but I wanted to call out the fifth and latest installment because she presents a problem that I think we can all relate to:
But the walls got in my way. Instead of minimal renovation, I got just far enough to live with it and then started a brand new Eleventy repo.
The plan was to prototype […] and bring back well-formed solutions. To echo Dave Rupert, prototyping is useful. It’s easier to play with new ideas when you’re not carrying a decade of content and old code along with you.
But prototyping evolved into what I would call tinkering (complimentary). Maybe I mean procrastinating (also complimentary), but it’s a wandering process that also helps me better understand what I want from a website. I might not make visible progress over two years, but I start to form a point of view […]. Keeping things easy is always where things get complicated. And it brings me back to where my redesign started – a desire to clarify the information architecture. Not only for visitors, but for myself.
Don’t even tell me you’ve never been there! Jim Neilsen blogged along similar lines. You get a stroke of inspiration that’s the kernel of some idea that motivates you to start, you know, working on it. There’s no real plan, perhaps. The idea and inspiration are more than enough to get you going… that is until you hit a snag. And what I appreciate about Miriam’s post is that she’s calling out content as the snag. Well, not so much a snag as a return to the founding principle for the redesign: a refined content architecture.
- Sometimes I do events where I speak, or teach a workshop, or perform. Events happen at a time and place.
- Sometimes I create artifacts like a book or an album, a website, or specification. Artifacts often have a home URL. They might have a launch date, but they are not date-specific.
- Some of my projects are other channels with their own feeds, their own events and artifacts.
- Those channels are often maintained by an organization that I work with long-term. A band, a web agency, a performance company, etc.
These boundaries aren’t always clean. A post that remains relevant could be considered an artifact. Events can generate artifacts, and vice versa. An entire organization might exist to curate a single channel.
So, Miriam’s done poking at visual prototypes and ready to pour the filling into the pie crust. I relate with this having recently futzed with the content architecure of this site. I find it tough to start with a solidified design before I know what content is going into it. But I also find it tough to work with no shape at all. In my case, CSS-Tricks has a well-established design that’s evolved, mostly outside of me. I love the design but it’s an inherited one and I’m integrating content around it. Design is the constraint. If I had the luxury of stripping the building to the studs, I might take a different approach because then I could “paint” around it. Content would be the constraint.
It’s yet another version of the Chicken-Egg dilemma. I still think of the (capital-W) Web as a content medium at least in a UA style sense in that it’s the default. It’s more than that, of course. I’m a content designer at heart (and trade) but I’m hesitant to cry “content is king” which reminded me of something I wrote for an end-of-year series we did here answering the question: What is one thing people can do to make their website better? My answer: Read your website.
We start to see the power of content when we open up our understanding of what it is, what it does, and where it’s used. That might make content one of the most extensible problem-solving tools in your metaphorical shed—it makes sites more accessible, extracts Google-juicing superpowers, converts sales, and creates pathways for users to accomplish what they need to do.
And as far as prioritizing content or design, or…?
The two work hand-in-hand. I’d even go so far as to say that a lot of design is about enhancing what is communicated on a page. There is no upstaging one or the other. Think of content and design as supporting one another, where the sum of both creates a compelling call-to-action, long-form post, hero banner, and so on. We often think of patterns in a design system as a collection of components that are stitched together to create something new. Pairing content and design works much the same way.
I’d forgotten those words, so I appreciate Miriam giving me a reason to revisit them. We all need to be recalibrated every so often — swap out air filters, top off the fluids, and rotate the ol’ tires. And an old dog like me needs it a little more often. I spent a few more minutes in that end-of-year series and found a few other choice quotes about the content-design continuum that may serve as inspiration for you, me, or maybe even Miriam as she continues the process of aggragating her distributed self.
This sounds serious, but don’t worry — the site’s purpose is key. If you’re building a personal portfolio, go wild! However, if someone’s trying to file a tax return, whimsical loading animations aren’t likely to be well-received. On the other hand, an animated progress bar could be a nice touch while providing visual feedback on the user’s action.
Cassie Evans, “Empathetic Animation”
Remember, the web is an interactive platform — take advantage of that, where appropriate (less is more, accessibility is integral, and you need to know your audience). Whether that’s scrollytelling, captioned video, and heck, maybe for your audience, now’s the time to start looking into AR/VR! Who knows. Sometimes you just need to try stuff out and see what sticks. Just be careful. Experimentation is great, but we need to make sure we’re bringing everyone along for the ride.
Mel Choyce, “Show, Don’t Tell”
Your personal site is a statement of who you are and what you want to do. If you showcase your favorite type of work, you’ll get more requests for similar projects or jobs — feeding back into a virtuous cycle of doing more of what you love.
Amelia Wattenberger, “Exactly What You Want”
And one of my favorites:
But the prime reason to have a personal website is in the name: it is your personalhome on the web. Since its early days, the web has been about sharing information and freedom of expression. Personal websites still deliver on that promise. Nowhere else do you have that much freedom to create and share your work and to tell your personal story. It is your chance to show what you stand for, to be different, and to be specific. Your site lets you be uniquely you and it can be whatever you imagine it to be.
So if you have a personal site, make sure to put in the work and attention to make it truly yours. Make it personal. Fine-tune the typography, add a theme switcher, or incorporate other quirky little details that add personality. As Sarah Drasner writes, you can feel it if a site is done with care and excitement. Those are the sites that are a joy to visit and will be remembered.
Matthias Ott, “Make it Personal”
That last one has the added perk of reminding me how incredibly great Sarah Drasner is.
Aggregating my distributed self originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
The element is a fairly straightforward concept: focus on it to reveal a set of
s that can be selected as the input’s value. That’s a great pattern and I’m not suggesting we change it. That said, I do enjoy poking at things and found an interesting way to turn a
into a dial of sorts — where options are selected by scrolling them into position, not totally unlike a combination lock or iOS date pickers. Anyone who’s expanded a
for selecting a country knows how painfully long lists can be and this could be one way to prevent that.
Here’s what I’m talking about:
It’s fairly common knowledge that styling in CSS is not the easiest thing in the world. But here’s the trick: we’re not working with at all. No, we’re not going to do anything like building our own by jamming a bunch of JavaScript into a
<section class=scroll-container>
<label for="madrid" class="scroll-item">
Madrid
<abbr>MAD</abbr>
<input id="madrid" type="radio" name="items">
</label>
<label for="malta" class="scroll-item">
Malta
<abbr>MLA</abbr>
<input id="malta" type="radio" name="items">
</label>
<!-- etc. -->
</section>
What we need is to style the list of selectable controls where we are capable of managing their sizes and spacing in CSS. I’ve gone with a group of labels with nested radio boxes as far as the markup goes. The exact styling is totally up to you, of course, but you can use these base styles I wrote up if you want a starting point.
.scroll-container {
/* SIZING & LAYOUT */
--itemHeight: 60px;
--itemGap: 10px;
--containerHeight: calc((var(--itemHeight) * 7) + (var(--itemGap) * 6));
width: 400px;
height: var(--containerHeight);
align-items: center;
row-gap: var(--itemGap);
border-radius: 4px;
/* PAINT */
--topBit: calc((var(--containerHeight) - var(--itemHeight))/2);
--footBit: calc((var(--containerHeight) + var(--itemHeight))/2);
background: linear-gradient(
rgb(254 251 240),
rgb(254 251 240) var(--topBit),
rgb(229 50 34 / .5) var(--topBit),
rgb(229 50 34 / .5) var(--footBit),
rgb(254 251 240)
var(--footBit));
box-shadow: 0 0 10px #eee;
}
A couple of details on this:
--itemHeight
is the height of each item in the list.--itemGap
is meant to be the space between two items.--containerHeight
variable is the .scroll-container’s height. It’s the sum of the item sizes and the gaps between them, ensuring that we display, at maximum, seven items at once. (An odd number of items gives us a nice balance where the selected item is directly in the vertical center of the list). --topBit
and –-footBit
variables are color stops that visually paint in the middle area (which is orange in the demo) to represent the currently selected item.I’ll arrange the controls in a vertical column with flexbox declared on the .scroll-container:
.scroll-container {
display: flex;
flex-direction: column;
/* rest of styles */
}
With layout work done, we can focus on the scrolling part of this. If you haven’t worked with CSS Scroll Snapping before, it’s a convenient way to direct a container’s scrolling behavior. For example, we can tell the .scroll-container
that we want to enable scrolling in the vertical direction. That way, it’s possible to scroll to the rest of the items that are not in view.
.scroll-container {
overflow-y: scroll;
/* rest of styles */
}
Next, we reach for the scroll-snap-style
property that can be used to tell the .scroll-container
that we want scrolling to stop on an item — not near an item, but directly on it.
.scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
/* rest of styles */
}
Now items “snap” onto an item instead of allowing a scroll to end wherever it wants. One more little detail I like to include is overscroll-behavior
, specifically along the y-axis as far as this demo goes:
.scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
overscroll-behavior-y: none;
/* rest of styles */
}
overscroll-behavior-y: none
isn’t required to make this work, but when someone scrolls through the .scroll-container
(along the y-axis), scrolling stops once the boundary is reached, and any further continued scrolling action will not trigger scrolling in any nearby scroll containers. Just a form of defensive CSS.
Time to move to the items inside the scroll container. But before we go there, here are some base styles for the items themselves that you can use as a starting point:
.scroll-item {
/* SIZING & LAYOUT */
width: 90%;
box-sizing: border-box;
padding-inline: 20px;
border-radius: inherit;
/* PAINT & FONT */
background: linear-gradient(to right, rgb(242 194 66), rgb(235 122 51));
box-shadow: 0 0 4px rgb(235 122 51);
font: 16pt/var(--itemHeight) system-ui;
color: #fff;
input { appearance: none; }
abbr { float: right; } /* The airport code */
}
As I mentioned earlier, the --itemHeight
variable is setting as the size of each item and we’re declaring it on the flex
property — flex: 0 0 var(--itemHeight)
. Margin is added before and after the first and last items, respectively, so that every item can reach the middle of the container through scrolling.
The scroll-snap-align
property is there to give the .scroll-container
a snap point for the items. A center alignment, for instance, snaps an item’s center (vertical center, in this case) with the .scroll-container
‘s center (vertical center as well). Since the items are meant to be selected through scrolling alone pointer-events: none
is added to prevent selection from clicks.
One last little styling detail is to set a new background on an item when it is in a :checked
state:
.scroll-item {
/* Same styles as before */
/* If input="radio" is :checked */
&:has(:checked) {
background: rgb(229 50 34);
}
}
But wait! You’re probably wondering how in the world an item can be :checked
when we’re removing pointer-events
. Good question! We’re all finished with styling, so let’s move on to figuring some way to “select” an item purely through scrolling. In other words, whatever item scrolls into view and “snaps” into the container’s vertical center needs to behave like a typical form control selection. Yes, we’ll need JavaScript for that.
let observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
with(entry) if(isIntersecting) target.children[1].checked = true;
});
}, {
root: document.querySelector(`.scroll-container`), rootMargin: `-51% 0px -49% 0px`
});
document.querySelectorAll(`.scroll-item`).forEach(item => observer.observe(item));
The IntersectionObserver
object is used to monitor (or “observe”) if and when an element (called a target
) crosses through (or “intersects”) another element. That other element could be the viewport itself, but in this case, we’re observing the .scroll-container
for when a .scroll-item
intersects it. We’ve established the observed boundary with rootMargin:"-51% 0px -49% 0px"
.
A callback function is executed when that happens, and we can use that to apply changes to the target element, which is the currently selected .scroll-item
. In our case, we want to select a .scroll-item
that is at the halfway mark in the .scroll-container
: target.children[1].checked = true
.
That completes the code. Now, as we scroll through the items, whichever one snaps into the center position is the selected item. Here’s a look at the final demo again:
Let’s say that, instead of selecting an item that snaps into the .scroll-container
‘s vertical center, the selection point we need to watch is the top of the container. No worries! All we do is update the scroll-snap-align
property value from center to start in the CSS and remove the :first-of-type
‘s top margin. From there, it’s only a matter of updating the scroll container’s background gradient so that the color stops highlight the top instead of the center. Like this:
And if one of the items has to be pre-selected when the page loads, we can get its position in JavaScript (getBoundingClientRect()
) and use the scrollTo()
method to scroll the container to where that specific item’s position is at the point of selection (which we’ll say is the center in keeping with our original demo). We’ll append a .selecte
d class on that .scroll-item
.
<section class="scroll-container">
<!-- more items -->
<label class="scroll-items selected">
2024
<input type=radio name=items />
</label>
<!-- more items -->
</section>
Let’s select the .selected
class, get its dimensions, and automatically scroll to it on page load:
let selected_item = (document.querySelector(".selected")).getBoundingClientRect();
let scroll_container = document.querySelector(".scroll-container");
scroll_container.scrollTo(0, selected_item.top - scroll_container.offsetHeight - selected_item.height);
It’s a little tough to demo this in a typical CodePen embed, so here’s a live demo in a GitHub Page (source code). I’ll drop a video in as well:
That’s it! You can build up this control or use it as a starting point to experiment with different layouts, styles, animations, and such. It’s important the UX clearly conveys to the users how the selection is done and which item is currently selected. And if I was doing this in a production environment, I’d want to make sure there’s a good fallback experience for when JavaScript might be unavailable and that my markup performs well on a screen reader.
How to Make a “Scroll to Select” Form Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
You have to get creative when you lack a strong visual. This month’s design trends roundup focuses on this concept.
Seeing a lot more headlines decrying JavaScript and pumping up PHP. Always interesting to see which direction the front-end zeitgeist is leaning.
Quick Hit #21 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
For the past two months, all my livelihood has gone towards reading, researching, understanding, writing, and editing about Anchor Positioning, and with many Almanac entries published and a full Guide guide on the way, I thought I was ready to tie a bow on it all and call it done. I know that Anchor Positioning is still new and settling in. The speed at which it’s moved, though, is amazing. And there’s more and more coming from the CSSWG!
That all said, I was perusing the last CSSWG minutes telecon and knew I was in for more Anchor Positioning when I came to the following resolution:
Whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree) (Issue #10526: When does
anchor-scope
“match” a name?)
Resolutions aren’t part of the specification or anything, but the strongest of indications about where they’re headed. So, I thought this was a good opportunity not only to take a peek at what we might get in anchor-scope
and touch on other interesting bits from the telecon.
Remember that you can subscribe and read the full minutes on W3C.org. 🙂
anchor-scope
?To register an anchor, we can give it a distinctive anchor-name
and then absolutely positioned elements with a matching position-anchor
are attached to it. Even though it may look like it, anchor-name
doesn’t have to be unique — we may reuse an anchor element inside a component with the same anchor-name
.
<ul>
<li>
<div class="anchor">Anchor 1</div>
<div class="target">Target 1</div>
</li>
<li>
<div class="anchor">Anchor 2</div>
<div class="target">Target 2</div>
</li>
<li>
<div class="anchor">Anchor 3</div>
<div class="target">Target 3</div>
</li>
</ul>
However, if we try to connect them with CSS,
.anchor {
anchor-name: --my-anchor;
}
.target {
position: absolute;
position-anchor: --my-anchor;
position-area: top right;
}
We get an unpleasant surprise where instead of each .anchor
have their .target
positioned at its top-right edge, meaning they all pile up on the last .anchor
instance. We can see it better by rotating each target a little. You’ll want to check out the next demo in Chrome 125+ to see the behavior:
The anchor-scope
property should make an anchor element only discoverable by targets in their individual subtree. So, the prior example would be fixed in the future like this:
.anchor {
anchor-name: --my-anchor;
anchor-scope: --my-anchor;
}
This is fairly straightforward — anchor-scope
makes the anchor element available only in that specific subtree. But then we have to ask another question: What should the anchor-scope
own scope be? We can’t have an anchor-scope-scope
property and then an anchor-scope-scope-scope
and so on… so which behavior should it be?
This is what started the conversation, initially from a GitHub issue:
When an
anchor-scope
is specified with a, it scopes the name to that subtree when the anchor name is “matching”. The problem is that this matching can be interpreted in at least three ways: (Assuming that
anchor-scope
is a tree-scoped reference, which is also not clear in the spec):
- It matches by the ident part of the name only, ignoring any tree-scope that would be associated with the name, or
- It matches by exact match of the ident part and the associated tree-scope, or
- It matches by some mechanism similar to dereferencing of tree-scoped references, where it’s a match when the tree-scope of the anchor-scope-name is an inclusive ancestor of the tree-scope of the anchor query.
And then onto the CSSWG Minutes:
TabAtkins: In anchor positioning, anchor names and references are tree scoped. The
anchor-scope
property that scopes, does not say whether the names are tree scoped or not. Question to decide: should they be?TabAtkins: I think the answer should be yes. If you have an anchor in a shadow tree with a part involved, then problems result if anchor scopes are not tree scoped. This is bad, so I think it should be tree scoped sounds pretty reasonable makes sense to me as far as I can understand it 🙂
This solution of the scope of scoping properties expanded towards View Transitions, which also rely on tree scoping to work:
khush: Thinking about this in the context of view transitions: in that API you give names and the tree scope has to be the same for them to match. There is another view transitions feature where I’m not sure if the spec says it’s tree scoped
khush: Want to make sure that feature is covered by the more general resolution
TabAtkins: Proposed more general resolution: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
So the scope of anchor-scope
is tree-scoped. Say that five times fast!
RESOLVED: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
The next resolution was pretty straightforward. Besides allowing a , the
anchor-scope
property can take an all
keyword, which means that all anchors are tree-scoped, while the says that specific anchor is three-scoped. So, the question was if
all
is also a tree-scoped value.
TabAtkins:
anchor-scope
, in addition to idents, can take the keyword ‘all
‘, which scopes all names. Should this be a tree-scoped ‘all
‘? (i.e. only applies to the current tree scope)TabAtkins: Proposed resolution: the ‘
all
‘ keyword is also tree-scoped in the same way sgtm +1, again same pattern withview-transition-group
RESOLVED: the ‘
all
‘ keyword is tree-scoped
The conversation switched gears toward new properties coming in the CSS Scroll Snap Module Level 2 draft, which is all about changing the user’s initial scroll with CSS. Taking anexample directly from the spec, say we have an image carousel:
<div class="carousel">
<img src="img1.jpg">
<img src="img2.jpg">
<img src="img3.jpg" class="origin">
<img src="img4.jpg">
<img src="img5.jpg">
</div>
We could start our scroll to show another image by setting it’s scroll-start-targe
to auto
:
.carousel {
overflow-inline: auto;
}
.carousel .origin {
scroll-start-target: auto;
}
As of right now, the only way to complete this is using JavaScript to scroll an element into view:
document.querySelector(".origin").scrollIntoView({
behavior: "auto",
block: "center",
inline: "center"
});
The last example is probably a carousel that is only scrollable in the inline direction. Still, there are doubts as far when the container is scrollable in both the inline and block directions. As seen in the initial GitHub issue:
The scroll snap 2 spec says that when there are multiple elements that could be
scroll-start-targets
for a scroll container “user-agents should select the one which comes first in tree order“.Selecting the first element in tree-order seems like a natural way to resolve competition between multiple targets which would be scrolled to in one particular axis but is perhaps not as flexible as might be needed for the 2d case where an author wants to scroll to one item in one axis and another item in the other axis.
And back to the CSSWG minutes:
DavidA: We have a property we’re adding called
scroll-start-target
that indicates if an element within a scroll container, then the scroll should start with that element onscreen. Question is what happens if there are multiple targets?
DavidA: Propose to do it in reverse-DOM order, this would result in the first one applied last and then be on screen. Also, should only change the scroll position if you have to.
After discussing why we have to define scroll-start-target
when we have scroll-snap-align
, the discussion went on discuss the reverse-DOM order:
fantasai: There was a bunch of discussion about regular vs reverse-DOM order. Where did we end up and why?
flackr: Currently, we expect that it scrolls to the first item in DOM order. We probably want that to still happen. That is why the proposal is to scroll to each item in sequence in reverse-DOM order.
So we are coming in reverse to scroll the element, but only as required so the following elements are showing as much as possible:
flackr: There is also the issue of nearest…
fantasai: Can you explain nearest?
flackr: Same as scroll into view
fantasai: ?
flackr: This is needed with you scroll multiple things into view and want to find a good position (?)
fantasai: You scroll in reverse-DOM order…when you add the spec can you make it really clear that this is the end result of the algorithm?
flackr: Yes absolutely
fantasai: Otherwise it seems to make sense
And so it was resolved:
Proposed resolution 2: When
scroll-start-target
targets multiple elements, scroll to each in reverse DOM order with text to specify priority is the first item
Lastly, there was the debate about the text-underline-position
, that when set to auto
says, “The user agent may use any algorithm to determine the underline’s position; however it must be placed at or under the alphabetic baseline.” The discussion was about whether the auto
value should automatically adjust the underlined position to match specific language rules, for example, at the right of the text for vertical writing modes, like Japanese and Mongolian.
fantasai: The initial value of
text-underline-position
isauto
, which is defined as “find a good place to put the underline”.
Three options there: (1) under alphabetical baseline, (2) fully below text (good for lots-of-descenders cases), (3) for vertical text on the RHS
fantasai: auto value is defined in the spec about ‘how far down below the text’, but doesn’t say things about flipping. The current spec says “at or below”. In order to handle language-specific aspects, there is a default UA style sheet that for Chinese and Japanese and Korean there are differences for those languages. A couple of implementations do this
fantasai: Should we change the spec to mention these things?
fantasai: Or should we stick with the UA stylesheet approach?
The thing is that Chrome and Firefox already place the underline on the right in vertical Japanese when text-underline-position
is auto
.
The group was left wiuth three options:
A) Keep spec as-is, update Gecko + Blink to match (using UA stylesheet for language switch)
B) Introduce auto totext-emphasis-positio
n and use it in bothtext-emphasis-position
andtext-underline-position
to effect language switches
C) Adopt inconsistent behavior:text-underline-position
uses ‘auto
‘ andtext-emphasis-position
uses UA stylesheetMany CSSWG members like Emilio Cobos, TabAtkins, Miriam Suzanne, Rachel Andrew and fantasai casted their votes, resulting in the following resolution:
RESOLVED: add auto value for text-emphasis-position, and change the meaning of text-underline-position: auto to care about left vs right in vertical text
I definitely encourage you to read at the full minutes! Or if you don’t have the time, you can there’s a list just of resolutions.
CSSWG Minutes Telecon (2024-09-18) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Having fun with Bramus’ new Caniuse CLI tool. This’ll save lots of trips to the Caniuse site!
Quick Hit #20 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Two possible syntaxes for CSS masonry, one draft specification, and you get to share your opinions.
Quick Hit #19 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.