Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 App ServicesSafari & Web

WWDC25 · 47 min · App Services / Safari & Web

What’s new in Safari and WebKit

Learn how the latest web technologies in Safari and WebKit can help you create incredible experiences. We’ll highlight different CSS features and how they work, including scroll driven animation, cross document view transitions, and anchor positioning. We’ll also explore new media support across audio, video, images, and icons.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 28 snippets

Progress bar code scroll() example xml · at 6:18 ↗
footer::after {
  content: "";
  height: 1em;
  width: 100%;
  background: var(--yellow);
  left: 0;
  bottom: 0;
  position: fixed;
  transform-origin: top left;
  animation: progress-scale linear;
  animation-timeline: scroll();
}

@keyframes progress-scale {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
html an css of text blocks showcasing different code topics xml · at 8:36 ↗
<section class="topics">
  <h3>What you can learn:</h3>
  <ul class="topics">
     <li class="topic-item">Web Development</li>
     <li class="topic-item">Computer Science</li>
     <li class="topic-item">Data Science</li>
     <!-- additional HTML... -->
  </ul>
</section>

.topic-item {
  background: var(--yellow);  
  border: 1px solid var(--gray);
  /* additional CSS... */  
}
text blocks twisting from the left - animation xml · at 9:12 ↗
@keyframes in-from-left {
  from {
    opacity: 0;
    transform: scale(.8) rotate(-90deg)   
               translateY(15vh);
  }
}
text blocks twisting from the middle - animation xml · at 9:18 ↗
@keyframes in-from-middle {
  from {
    opacity: 0;
    transform: scale(.8)   
               translateY(15vh);
  }
text blocks twisting from the right - animation xml · at 9:24 ↗
@keyframes in-from-right {
  from {
    opacity: 0;
    transform: scale(.8) rotate(90deg)   
               translateY(15vh);
  }
}
view() timeline example with timeline and range xml · at 10:07 ↗
.topic-item {
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range:
  &:nth-child(3n + 1) { animation-name: in-from-left; }
  &:nth-child(3n + 2) { animation-name: in-from-middle; }
  &:nth-child(3n + 3) { animation-name: in-from-right; }
}
animation range 50% xml · at 12:20 ↗
.topic-item {
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: 0% 50%;
  &:nth-child(3n + 1) { animation-name: in-from-left; }
  &:nth-child(3n + 2) { animation-name: in-from-middle; }
  &:nth-child(3n + 3) { animation-name: in-from-right; }
}
simple cross document view transition code xml · at 14:20 ↗
@view-transition {
    navigation: auto;
}
adding media query for reduced motion xml · at 16:00 ↗
@view-transition { navigation: auto; }

@media not (prefers-reduced-motion) {
  @keyframes slide-in {
    from { translate: 100vw 0; }
  }
  @keyframes slide-out {
    to { translate: -100vw 0; }
  }
}
adding ids to html for cross document view transition xml · at 16:22 ↗
<body>
  <nav>
    <!-- additional HTML... -->
  </nav>

    <section class="hero">
      <div class="hero-image">
      <!-- additional HTML... -->
  </main>
  <footer>
    <!-- additional HTML... -->
  </footer>
<body>
slide effect for cross document view transition xml · at 16:58 ↗
@view-transition { navigation: auto; }

@media not (prefers-reduced-motion) {
  #school-info {
    view-transition-name: main-body;
  }
  ::view-transition-old(main-body) {

  }
  ::view-transition-new(main-body) {

  }
  @keyframes slide-in {
    from { translate:e100vw 0; }
	}
}
nav bar and profile menu xml · at 19:48 ↗
<nav>
  <h1 class="logo">A-School of Code</h1>
  <ul>
    <li>Courses</li>
    <li>Cohorts</li>
    <li class="profile">
      <img src="https://example.com/saron.jpeg" alt="woman speaking"/>
    </li>
  </ul>
</nav>

<ul class="profile-menu">
  <li>Account</li>
  <li>Settings</li>
  <li>Profile</li>
  <li>Billing</li>
</ul>
adding popover attributes xml · at 20:37 ↗
<ul class="profile-menu" id="profile-menu" popover>
  <li>Account</li>
  <li>Settings</li>
  <li>Profile</li>
  <li>Billing</li>
</ul>
adding aria to popover target xml · at 20:51 ↗
<nav>
  <div class="wrapper">
    <h1 class="logo">A-School of Code</h1>
    <ul>
      <li>Courses</li>
      <li>Cohorts</li>
      <li class="profile">
        <button class="profile-button" aria-haspopup="true" popovertarget="profile-menu">                                                 >
          <img src="https://example.com/saron.jpg" alt="woman speaking"/>
        </button>
      </li>
    </ul>
  </div>
</nav>
establishing the anchor xml · at 21:58 ↗
.profile-button {
  anchor-name: --profile-button;
}

.profile-menu {
  position-anchor: --profile-button;
}
setting the target to top right xml · at 23:25 ↗
.profile-menu {
  position-anchor: --profile-button;
  position-area: top right;
}
setting the target to bottom center swift · at 23:39 ↗
.profile-menu {
  position-anchor: --profile-button;
  position-area: bottom center;
}
setting the target to span right xml · at 24:16 ↗
.profile-menu {
  position-anchor: --profile-button;
  position-area: span-right;
}
setting the target to span left xml · at 24:17 ↗
.profile-menu {
  position-anchor: --profile-button;
  position-area: span-left;
}
intro to the anchor() function xml · at 27:30 ↗
.profile-button {
  anchor-name: --profile-button;
}

.profile-menu {
  position-anchor: --profile-button;
  position: absolute;
  top: anchor(bottom);
  left: anchor(left);
}
using calc and units in anchor() function xml · at 28:26 ↗
.profile-button {
  anchor-name: --profile-button;
}

.profile-menu {
  position-anchor: --profile-button;
  position: absolute;
  top: anchor(bottom);
  left: calc(anchor(left) + 1.5em);
}
adding a text gradient xml · at 29:43 ↗
.logo {
  background-image: linear-gradient(to 
                    bottom right in hsl, 
                    yellow, orange);
  background-clip: text;
  color: transparent;
}
adding a gradient to border xml · at 31:05 ↗
.primary-btn {
  background-image: linear-gradient(to 
                    bottom right in hsl, 
                    yellow, orange);
  background-clip: border-area;
  border-color: transparent;
  background-origin: border-box;
}
shorthand for adding gradient to border xml · at 32:15 ↗
.primary-btn {
  background: border-area linear-gradient(to bottom right in hsl, yellow, orange);
  border-color: transparent;
}
arrow shape using path xml · at 33:33 ↗
.review-shape {
  clip-path: path("M0 0 L 500 0 L 600 
                   100 L 500 200 L 0 
                   200 Q 100 100 0 0 z");
}
arrow shape using shape() xml · at 35:01 ↗
.review-shape {
  clip-path: shape(from top left,
    line to calc(100% - 50cqh) 0%,
    line to 100% 50cqh,
    line to calc(100% - 50cqh) 100%,
    line to bottom left,
    curve to top left with 50cqh 50cqh,
    close);
}
dynamic range limit: no limit xml · at 41:42 ↗
img {
  dynamic-range-limit: no-limit;
}
dynamic range limit: standard xml · at 41:57 ↗
img {
  dynamic-range-limit: standard;
}

Resources