WWDC Quick Look 💓 By SwiftGGTeam
What's new in CSS

What's new in CSS

Watch original video

Highlight

Safari adds Masonry layout preview, margin-trim, OKLCH/LCH/OKLAB/LAB color functions, color-mix(), relative color syntax, :user-valid/:user-invalid pseudo-classes, font-size-adjust, lh/rlh units, and @counter-style this year—making layouts simpler, colors richer, forms friendlier, and typography more precise.

Core Content

Layout: Masonry and margin-trim

Masonry layouts are common on the web, especially for content cards of varying height. Previously you either used CSS Multicolumn (content flows vertically down columns) or a JavaScript library. Multicolumn orders content vertically, which rarely matches design intent; JavaScript solutions are slower and more fragile than CSS.

Safari Technology Preview previews Masonry Layout as an extension of CSS Grid. A single line—grid-template-rows: masonry—packs items tightly in the row direction while keeping column definitions.

(01:15)

Unlike JavaScript libraries, combining Masonry with Grid lets you use Masonry in one dimension and Grid’s full power in the other: fr units, minmax(), fixed-width columns, and more.

margin-trim solves another common pain point. Child elements inside a container often have vertical margins that stack with the container’s padding, making top and bottom spacing larger than expected. The old fix was manually removing the first child’s top margin and the last child’s bottom margin—fragile when content changes.

margin-trim: block trims margins that touch the container edge. Safari 16.4 supports it; use margin-trim: inline for horizontal trimming.

(03:58)

Color: Beyond sRGB

The sRGB gamut represents only part of human-visible color. Display P3 shows 50% more colors; Apple has supported P3 in hardware and software since the 2015 iMac.

CSS adds four color functions to go beyond sRGB:

  • LCH and OKLCH: based on Lightness, Chroma, Hue
  • LAB and OKLAB: based on Lightness, green-red axis (A), blue-yellow axis (B)

These models are perceptually uniform and can represent any gamut, including Display P3 and future gamuts. Safari 15.0/15.4 supports them; Chrome, Edge, and Firefox joined Interop 2023 this year.

(06:51)

Relative Color Syntax derives new colors from existing ones:

/* 70% opacity version of blue */
rgb(from blue r g b / 0.7)

/* Half the lightness of a LAB color */
lab(from var(--brand-color) calc(l / 2) a b)

/* One-third the chroma of an OKLCH color */
oklch(from var(--accent) l calc(c / 3) h)

The color-mix() function blends two colors in a specified color space, default 50/50 or custom ratios:

color-mix(in oklab, white, blue)
color-mix(in oklch, white 30%, blue 30%) /* result has 60% opacity */

Gradients and color animations can specify color spaces too. The same white-to-blue gradient looks purple-ish in sRGB, bluer in OKLAB, and passes through yellow-green-cyan in OKLCH. The choice depends on the design effect.

(10:23)

Pseudo-classes: Smarter Form Validation

:valid and :invalid have a long-standing problem: empty fields are invalid on page load, showing errors before the user types.

:user-valid and :user-invalid use smarter algorithms for when to show validation state. Errors appear after the user leaves a field, not while they are still typing. Supported since Safari 16.5.

(16:55)

:has() also improved this year, supporting more pseudo-class combinations: :has(:lang()) styles based on whether specific languages appear on the page; :has() supports media pseudo-classes for conditional styling based on audio/video playback state.

The :dir() pseudo-class fills a gap in language direction support. Combined with logical properties (such as margin-inline-start), you can write CSS that works for both LTR and RTL.

(18:14)

Typography: Precise Font Control

lh and rlh units: 1lh equals the current element’s line height; 1rlh equals the root element’s line height. Spacing in layouts can stay in integer multiples of line height for vertical rhythm.

(20:18)

font-size-adjust: At the same font-size, different fonts can look very different in visual size. font-size-adjust adjusts visual size based on x-height ratio so fallback fonts in a stack look consistent.

Safari 16.4 supports basic usage (font-size-adjust: 0.47); Safari 17 adds the from-font value (auto-calculated) and two-value syntax (choose cap-height, ch-width, ic-width, or ic-height as the adjustment basis).

(22:25)

text-box-trim (preview): trims extra space fonts reserve in the text box, fixing imprecise vertical centering. Still evolving—the property was renamed from leading-trim to text-box-trim and details may change.

(26:42)

@counter-style: custom counter styles. Define any counting system—Serbo-Croatian letters, binary, cycling emoji, and more. W3C provides hundreds of ready-made counter styles to copy.

(28:53)

Detailed Content

