There are several exciting new CSS features in Safari 17.5, including text-wrap: balance
, the light-dark()
color function, and @starting-style
, plus the ability to use feature queries with @import
rules. Let’s look at how you can put each one to use.
On the web, with its flexible container widths, inconsistent lengths of content, and variation between browsers, it can feel impossible to avoid having text wrap in such a way that too few words end up all by themselves on a very short last line.
When type was set by hand, typographers would painstakingly avoid this undesirable result by manually moving content around. Over the decades, web developers have tried a series of different tricks to avoid orphans in CSS, in HTML, in JavaScript, and in content management systems. None work very well. The attempts usually feel hacky, laborious, and fragile.
To solve this and other frustrations, the CSS Working Group has defined three new options that you can use to change how text will wrap. You can switch from default wrapping to another style with text-wrap
. WebKit for Safari 17.5 adds support for the first of these new options — balancing.
The text-wrap: balance
rule asks the browser to “balance” the lines of text and make them all about the same length.
You can see how now the text no longer fills the containing block — there’s a large amount of space on the right of the words. This is expected, and something you’ll want to think about as you decide when to use text-wrap: balance
.
Where exactly each line of text will break when using text-wrap: balance
may be slightly different in each browser. The CSS Text level 4 web standard leaves it up to each browser engine team to decide which algorithm they want to use in determining how exactly to wrap balanced text.
It can be computationally expensive for the browser to count characters and balance multiple lines of text, so the standard allows browsers to limit the number of lines that are balanced. Chromium browsers balance 6 or fewer lines, Firefox balances 10 or fewer, while Safari/WebKit balances an unlimited numbers of lines.
For now, Safari does not balance text if it’s surrounding a float or initial letter. And Safari disables the balancer if the content contains preserved tabs or soft hyphens.
The text-wrap
property is actually a shorthand for two longhand properties: text-wrap-style
and text-wrap-mode
.
The text-wrap-mode
property provides a mechanism for expressing whether or not text should wrap.
text-wrap-mode: wrap; /* initial value */
text-wrap-mode: nowrap;
The wrap
value turns it on, and the nowrap
value turns it off, just like the values for white-space
. (In fact, text-wrap-mode
is the newly introduced longhand of white-space
.) WebKit added support for text-wrap-mode: wrap
and nowrap
in Safari 17.4.
The text-wrap-style
property selects how to wrap. The initial value is auto
— asking text to wrap in the way it has for decades. Or, you can choose a value to switch to another “style” of wrapping.
WebKit for Safari 17.5 adds support for text-wrap-style: balance
, stable
, and auto
.
text-wrap-style: auto; /* initial value */
text-wrap-style: balance;
text-wrap-style: stable;
Of course, the text-wrap
shorthand is a way to combine text-wrap-mode
and text-wrap-style
and declare them together. If you write text-wrap: balance
it’s the same as text-wrap: wrap balance
, meaning: “yes, please wrap, and when you do, please balance the text”.
Full support will eventually include three properties and six values. No browser supports everything yet, so be sure to look up support for the text-wrap
, text-wrap-mode
, and text-wrap-style
properties, as well as the balance
, pretty
, stable
, auto
, wrap, and nowrap
values.
The balance
, pretty
, and stable
values will simply fall back to auto
in browsers without support, so progressive enhancement is easy. You can use these values today, no matter how many of your users don’t yet have a browser with support. They will simply get auto
-wrapped text, just like they would if you didn’t use text-wrap
. Meanwhile, those users with support will get an extra boost of polish.
light-dark()
color functionMore and more, users expect websites and web apps to support dark mode. Since Safari 12.1, the prefers-color-scheme
media query has given you the ability to write code like this:
body {
background: white;
color: black;
}
@media (prefers-color-scheme: dark) {
body {
background: darkslategray;
color: white;
}
}
Or perhaps you’ve used variables to define colors for both light and dark mode at once, making it easier to use them everywhere.
:root {
--background: white;
--text: black;
}
@media (prefers-color-scheme: dark) {
:root {
--background: darkslategray;
--text: white;
}
}
body {
background: var(--background);
color: var(--text);
}
Well, now there’s a new option — the light-dark()
function. It makes defining colors for dark mode even easier.
First, inform the browser you are providing a design for both light and dark modes with the color-scheme
property. This prompts the browser to switch the default user agent styles when in dark mode, ensuring the form controls appear in dark mode, for example. It’s also required for light-dark()
to work correctly.
:root {
color-scheme: light dark;
}
Then, any time you define a color, you can use the light-dark()
function to define the first color for light mode, and the second color for dark mode.
color: light-dark(black, white);
background-color: light-dark(white, darkslategray);
You can still use variables, if you’d like. Perhaps you want to structure your code like this.
:root {
color-scheme: light dark;
--background: light-dark(black, white);
--text: light-dark(white, darkslategray);
}
body {
background: var(--background);
color: var(--text);
}
An often-asked question when learning about light-dark()
is “does this only work for colors?” Yes, this function only works for colors. Use the prefers-color-scheme
media query to define the rest of your color-scheme dependent styles.
WebKit for Safari 17.5 adds support for @starting-style
. It lets you define starting values for a particular element. This is needed to enable a transition when the element’s box is created (or re-created).
.alert {
transition: background-color 2s;
background-color: green;
@starting-style {
background-color: transparent;
}
}
In the above example, the background-color
will transition from transparent to green when the element is added to the document.
Many developers are excited to use @starting-style
along with display: none
interpolation. To do so, WebKit also needs to support animation of the display
property, which has not yet shipped in Safari. You can test this use case today in Safari Technology Preview.
WebKit for Safari 17.5 adds the supports()
syntax to @import
rules. Now you can conditionally import CSS files based on whether or not there’s support for a certain feature.
@import <url> supports(<feature>);
For example, you could load different stylesheets based on whether or not CSS Nesting is supported.
@import "nested-styles.css" supports(selector(&));
@import "unnested-styles.css" supports(not selector(&));
Or you could load certain CSS files when a browser does not have support for Cascade Layers. (Note that any @import
rules with layer()
will automatically be ignored in a browser without layer support.)
@import url("reset.css") layer(reset);
@import url("framework.css") layer(framework);
@import url("custom.css") layer(custom);
@import url("unlayered-fallback-styles.css") supports(not at-rule(@layer));
Or simply test for a feature. Here, these layout styles will only be loaded if Subgrid is supported.
@import url("layout.css") supports(grid-template-columns: subgrid);
WebKit for Safari 17.5 adds support for AV1 to WebCodecs when an AV1 hardware decoder is available.
WebKit for Safari 17.5 adds WebGL support for EXT_conservative_depth
and NV_shader_noperspective_interpolation
.
WKWebView adds support for logging MarketplaceKit errors to the JavaScript console. This will make errors easier to debug.
In addition to these new features, WebKit for Safari 17.5 includes work polishing existing features.
excludeCredentials
property being ignored during a passkey registration request. (124405037) @scope
. (124640124) @scope
limit making the element out of scope. (124956673) <select>
rendering in dark mode. (123845293) pointer-events: none
is placed over it. (118936715) <audio>
playback to continue to the next media activity when in the background. (121268089) (FB13551577) loadeddata
events for <audio>
and <video>
on page load. (124079735) (FB13675360) mousemove
events in an iframe
when the mouse is clicked from outside the iframe
and then moves into it while the button is held down. (120540148) (FB13517196) Safari 17.5 is available on iOS 17.5, iPadOS 17.5, macOS Sonoma 14.5, macOS Ventura, macOS Monterey and in visionOS 1.2.
If you are running macOS Ventura or macOS Monterey, you can update Safari by itself, without updating macOS. On macOS Ventura, go to > System Settings > General > Software Update and click “More info…” under Updates Available.
To get the latest version of Safari on iPhone, iPad, or Apple Vision Pro, go to Settings > General > Software Update, and tap to update.
We love hearing from you. To share your thoughts on Safari 17.5, find us on Mastodon at @jensimmons@front-end.social and @jondavis@mastodon.social. Or send a reply on X to @webkit. You can also follow WebKit on LinkedIn. If you run into any issues, we welcome your feedback on Safari UI, or your WebKit bug report about web technologies or Web Inspector. Filing issues really does make a difference.
Download the latest Safari Technology Preview on macOS to stay at the forefront of the web platform and to use the latest Web Inspector features.
You can also find this information in the Safari 17.5 release notes.
]]>This release includes WebKit changes between: 277150@main…277790@main.
aria-hidden
on a slot not hiding the slot’s assigned nodes. (277359@main) (108762653) aria-hidden=true
to be ignored on the <body>
and <html>
elements. (277677@main) (123049663) -apple-pay-button
from applying to any element that supports appearance: auto
and is not a button. (277268@main) (126107516) v
flag with RegExp.prototype[Symbol.matchAll]
. (277160@main) (126017731) RegExp.prototype.@@split
to update the following legacy RegExp static properties: RegExp.input
, RegExp.lastMatch
, RegExp.lastParen
, RegExp.leftContext
, RegExp.rightContext
, and RegExp.$1, ... RegExp.$9
. (277559@main) (99865597) Sec-Fetch-Site
value for navigation of a nested document. (277662@main (109358563) break-word
with a float discarding text. (277461@main) (126309547) d
property in SVG. (277299@main) (126206354) transform
property while that property is animated with an implicit keyframe. (277257@main) (126126617) color-mix
. (277256@main) (126148201) getHTML()
as well as the corresponding shadow root serializable
feature. (277374@main) (125513986) toJSON()
method. (277347@main) (126183686) toJSON()
method. (277413@main) (126247408) CustomEvent.target
when dispatching an event. (277435@main) (126311287) permissionState
to be requested if an extension has the webNavigation
permission. (277308@main) (126212951) EXT_render_snorm
, OES_sample_variables
, and OES_shader_multisample_interpolation
. (277775@main) (126863775) One issue happened when running JetStream on fast hardware where the resolution of time to run a subtest, as measured by the timing code, occasionally returned 0. This caused problems when calculating subtest scores. Two other issues were found in the sub-test segmentation, where the code didn’t create the initial Float32Array for the test and another race condition issue was fixed in the task queue code for that test.
Two other issues running JetStream via the command-line were found and fixed. The first was specific to the Spider Monkey command-line shell and the second was to fix the Wasm command-line script when running with both the D8 and Spider Monkey shells.
We are introducing JetStream 2.2, which fixes the issues mentioned above and includes the same subtests as JetStream 2.1 as well as the original JetStream 2. The fixes improve the stability of the benchmark. We measured JetStream 2.2’s benchmark scores from Chrome, Safari, and Firefox and found them to be as stable as JetStream 2.1. Measurements were performed using a 16“ MacBook Pro with an M3 processor. The system was not running other applications and the browser app being tested, configured without extensions enabled, was restarted before each JetStream run. A total of 10 runs were performed for each browser app JetStream version combination. Scores produced from JetStream 2.2 are not comparable to other versions of any JetStream benchmark.
]]>This release includes WebKit changes between: 276610@main…277149@main.
aria-labelledby
to expose their entire subtree text, not just their direct child text. (276864@main) (125634439) visibility: visible
inside a container with visibility: hidden
. (277004@main) (125738704) @starting-style
incorrectly invoking with a null element. (276993@main) (125837628) input
element with type="email"
and the multiple
attribute. (276895@main) (125221858) Function.prototype.toString
for accessor properties. (276904@main) (125739577) height: 100%
is applied on nested content. (276880@main) (125572851) hasUAVisualTransition
. (277001@main) (125849073) What do we mean by the term “masonry layout”? Basically it’s the pattern seen in the following image — where content packs together like a brick or stone wall. That’s where it gets the name “masonry”. It’s also frequently called “waterfall layout”, as a metaphor for how content flows down the page like a waterfall.
This layout is popular because it solves a few problems that other layouts do not.
This layout creates uniformly-sized columns, without any rows. It’s quite possible that because this layout has required JavaScript, anything more creative or complex has been too hard to pull off — and we’ve been left with an expectation that masonry layout should only ever be a simple pattern with uniformly-sized columns. Let’s see what’s possible if we build it into CSS instead.
A mechanism in CSS for “masonry layout” was first proposed by Mozilla in January 2020 as an extension of CSS Grid, and implemented as an experiment behind a flag in Firefox Nightly. In 2022, Apple started implementing this CSS Grid Level 3 proposal in Safari Technology Preview (where it’s currently on by default), and we’ve been helping to move the web standard along to reach maturity.
However, there are big questions still being asked about how CSS should handle masonry-style layouts. Some people remain skeptical that this capability should be part of CSS Grid, and want it to instead be its own separate display
type. Others are questioning whether or not this kind of layout is needed on the web at all — they aren’t sure that well-known websites will use it. With such fundamental disagreements at play, no browser can ship. We must first come to consensus in the CSS Working Group.
This is where we need your help. We’d like real-world web designers and developers to weigh into the discussion, and express what it is that you want. Your input really can make a difference.
In this article, we’ll walk through how the CSS Grid Level 3 proposal works, and how you can use its new capabilities. We’ll show you why we believe these features should be part of CSS Grid, and explain what the alternative would be if the CSS Working Group creates display: masonry
instead. And then, we’ll ask you to join the debate to help move us forward. Please do read to the end.
To show why we at Apple believe this capability should be part of CSS Grid, we created four demonstrations. If you’d like, try them yourself at webkit.org/demos/grid3. View these demos in a browser that supports Grid Level 3 — currently Safari Technology Preview or Firefox after you’ve turned on the feature flag.
Note there’s a control panel for each demo, with the relevant layout code printed to the page. Turn on “Number items” to see the relationship between the HTML order of content and the layout placement of that content.
Each demo has a multitude of variations. Switch between variations from the dropdown menu, which changes only the CSS. The HTML stays the same.
First, let’s take a look at how to build a classic masonry/waterfall layout. In this gallery of photos, each image is wrapped with a figure
element, and the figures are direct children of a main
element.
<main>
<figure><img src="photo-1.jpg"></figure>
<figure><img src="photo-2.jpg"></figure>
<figure><img src="photo-3.jpg"></figure>
</main>
We start by applying display: grid
to the main element to create the Grid container. Then we can define grid-template-columns
however we’d like.
In this case, let’s use grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr))
to ask the browser to repeat a looped definition to create a set of flexible columns that are each of a minimum of 14rem. This results in uniformly-sized columns, typical of the classic masonry/waterfall layout. The gap: 1rem;
rule creates a 1rem-wide space between the items — both between the columns, and horizontally between items.
And then, we’ll define the “rows” with the masonry
value. (It’s likely the name of this value will change before this ships in browsers — more on that at the end of this article. For now, masonry
is what works.)
main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
gap: 1rem;
grid-template-rows: masonry;
}
The grid-template-rows: masonry
rule tells the browser: “Please do not create rows. Instead pack the content into a masonry/waterfall-like pattern.”
That’s it! In four lines of CSS, with zero media queries or container queries, we’ve created a flexible layout that works on screens of all sizes. And there’s no need to crop content to force everything into same-sized boxes.
In graphic design, a layout that has uniformly-sized columns and no rows is often called a “symmetrical columnar grid”. For centuries, columnar grids were the dominant type of grid used in page design.
Now let’s dive into the advantages of combining the full power of CSS Grid with masonry/waterfall packing. CSS Grid provides many options for defining grid our columns. Using fr
units to create a symmetrical grid is only one option of many.
How could these possibilities be used for a masonry/waterfall-style layout? Let’s try mixing fixed-sized columns with flexible columns. We could make the first and last column fixed-sized, while the middle columns are flexible, changing in both size and number.
Specifically, the first and last columns are exactly 14 characters wide, while the middle columns are flexible (at least 28 characters wide) and change in number to fill the available space.
main {
display: grid;
grid-template-columns: 14ch repeat(auto-fill, minmax(28ch, 1fr)) 14ch;
grid-template-rows: masonry;
gap: 1rem;
}
This is just one of many, many possibilities.
CSS Grid allows for a lot of creativity with its options for defining grid tracks:
max-content
and min-content
fr
unitsminmax()
functions%
-sized These options in CSS Grid allow you to create something much more dynamic and flexible in interesting ways. You can create two stages of flexibility, because the fr
-unit sized columns grow and shrink in a separate stage from the minmax()
-sized columns. The max-content
and min-content
values let you size the columns based on the content size, rather than sizing the content based on the column size. The fr
units can easily be used to create compound or asymmetrical grids, where the columns are different sizes. The options are endless.
By adding the ability to pack content in a masonry/waterfall pattern to CSS Grid, we maintain the full power of Grid for defining our columns in whichever manner we like.
For example, let’s use grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr) minmax(16rem, 2fr)) minmax(8rem, 1fr)
to create a pattern of alternating narrow and wider columns, where all the columns are flexible. More columns are added when there’s space. And there’s always an odd number of columns, ending with a narrow one.
Even when we define columns using only the simple fr
unit, the full power provided in CSS Grid means different columns can be set to different sizes. For fun, let’s use fr
units to define a set of columns sized to inject the vibes of the golden ratio by using the Fibonacci sequence in grid-template-columns: 1fr 1fr 2fr 3fr 5fr 8fr;
In a more practical example, let’s use max-content
when defining our columns. Content-based sizing is an incredibly power feature of CSS Grid. This demo of a mega menu layout uses grid-template-columns: repeat(auto-fill, minmax(max-content, 30ch));
to ensure that every column is big enough to fit every link without wrapping text.
Mega menus have been hard to code, especially across multiple screen sizes. With CSS Grid Level 3, it becomes incredibly easy. A few lines of code creates a dynamic layout which adds and removes columns one at a time as space allows — without any media/container queries, and with wrapping prevention.
Many of these examples could never be created with masonry as a separate display type. The discussion of display: masonry
is to only allow symmetrical columns (columns that are the same size as each other), much like multicolumn layout does today.
CSS Grid also lets us span items across multiple columns. Let’s use that capability to see what interesting options might emerge. How about making every 5th image span two grid columns, while the rest of the images are span one column.
What if instead, we put a wider
class on specifically on images that have a wider aspect-ratio, to make those images span multiple columns. We can also change the styling a bit, making the corners square instead of round, and reducing the grid gap to zero. This gives us a different way to pack photos of different aspect ratios together.
We also experimented with combining the classic masonry/waterfall layout of photos with View Transitions. When a user clicks/taps on any photo, it grows to span multiple columns. The browser automagically animates the transition. (This demo requires Safari Technology Preview 192 or later.)
These variations of the Photos and Mega Menu demos are just a small taste of all of the many possibilities you get when leveraging the full power of CSS Grid in the column direction, while simultaneously turning off rows.
What happens when we keep experimenting? Let’s let go of thinking about “masonry”, and start imagining Grid Level 3 purely as an expansion of Grid. At its core, CSS Grid Level 3 provides a mechanism for turning off rows. It lets us create a columnar grid — a grid that’s made up of columns alone.
By contrast, a modular grid is a grid where everything is lined up in both columns and rows. They became popular in the 20th century during the dominance of modernism in graphic design. CSS Grid Level 1 is really good at making modular grids… that’s what it wants to do. In fact, float-based layouts also encouraged the creation of modular grids on the web, since you had to make all your content the same height to get your floats to clear. Images need to be the same aspect ratio. Text has to be the same length. This is often accomplished on the back-end with policies enforced by the content management system, or on the front-end by CSS that truncates/crops the content.
It’s incredible common for websites to do some variation of this sort of modular grid, laid out here with CSS Grid Level 1.
Of course, this example is overly simplistic. The article ledes look bare with no teaser images. The uniformity is so strict and formal, the design lacks life. Real websites find other ways to breathe life back into the design.
But what if the layout itself could also provide some vitality and interest? What will happen if we use CSS Grid to create a columnar grid as easily as it creates a modular grid? What if we don’t truncate content, and instead let it be the size that it wants to be — and get the layout to work for the content, rather than forcing the content to work for the layout?
A classic masonry/waterfall layout with various lengths of text looks like this, which is already more engaging since a user can read more about each article:
Although, that’s still a bit visually repetitive. Symmetrical columnar grids often are. We need the rest of the power of CSS Grid to do something more interesting. Let’s make the latest article much bigger, and have it span four columns. A handful of other recent articles can be medium-sized and span two columns. While the older content is smaller, spanning one column.
Now this otherwise visually boring text is starting to look fairly lively. If we were to add images to each of these articles, it would be it even more dynamic.
Let’s experiment with mixing images and text together on a webpage for a museum. The first grid item is a header that introduces the museum, and provides navigation to other resources. The rest of the content consists of pieces of artwork and their information: title, artist, year, medium, catalog number and location.
Because the paintings are gorgeous, the content looks pretty great in a classic masonry/waterfall layout.
Let’s see what else we can do by utilizing two more powerful features of CSS Grid — subgrid and explicit placement.
The functionality provided by subgrid in CSS Grid Level 2 is incredible, and it’s finally supported in most browsers.
Instead of listing the painting’s metadata in a single left-aligned column, let’s see how we might better use the available space. By using subgrid
, we can put the year and catalog number on the right of each card — and line up this data for one painting with the same data for the other paintings.
By adding this new functionality to CSS Grid Level 3, we get the benefit of existing developer tools. You can use the Grid Inspector in Safari Technology Preview today as you try out grid-template-rows: masonry
.
If masonry is its own display type, and not part of CSS Grid, it will not get the benefit of subgrid.
We can also use the power of CSS Grid Level 1 to explicitly place the header into the last two columns, moving it to the top right corner of the page with grid-column: -3 / -1
.
In just a few lines of layout code, we are using the full power of CSS Grid Levels 1, 2, and 3 to create flexible columns that change in number to accommodate the available size — without using any media queries or container queries.
Hopefully you can see the advantages of fully combining a mechanism for masonry/waterfall layouts with CSS Grid — providing many more creative possibilities than masonry alone.
So let’s get into the debate that’s been blocking the CSS Working Group from moving forward. Our hope is that web designers and developers chime in (post to social media, write blog posts) with your thoughts about which direction CSS should take.
Some people, including those of us at Apple, like having “Masonry” be part of CSS Grid. We believe this functionality is a mechanism to expand CSS Grid — allowing it to finally create columnar grids as well as modular grids. And we want this functionality to be mixed with all the other features of Grid, including the powerful options for defining a columns, track spanning, explicit placement, and subgrid.
Other people instead believe Masonry should be its own separate display type. At first glance, defining Masonry with a new display type might make a lot of sense. You do get a tidy separation between layout types.
display: block;
display: inline;
display: flexbox;
display: grid;
display: masonry;
The CSS Working Group has not discussed how the syntax for a separate Masonry display type would work, but perhaps it would be patterned after Multicolumn layout.
main {
display: masonry;
columns: 28ch;
}
Or perhaps the syntax would be patterned after Grid, but with significant limitations:
main {
display: masonry;
masonry-columns: repeat(5, minmax(28ch, 1fr));
/* where only one repeating width is allowed */
}
Either way, it’s clear that advocates of this option want Masonry to be limited to a symmetrical grid — where all the columns are the same size as each other. None of the rest of CSS Grid’s track sizing capabilities would be allowed.
Making masonry a simple and separate layout type would avoid the work necessary to keep Grid and Masonry working together in combination — both now and in the long term. Doing this would simplify the layout model, make it easier to implement in browsers, reduce the potential for performance traps, and allow the feature sets of Grid and Masonry to diverge.
Conversely, we believe the effort needed to add this capability to CSS Grid is worth the many benefits to be had. The CSS Grid Level 3 specification has already been written, and implemented in two browser engines. And yes, while making CSS Grid more complex will make it harder to extend in the future, we believe there’s an advantage to having these two types of grid layouts intertwined. This way the CSS Working Group will always define all new additions for both modular and columnar grids. There won’t be something added to display: grid
that will be left out of display: masonry
, or vice versa. For example, many developers want CSS Grid Level 4 to provide a mechanism for styling grid areas and grid lines — perhaps a way to add a background color to a track, or create a rule line in a gap. It’d be great to ensure that will work for both modular and columnar grids from Day 1.
Another argument made by advocates of display: masonry
is that that masonry is conceptually a fundamentally different layout type from CSS Grid, and therefore should have its own display type. They often describe CSS Grid as inherently being about “lining things up in two-dimensions”, and since masonry only lines things up in one dimension, “it’s not a grid”. (In fact, some have advocated that Masonry is more like Flexbox, since “both line things up in one direction”.)
In many ways, your perspective on this question might depends on what you imagine a grid is.
Grids are an incredibly important aspect of graphic design. Grids are used to line up text, images and other content in a regular pattern. They help readability and usability by making things predictable.
You can trace their use through thousands of years of history.
It wasn’t until the 20th century that European and American modernists started promoting the idea that “proper” graphic design grids should line content up in both directions — in rows as well as columns.
Even today, there is a lot of debate about which type of grid is the best grid or the only legitimate grid. Many designers claim a 12 column grid is the only correct way to design a web page — or 12 columns for “desktop”, 8 columns for “tablet”, and 4 columns for “phones”. At times designers have gotten quite religious about their ideas of what a “proper grid” looks like.
Mark Boulton argued for years that symmetrical columnar grids are incredibly formulaic and boring. He promoted the use of asymmetrical compound grids in design for the web. Today, luckily CSS Grid Level 1 makes it incredibly easy to create both asymmetrical grids and compound grids, giving designers the freedom to create what they want. But only if they also want all their grids to be a modular grids.
Both modular and columnar grids are in fact grids. And CSS Grid deserves the ability to also create columnar grids.
We believe there’s an opportunity for CSS to enable a rich history of design grids to come to the web — and would be greatly disappointed to see the new masonry feature limited to only allowing symmetrical columnar grids.
But what do you think?
This is where you come in. Try some demos of your own. Write about your thoughts on your own blog. Comment at the CSS Working Group in this issue.
Often, thinking about something theoretically and actually seeing it in use can be very different. To make sure the CSSWG gets the design of this feature correct, we need developers to gain some hands-on experience, and articulate your thoughts about what it’s like.
The WebKit team has been working on Masonry for a year and a half. It was first enabled by default in Safari Technology Preview 163 in February 2023. There’s a bit more polish needed, and details to work out (naming being one). But we would like to ship this feature soon. To do so, these fundamental questions need to be resolved.
Thank you for your help!
It’s likely masonry
is not the best name for this new value. Names in CSS are usually simple words that directly describe the result they create — like center
, under
, contain
, clip
, revert
, ltr
, always
, break-word
, hidden
, allow-end
, scale-down
, wrap
, smooth
.
The word “masonry” is more of a metaphor, where the meaning has to be explained with a backstory. Such a term is harder to remember for developers who do not speak English. And arguably, the syntax could just as easily be grid-template-rows: waterfall
instead, since that’s the dominant word for this layout used in certain regions, not masonry.
Plus, once you start to write a lot of code using this feature, it’s likely you’ll come to the realization that we did — this really isn’t about the layout used by Pinterest or other similar sites. This is a mechanism for telling the browser, “please create a grid, but without any rows.”
Perhaps the best syntax could be grid-template-rows: none;
to convey “please do not give me any rows”. Sadly, it’s too late to use this name, because none
is the default value for grid-template-*
and means “please give me only implicit rows, no explicit ones”.
Instead we could use the name off
to convey “please turn off the grid in the row direction, and give me only columns”.
main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
grid-template-rows: off;
}
The CSSWG is debating this name in this issue. If you have ideas or preferences for a name, please join that discussion.
Meanwhile, masonry
is the value that is currently implemented in Safari Technology Preview, since that’s what the Editor’s Draft currently uses. And so that’s what we used in our demos above, and what you should use in yours. But do expect the name of this value to change in the future. And perhaps prepare for a future where we call this “columnar grid” or “Grid Level 3” instead of “Masonry”.
This release includes WebKit changes between: 276247@main…276863@main.
white-space
to a non-default value dynamically on a whitespace or a new line. (276277@main) (92559818) @scope
. (276345@main) (124640124) @scope
limit making the element out of scope. (276359@main) (124956673) gap
as-is in serialized and computed values. (276794@main) (125335787) WEBKIT_KEYFRAMES_RULE
and WEBKIT_KEYFRAME_RULE
in CSSRule. (276264@main) (97084520) Object.groupBy
and Map.groupBy
to work for non-objects. (276736@main) (125485685) Array.fromAsync
to not call the Array constructor twice. (276752@main) (125509304) pointer-events: none
is placed over it. (276807@main) (118936715) currentTime
to be further than the gap’s start time. (276761@main) (124186726) sampleRate
and numberOfChanges
to be required and non-zero in a valid AudioEncoderConfig. (276413@main) (125107934) URL.parse()
. (276656@main) (125376520) shadowRootDelegatesFocus
and shadowRootClonable
to <template>
. (276631@main) (125401993) <picture>
element displaying the same image twice. (276754@main) (123795045) In order to make these improvements, we made an extensive use of our performance testing infrastructure. It’s integrated with our continuous integration, and provides the capability to schedule A/B tests. This allows engineers to quickly test out performance optimizations and catch new performance regressions.
Proper tooling support is the key to identifying and addressing performance bottlenecks. We defined new internal JSON format for JavaScriptCore sampling profiler output to dump and process them offline. It includes a script which processes and generates analysis of hot functions and hot byte codes for JavaScriptCore. We also added FlameGraph generation tool for the dumped sampling profiler output which visualizes performance bottlenecks. In addition, we added support for JITDump generation on Darwin platforms to dump JIT related information during execution. And we improved generated JITDump information for easy use as well. These tooling improvements allowed us to quickly identify bottlenecks across Speedometer 3.0.
Megamorphic IC offers faster property access when one property access site observes many different object types and/or property names. We observed that some frameworks such as React contain a megamorphic property access. This led us to continuously improve JavaScriptCore’s megamorphic property access optimizations: expanding put megamorphic IC, adding megamorphic IC for the in
operation, and adding generic improvements for megamorphic IC.
Call IC offers faster function calls by caching call targets inline. We redesigned Call IC and we integrated two different architectures into different tiers of Just-In-Time (JIT) compilers. Lower level tiers use Call IC without any JIT code generation and the highest level tier uses JIT code generatiton with the fastest Call IC. There is a tradeoff between code generation time and code efficiency, and JavaScriptCore performs a balancing act between them to achieve the best performance across different tiers.
Speedometer 3.0 also presented new optimization opportunities to our JSON implementations as they contain more non-ASCII characters than before. We made our fast JSON stringifier work for unicode characters. We also analyzed profile data carefully and made JSON.parse
faster than ever.
There are many tradeoffs when inlining functions in JavaScript. For example, inline functions can more aggressively increase the total bytecode size and may cause memory bandwidth to become a new bottleneck. The amount of instruction cache available in CPU can also influence how effective a given inlining strategy is. And the calculus of these tradeoffs change over time as we make more improvements to JavaScriptCore such as adding new bytecode instruction and changes to DFG’s numerous optimization phases. We took the release of the new Speedometer 3.0 benchmark as an opportunity to adjust inlining heuristics based on data collected in modern Apple silicon Macs with the latest JavaScriptCore.
Due to complicated conditions, JavaScriptCore eagerly destroyed CodeBlock and JIT code when GC detects they are dead. Since these destructions are costly, they should be delayed and processed while the browser is idle. We made changes so that they are now destroyed lazily, during idle time in most cases.
In addition, we noticed that a significant amount of time goes into performing garbage collection and incremental sweeping across all subtests in both Speedometer 2.1 and 3.0. In particular, if a subtest allocated a large number of JavaScript objects on the heap, we would often spend a significant amount of time in subsequent subtests collecting these objects. This had several effects:
At a high level, we realized that some of this work could be performed opportunistically in between rendering updates — that is, during idle time — instead of triggering in the middle of subtests. To achieve this, we introduced a new mechanism in WebCore to provide hints to JavaScriptCore to opportunistically perform scheduled work after the previous rendering update has completed until a given deadline (determined by the estimated remaining time until the next rendering update). The opportunistic task scheduler also accounts for imminently scheduled zero delay timers or pending requestAnimationFrame
callbacks: if it observes either, it’s less likely to schedule opportunistic work in order to avoid interference with imminent script execution. We currently perform a couple types of opportunistically scheduled tasks:
Overall, this strategy yields a 6.5% total improvement in Speedometer 3.0*, decreasing the time spent in every subtest by a significant margin, and a 6.9% total improvement in Speedometer 2.1*, significantly decreasing the time spent in nearly all subtests.
* macOS 14.4, MacBook Air (M2, 2022)
We extensively reviewed all Speedometer 3.0 subtests and did many optimizations for realistic use cases. The examples include but are not limited to: faster Object.assign with empty objects, improving object spread performance, and so on.
Improving DOM code is Speedometer’s namesake, and that’s exactly what we did. For example, we now store the NodeType in the Node object itself instead of relying on a virtual function call. We also made DOMParser use a fast parser, improved its support of li
elements, and made DOMParser not construct a redundant DocumentFragment. Together, these changes improved TodoMVC-JavaScript-ES5 by ~20%. We also eliminated O(n^2) behavior in the fast parser for about ~0.5% overall progression in Speedometer 3.0. We also made input
elements construct their user-agent shadow tree lazily during construction and cloning, the latter of which is new in Speedometer 3.0 due to web components and Lit tests. We devirtualized many functions and inlined more functions to reduce the function call overheads. We carefully reviewed performance profile data and removed inefficiency in hot paths like repeated reparsing of the same URLs.
We landed a number of important optimizations in our layout and rendering code. First off, most type checks performed on RenderObject
are now done using an inline enum class instead of virtual function calls, this alone is responsible for around ~0.7% of overall progression in Speedometer 3.0.
We also optimized the way we compute the properties animated by Web Animations code. Previously, we were enumerating every animatable properties while resolving transition: all
. We optimized this code to only enumerate affected properties. This was ~0.7% overall Speedometer 3.0 progression. Animating elements can now be resolved without fully recomputing their style unless necessary for correctness.
Speedometer 3.0 content, like many modern web sites, uses CSS custom properties extensively. We implemented significant optimizations to improve their performance. Most custom property references are now resolved via fast cache lookups, avoiding expensive style resolution time property parsing. Custom properties are now stored in a new hierarchical data structure that reduces memory usage as well.
One key component of WebKit styling performance is a cache (called “matched declarations cache”) that maps directly from a set of CSS declarations to the final element style, avoiding repeating expensive style building steps for identically styled elements. We significantly improved the hit rate of this cache.
We also improved styling performance of author shadow trees, allowing trees with identical styles to share style data more effectively.
We fixed a number of performance bottlenecks in inline layout engine as well. Eliminating complex text path in Editor-TipTap was a major ~7% overall improvement. To understand this optimization, WebKit has two different code paths for text layout: the simple text path, which uses low level font API to access raw font data, and the complex text path, which uses CoreText for complex shaping and ligatures. The simple text path is faster but it does not cover all the edge cases. The complex text path has full coverage but is slower than the simple text path.
Previously, we were taking the complex text path whenever a non-default value of font-feature or font-variant was used. This is because historically the simple text path wouldn’t support these operations. However, we noticed that the only feature of these still missing in the simple text path was font-variant-caps. By implementing font-variant-caps support for the simple text path, we allowed the simple text path to handle the benchmark content. This resulted in 4.5x improvement in Editor-TipTap subtest, and ~7% overall progression in Speedometer 3.0.
In addition to improving the handling of text content in WebKit, we also worked with CoreText team to avoid unnecessary work in laying out glyphs. This resulted in ~0.5% overall progression in Speedometer 3.0, and these performance gains will benefit not just WebKit but other frameworks and applications that use CoreText.
Another area we landed many optimizations for is SVG. Speedometer 3.0 contains a fair bit of SVG content in test cases such as React-Stockcharts-SVG. We were spending a lot of time computing the bounding box for repaint by creating GraphicsContext, applying all styles, and actually drawing strokes in CoreGraphics. Here, we adopted Blink’s optimization to approximate bounding box and made ~6% improvement in React-Stockcharts-SVG subtest. We also eliminated O(n^2) algorithm in SVG text layout code, which made some SVG content load a lot quicker.
Another optimization we did involve improving the cache hit rate of IOSurface. An IOSurface is a bitmap image buffer we use to paint web contents into. Since creating this object is rather expensive, we have a cache of IOSurface objects based on their dimensions. We observed that the cache hit rate was rather low (~30%) so we increased the cache size from 64MB to 256MB on macOS and improved the cache hit rate by 2.7x to ~80%, improving the overall Speedometer 3.0 score by ~0.7%. In practice, this means lower latency for canvas operations and other painting operations.
Previously, we required a synchronous IPC call from the Web Process to the GPU process to determine which of the existing buffers had been released by CoreAnimation and was suitable to use for the next frame. We optimized this by having the GPUP just select (or allocate) an appropriate buffer, and direct all incoming drawing commands to the right destination without requiring any response. We also changed the delivery of any newly allocated IOSurface handles to go via a background helper thread, rather than blocking the Web Process’s main thread.
Updates to compositing layers are now batched, and flushed during rendering updates, rather than computed during every layout. This significantly reduces the cost of script-incurred layout flushes.
In addition to optimizations we made in WebKit, there were a handful of optimizations for Safari as well.
One area we looked at was Safari’s AutoFill code. Safari uses JavaScript to implement its AutoFill logic, and this execution time was showing up in the Speedometer 3.0 profile. We made this code significantly faster by waiting for the contents of the page to settle before performing some work for AutoFill. This includes coalescing handling of newly focused fields until after the page had finished loading when possible, and moving lower-priority work out of the critical path of loading and presenting the page for long-loading pages. This was responsible for ~13% progression in TodoMVC-React-Complex-DOM and ~1% progression in numerous other tests, improving the overall Speedometer 3.0 score by ~0.9%.
In addition to making the above code changes, we also adjusted our profile-guided optimizations to take Speedometer 3.0 into account. This allowed us to improve the overall Speedometer 3.0 score by 1~1.6%. It’s worth noting that we observed an intricate interaction between making code changes and profile-guided optimizations. We sometimes don’t observe an immediate improvement in the overall Speedometer 3.0 score when we eliminate, or reduce the runtime cost of a particular code path until the daily update of profile-guided optimizations kicks. This is because the modified or newly added code has to benefit from profile-guided optimizations before it can show a measurable difference. In some cases, we even observed that a performance optimization initially results in a performance degradation until the profile-guided optimizations are updated.
With all these optimizations and dozens more, we were able to improve the overall Speedometer 3.0 score by ~60% between Safari 17.0 and Safari 17.4. Even though individual progressions were often less than 1%, over time, they all stacked up together to make a big difference. Because some of these optimizations also benefited Speedometer 2.1, Safari 17.4 is also ~13% faster than Safari 17.0 on Speedometer 2.1. We’re thrilled to deliver these performance improvements to our users allowing web developers to build websites and web apps that are more responsive and snappier than ever.
]]>This release includes WebKit changes between: 274942@main…276246@main.
ariaBrailleLabel
and ariaBrailleRoleDescription
element reflection properties. (275591@main) (123926949) datetime
values being exposed to assistive technologies in the wrong timezone. (275265@main) (123522296) datetime
value being exposed to assistive technologies for datetime-local
inputs. (275548@main) (123803281) dd
, details
, dt
, em
, hgroup
, option
, s
, and strong
. (276240@main) (124641956) excludeCredentials
property being ignored during a passkey registration request. (276006@main) (124405037) getComputedStyle()
to work with functional pseudo-elements like ::highlight()
. (274846@main) (117864743) light-dark()
. (275645@main) (121285450) :empty
selector to work with animations. (275832@main) (122838142) !important
. (275104@main) (123374708) <select multiple>
scrollbars to match the used color scheme. (275505@main) (123807167) <select>
rendering in dark mode. (275532@main) (123845293) String.prototype.replace
to not take the fast path if the pattern is RegExp Object and the lastIndex
is not numeric. (275255@main) (101122567) Symbol.species
getters to not share a single JS Function. (275064@main) (120416817) [[Construct]]
of derived class. (275353@main) (121959506) false
. (276130@main) (122093956) emitReturn()
to load this
value from arrow function lexical environment prior to the TDZ check. (275425@main) (122430056) instanceof
to not get RHS prototype when LHS is primitive. (275318@main) (123629166) loadeddata
events for <audio>
and <video>
on page load. (275997@main) (124079735) (FB13675360) stop-color
if it is not rendered in the page. (274997@main) (123262508) cssText
setter to change the style
attribute when the serialization differs. (276176@main) (29861252) (FB5535475) getGamepads()
to no longer trigger an insecure contexts warning. (274962@main) (123039555) HashChangeEvent
‘s non-standard initHashChangeEvent()
method. (276232@main) (124736521) !important
not getting overridden when using the interactive editing controls. (275389@main) (112080113) text-indent
property suggesting prefixed properties instead of each-line
or hanging
. (274979@main) (123240715) background
autocompletion suggestion to include repeating-conic-gradient
. (275160@main) (123428709) WebXR now includes a more natural and privacy-preserving method for interaction — the new transient-pointer
input mode — available for Safari 17.4 in visionOS 1.1. Let’s explore how natural input for WebXR works, and how to leverage it when developing a WebXR experience for Apple Vision Pro.
WebXR lets you transform 3D experiences created in WebGL into immersive, spatial experiences available directly in the browser. Defined in web standards governed by the W3C, WebXR is an excellent choice for presenting immersive content to your users.
One challenge, though, is that because it’s completely immersive — and rendered entirely through WebGL — it’s not possible to provide interaction via DOM content or the traditional two-dimensional input available on a typical web page via a mouse or trackpad. This, combined with the fact that a spatial experience requires spatial input, means WebXR needs a totally new interaction model.
The interaction model of visionOS, known as natural input, uses a combination of eyes and hands for input. A user simply looks at a target and taps their fingers to interact. (Or uses the alternatives provided by accessibility features.)
The initial web standards for WebXR assumed all input would be provided by persistent hardware controllers. Since the natural input interaction model of visionOS differs from XR platforms which rely on listening to physical controllers and button presses, many existing WebXR experiences won’t work as intended on Apple Vision Pro.
We’ve been collaborating in the W3C to incorporate support for the interaction model of visionOS into WebXR. And we’re excited to help the WebXR community add support to popular WebXR frameworks.
Since WebXR in visionOS requires the use of spatial inputs, rather than a trackpad, touch, or mouse, and the DOM isn’t visible within a WebXR session, inputs are provided as part of the XRSession itself. Events related to the input, e.g. select
, selectstart
and selectend
are then dispatched from the session object. The XRInputSources are available within the xrSession.inputSources
array. Because the default WebXR input in visionOS is transient, that array is empty — until the user pinches. At that moment, a new input is added to the array, and the session fires an inputsourceschange
event followed by a selectstart
event. You can use these for detecting the start of the gesture. To differentiate this new input type, it has a targetRayMode
of transient-pointer
.
The XRInputSource
contains references to two different positions in space related to the input: the targetRaySpace
and the gripSpace
. targetRaySpace
represents the user’s gaze direction, this space begins with its origin between the user’s eyes and points to what the user was looking at the start of the gesture. The targetRaySpace
is initially set to the direction of the user’s gaze but is updated with the movement of their hand rather than their eyes — that is, a movement of the hand to the left will move that ray to the left as well. The gripSpace
represents the location of the user’s pinching fingers at the current point in time.
The targetRaySpace
can be used for finding what the user wanted to interact with when they started the gesture typically by raycasting into the scene and picking the intersected object and the gripSpace can be used for the positioning and orientation of objects near the user’s hand for interaction purposes, e.g. to flip a switch, turn a dial or pick up an item from the virtual environment. To learn more about finding the pose of the input, see Inputs and input sources on MDN.
Three events are fired from the session
object when the user releases the pinch.
First is a select
and selectEnd
event, both referencing the input as the event.inputSource
object. The session then fires a final inputsourceschange
event, indicating that this inputSource
has been removed.
As each input represents a single, tracked point in space per pinch, and because it exists only for the duration of the user’s pinch, significantly less data about a user’s motion is required overall.
WebXR on Safari in visionOS continues to support full hand tracking as well, supplying hand joint information for the duration of the experience. If the call to navigator.xr.requestSession
has included hand-tracking
as an additional feature and this is granted by the user, the first two inputs in the inputSources
list will be standard tracked-pointers
supplying this joint information. For information on requesting features in an XRSession, refer to the documentation at MDN. Because these inputs persist for the duration of the session, any transient-pointer
inputs will appear further down the list. The hand inputs are supplied for pose information only and do not trigger any events.
The new transient-pointer
input mode works in the visionOS simulator, so you can test your Web XR experience without needing Apple Vision Pro. To get started:
If you have a website open in Safari on macOS, you can easily trigger that page to open in Safari in visionOS Simulator. On Mac, select Develop > Open Page With > Apple Vision Pro. (If the Develop menu is not present in the menu bar, enable features for web developers.)
Whether you have Apple Vision Pro or are using visionOS Simulator, you’ll want to turn on support for WebXR. From the Home View, go to Settings > Apps > Safari > Advanced > Feature Flags and enable WebXR Device API
.
WebXR is provided in visionOS 1.1 for testing purposes only. If you encounter bugs in the browser, please don’t attempt to target a user-agent string to work around them. Instead, report them to us via Feedback Assistant.
Your experience probably is only looking at events which correspond to the first two inputs in the inputSources array. Many examples in the popular framework Three.js only act on events related to inputs at index 0 and 1. When hand tracking is enabled, the inputs corresponding to the hand tracking data appear in entries 0 and 1 but the transient-pointer
style inputs appear in entries 2 and 3.
The input’s targetRaySpace
originates between the user’s eyes and points in the direction of their gaze. To place an object in the user’s hand, instead attach objects to the input’s gripSpace
position, which corresponds to the location of the user’s pinch.
Some frameworks attempt to smooth out controller motion by interpolating the position of the controller each frame. This interpolated motion should be reset if a controller is disconnected and reconnected. You may need to file an issue with the framework author.
We can’t wait to see what you make. To share your thoughts on WebXR, find us on Mastodon at @ada@mastodon.social, @jensimmons@front-end.social, @jondavis@mastodon.social, or on X at @zachernuk. Or send a reply on X to @webkit. You can also follow WebKit on LinkedIn. If you run into any issues, we welcome your feedback (to include a sysdiagnose from Apple Vision Pro), or your WebKit bug report about Web Inspector or web platform features. Filing issues really does make a difference. Thanks.
]]>Setting written text vertically is commonly observed in East Asian languages. For example, Chinese, Japanese, and Korean (CJK) may be written vertically and read top-to-bottom, flowing in lines from right to left. Similarly, Traditional Mongolian is a vertical script that flows in lines from left to right.
Support for vertical text has been available in browsers for several years using the CSS writing-mode
property. However, until recently, support for the vertical-lr
and vertical-rl
values for form controls was inconsistent among all browsers. Consequently, as part of Interop 2023, the industry committed to working towards vertical writing mode support in form controls. We are excited to see cross-browser support improve considerably. And we are proud that Safari 17.4 brings support for vertical writing modes to form controls everywhere you find Safari.
Adopting vertical form controls is as simple as adding a CSS declaration using the writing-mode
property and applying it to your controls. For a right-to-left block flow direction, as observed in CJK languages, use writing-mode: vertical-rl
. Or, for a left-to-right block flow direction, use writing-mode: vertical-lr
.
button, textarea, progress, meter, input, select {
writing-mode: vertical-rl;
}
Support for vertical writing mode is available for the following elements: <button>
, <textarea>
, <progress>
, <meter>
, <input>
, and <select>
. WebKit is committed to supporting vertical writing modes on any new controls moving forward, including <input type="checkbox" switch>
, which was also added in Safari 17.4.
Note that in all browsers, any popup UIs associated with form controls, such as the menu for a <select>
element, or the color picker for an <input type="color">
are still displayed horizontally.
Adding vertical writing mode support for form control elements in WebKit was a large undertaking that involved over 10 unique types of controls. Below are some of the changes that were needed to support this feature in WebKit.
WebKit’s user-agent stylesheet made frequent use of physical properties, such as width
, height
, and margin
. In order to support vertical writing modes, this usage was updated to use logical properties, such as inline-size
and block-size
, where appropriate.
Form controls in WebKit make heavy use of custom layout code. While the interfaces were designed to support logical properties, the implementations often assumed a horizontal writing mode. We updated the methods to compute logical widths to ensure they considered the writing mode.
Additionally, custom baseline adjustment logic, which makes controls such as checkboxes and radio buttons look great alongside text, was updated to use the central baseline for vertical writing modes. This ensures that controls continue to look great alongside text in a vertical writing mode.
Rendering form controls with a vertical writing mode required unique changes depending on the control and system capabilities.
On macOS, WebKit’s form controls match the look and feel of the operating system. However, macOS itself does not support vertical writing modes. Consequently, some controls, such as <progress>
, are simply rotated after obtaining the system image in order to support vertical rendering.
Other controls cannot simply be rotated due to details such as shadows, for example <select>
, so we used a custom fallback rendering approach.
If a control was already custom painted in WebKit, it was updated to use logical coordinates rather than physical coordinates. We used this approach throughout the iOS rendering code and it was also necessary in our rewrite of the listbox (<select multiple>
) on macOS.
Finally, some controls, such as checkbox and radio buttons, did not require any rendering changes. As “glyph-like” controls, they look the same in all writing modes.
WebKit remains committed to improving internationalization support in our engine. By adding vertical writing mode support for form control elements in Safari 17.4, we hope to empower authors to create the best content for their local and global communities.
A special thanks goes to Tim Nguyen, who kicked off the vertical form controls project in WebKit, and Alan Baradlay, whose deep knowledge of CSS layout proved invaluable in driving the project to the finish line.
We love hearing from you. Send a tweet to @webkit to share your thoughts on this feature. Find us on Mastodon at @jensimmons@front-end.social and @jondavis@mastodon.social. You can also follow WebKit on LinkedIn. If you run into any issues, we welcome your WebKit bug reports on WebKit features like this. Reporting issues makes an enormous difference.
You can also download the latest Safari Technology Preview to try out new web platform features like this before they appear in a Safari beta.
]]>