Combining Masonry Layout with Grid

/* Basic masonry: auto-fill columns */
main {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  grid-template-rows: masonry;
  gap: 1rem;
}

/* Columns of different widths */
main {
  display: grid;
  grid-template-columns: 1fr 2fr 3fr;
  grid-template-rows: masonry;
  gap: 1rem;
}

/* Fixed + flexible + minmax combination */
main {
  display: grid;
  grid-template-columns: 10rem 1fr minmax(100px, 300px);
  grid-template-rows: masonry;
  gap: 1rem;
}

Key points:

  • grid-template-rows: masonry enables masonry mode (row-direction packing)
  • grid-template-columns defines columns with full Grid capabilities
  • Items pack in row direction, each placed in the column that minimizes overall height
  • Currently available only in Safari Technology Preview
  • Part of the CSS Grid spec, not a separate layout mechanism

margin-trim Fixes Edge Margin Collapse

/* Old approach: manually remove edge margins */
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

/* New approach: one line auto-trims */
.card {
  background-color: #fcf5e7;
  padding: 2rlh;
  margin-trim: block;
}

.card h2, .card p {
  margin: 1rlh 0;
}

Key points:

  • margin-trim: block trims vertical (block-axis) margins touching the container edge
  • margin-trim: inline trims horizontal (inline-axis) margins
  • No need to know first/last child element types
  • Adapts automatically when content changes
  • Supported since Safari 16.4

Wide-Gamut Color Definitions

/* sRGB colors (traditional) */
.brand-red {
  color: #ff0000;
  color: rgb(255, 0, 0);
  color: hsl(0, 100%, 50%);
}

/* Display P3 colors */
.brand-red-p3 {
  color: color(display-p3 1 0 0);
}

/* OKLCH: perceptually uniform color model */
.brand-red-oklch {
  color: oklch(62.8% 0.257 29.2);
}

/* Conditionally apply P3 colors */
@media (color-gamut: p3) {
  .brand-red {
    color: color(display-p3 1 0 0);
  }
}

Key points:

  • color(display-p3 1 0 0) defines pure red, richer than sRGB red on P3 displays
  • oklch(62.8% 0.257 29.2) defines color with a perceptually uniform model
  • @media (color-gamut: p3) detects P3 gamut support
  • Progressive enhancement: sRGB devices see standard colors, P3 devices see richer colors

Relative Color Syntax

:root {
  --brand-blue: oklch(50% 0.2 250);
}

/* Variants from brand color */
.brand-blue-light {
  color: oklch(from var(--brand-blue) calc(l + 20%) c h);
}

.brand-blue-dark {
  color: oklch(from var(--brand-blue) calc(l - 15%) c h);
}

.brand-blue-transparent {
  color: oklch(from var(--brand-blue) l c h / 0.5);
}

/* Mix current text color */
a:hover {
  color: color-mix(in oklab, currentColor 60%, white);
}

Key points:

  • from var(--brand-blue) specifies the base color
  • calc(l + 20%) adjusts lightness for a lighter variant
  • c and h unchanged inherit chroma and hue from the original
  • / 0.5 sets opacity
  • color-mix(in oklab, currentColor 60%, white) blends current text color with white

Gradient Color Space Comparison

/* sRGB gradient: purple-ish transition */
.gradient-srgb {
  background: linear-gradient(in srgb, white, blue);
}

/* OKLAB gradient: more natural blue transition */
.gradient-oklab {
  background: linear-gradient(in oklab, white, blue);
}

/* OKLCH gradient: passes through yellow-green-cyan */
.gradient-oklch {
  background: linear-gradient(in oklch, white, blue);
}

/* LCH gradient: similar to OKLCH but slightly different */
.gradient-lch {
  background: linear-gradient(in lch, white, blue);
}

Key points:

  • in srgb explicitly computes the gradient in sRGB color space
  • in oklab computes in OKLAB space for more natural transitions
  • Different color spaces produce different intermediate colors—no absolute right or wrong
  • Choice depends on design needs and expected visual effect

Form Validation Pseudo-classes

/* Old approach: :invalid shows errors on page load */
.form-group:has(input:invalid) label::before {
  content: "✗ ";
  color: red;
}

/* New approach: :user-invalid shows errors only after interaction */
.form-group:has(input:user-invalid) label::before {
  content: "✗ ";
  color: red;
}

.form-group:has(input:user-valid) label::before {
  content: "✓ ";
  color: green;
}

/* Complete form styling with :has() */
input:user-invalid {
  border-color: #dc2626;
  background-color: #fef2f2;
}

input:user-valid {
  border-color: #16a34a;
  background-color: #f0fdf4;
}

Key points:

  • :user-invalid triggers after the user leaves a field with invalid content
  • :user-valid triggers after correct input
  • No errors on page load or while the user is still typing
  • Combine with :has() to style parent elements containing invalid inputs
  • Supported since Safari 16.5

font-size-adjust for Uniform Visual Size

/* Basic usage: specify x-height ratio */
article {
  font-family: "Iowan Old Style", Georgia, serif;
  font-size-adjust: 0.47;
}

article code {
  font-family: "SF Mono", Courier, monospace;
  /* Browser auto-adjusts so x-height matches 0.47 */
}

/* Safari 17: from-font auto-calculates */
article {
  font-size-adjust: from-font;
}

/* Safari 17: two-value syntax using cap-height */
article {
  font-size-adjust: cap-height from-font;
}

Key points:

  • font-size-adjust: 0.47 makes all fonts’ x-height equal 47% of the specified font-size
  • from-font lets the browser auto-calculate the primary font’s ratio
  • Two-value syntax can choose ex-height (default), cap-height, ch-width, ic-width, or ic-height
  • Fixes inconsistent visual size among fallback fonts in a stack
  • Safari 16.4 supports basic usage; Safari 17 supports advanced usage

@counter-style Custom Counters

/* Serbo-Croatian alphabetic counter */
@counter-style serbo-croatian {
  system: alphabetic;
  symbols: "A" "B" "V" "G" "D" "Đ" "E" "Ž" "Z" "I" "J" "K" "L" "Lj" "M" "N" "Nj" "O" "P" "R" "S" "T" "Ć" "U" "F" "H" "C" "Č" "Dž" "Š";
}

ol.serbian {
  list-style: serbo-croatian;
}

/* Binary counter */
@counter-style binary {
  system: numeric;
  symbols: "0" "1";
  pad: 4 "0";
}

ol.binary {
  list-style: binary;
}

/* Cycling emoji */
@counter-style emoji-cycle {
  system: cyclic;
  symbols: "🐶" "🐱" "🐰";
}

ol.pets {
  list-style: emoji-cycle;
}

/* For heading numbering */
@counter-style section-number {
  system: numeric;
  symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10";
}

h2 {
  counter-increment: section;
}
h2::before {
  content: counter(section, section-number) ". ";
}

Key points:

  • @counter-style defines custom counter styles
  • system specifies counter type: alphabetic, numeric, cyclic, etc.
  • symbols lists symbols used for counting
  • pad sets minimum digits and pad character
  • Usable in list-style and the counter() function
  • W3C provides hundreds of ready-made counter styles

Core Takeaways

1. Replace manual edge margin cleanup with margin-trim

  • What to do: Use margin-trim: block in cards, article containers, and similar components to auto-trim edge margins
  • Why it matters: Eliminates maintenance of manual :first-child/:last-child rules; adapts when content changes
  • How to start: Find all CSS that manually cleans edge margins and replace with margin-trim: block or margin-trim: inline

2. Build design system palettes with OKLCH

  • What to do: Migrate design system colors from HEX/HSL to OKLCH; generate variants with Relative Color Syntax
  • Why it matters: OKLCH is perceptually uniform—lightness adjustments (calc(l + 20%)) produce consistent visual changes across hues; dark mode adaptation is more natural
  • How to start: Define base OKLCH colors, generate light/dark/transparent variants with oklch(from ...), use @media (color-gamut: p3) for richer colors on supported devices

3. Improve forms with :user-invalid

  • What to do: Migrate form validation styling from :invalid to :user-invalid
  • Why it matters: Users do not see errors before they start typing—errors show only after leaving a field with invalid content
  • How to start: Search globally for :invalid and replace with :user-invalid in form contexts; style parents with :has(input:user-invalid)

4. Unify code block visual size with font-size-adjust

  • What to do: Add font-size-adjust to areas containing monospace fonts so code matches body text visually
  • Why it matters: Monospace fonts often look smaller than body text, making code appear “indented”
  • How to start: Add font-size-adjust: from-font (Safari 17) on code, pre, or parent elements containing code, or manually calculate a suitable ratio

5. Localize list numbering with @counter-style

  • What to do: Define counter styles for content in different languages
  • Why it matters: Default Western Arabic numerals are not appropriate for all languages; custom counters match local conventions
  • How to start: Copy needed styles from W3C Ready-made Counter Styles, add with @counter-style, apply to lists in the corresponding language

Comments

GitHub Issues · utterances