Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
153482 stories
·
33 followers

The power behind AI: Your brain

1 Share

Learn how tailoring AI to your thinking style can unlock surprising progress. This story shows how a neurodivergent creator used vibe coding to build practical tools and find renewed inspiration.


Nency Yera is a Revenue Strategy professional at Microsoft who discovered vibe coding three months ago and hasn’t stopped building since.

 

I have ADHD and no coding background. Until three months ago, the idea of building software made me want to close my laptop and walk away.

Every time I tried to learn something technical, the same thing happened. Launch a tutorial. Stare at wall of text. Brain opens 47 tabs, panics, and quietly exits. Then that voice shows up: This isn’t for people like you.

I believed that voice for a long time. I don’t anymore. Because it turns out that my brain was never the problem; my brain was the power source.

The wall

I work in Revenue Strategy at Microsoft. My job is operational excellence, not code. But I had a problem I couldn’t fix with spreadsheets: critical knowledge that leaders need to run their business was buried in slide decks and documents nobody could find.

I could see the solution in my head. I just couldn’t build it. The traditional path to learning—tutorials, documentation, courses? Linear steps. Walls of text. Built for brains that work differently than mine.

My brain needs fast feedback. Not a 40-page document. It needs someone to say, Here. One step. Try it. Good. Now the next one.

The unlock

So I decided to open GitHub Copilot in Visual Studio Code and do something I’d never done with any tool before. I told it exactly how my brain works.

“I have ADHD. Give me one step at a time. Use plain language. Wait for me before moving on. No jargon.”

And it took this information in.

 

A supportive, low‑jargon VS Code guide introduces a noncoder to the basics of building with AI.

 

When I said, “I’m confused,” it broke things into smaller pieces. When I said, “Too many words,” it switched to bullet points. When I prompted, “Explain this like I’m brand new,” it did. No judgment. No rush.

I broke things constantly. Red squiggly lines everywhere. But instead of panicking, I’d prompt, “Something’s broken. Help me fix it,” and Copilot walked me through it. Every mistake became a lesson I remembered because I learned it by doing.

I saved those preferences in a file called copilot-instructions.md. Now, with every conversation, Copilot already has the context it needs about how I think and process. That was the unlock. It turned a generic tool into a collaborator that adapts to me.

Then I made VS Code feel like mine. Dark theme. Rainbow-colored indentation so every line popped. Extensions that showed errors right where they happen instead of hiding them in some log I’d never find. Suddenly, my screen looked like something I wanted to stare at.

It stopped being scary. It started being fun.

What I built

In the first week, I built a website that organizes 205 performance metrics into one searchable hub. Prep that used to take leaders hours now takes minutes.

Then I built a dashboard that helps leaders track how teams adopt AI-powered tools across every operating unit. It’s deployed globally. What used to require hours of manual data gathering now runs on its own.

After that, I built newsletter templates that turn meeting transcripts into polished executive recaps sent to all levels of leadership. What previously took 45 minutes of writing now takes seconds.

And I built a cheat sheet that translates every scary technical term into plain English, because “commit” should just mean “save,” and “repo” should just mean “project folder.”

None of this required a computer science degree. It required what I already had: knowledge of the problem, ideas for the solution, and a brain that wouldn’t stop until the fix worked.

The leader who made it possible

I have to be honest. I didn’t do this alone. I have a leader who saw what was possible before I did. He ran live demos in team meetings. Created space to experiment. Never once said, “That’s not your job.” Instead, he said, “What do you want to build next?”

Not every neurodivergent person has that. And they should. Because when a leader gives someone permission to explore and learn at their own pace, what comes back is extraordinary.

People with neurodivergent brains don’t think in straight lines. We think in webs. In patterns. In creative leaps that don’t make sense to others until we build the thing and say, “See? This is what I meant.” Vibe coding finally lets us show our ideas instead of just describing them.

Your brain might work like mine

If you’ve ever closed a laptop in frustration or told yourself, “I’m not technical enough,” I want you to know something.

The problem was never your brain. The tools just weren’t ready for you yet. They are now.

You don’t need to learn to code. You need VS Code (a free app), GitHub Copilot (the AI inside it), and five minutes to tell it how you think. My first working page took 20 minutes. Start with the Vibe Coding Cheat Sheet. Customize your setup so it feels like yours. Then describe what you want to build, and watch it happen. As noted on the cheat sheet, you don't need to be a coder to build cool things.

AI without your brain is just an empty tool. Your expertise, your ideas, your way of thinking? That’s the power behind everything AI can do. And what you build, when someone finally gives you the tools and trusts you to run with them, will surprise everyone.

Especially yourself.

 

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

New investments to accelerate your migration to Microsoft 365

1 Share

Microsoft continues to power the digital transformation of customers with cutting edge, AI-enhanced productivity and collaboration tools. That's why we're excited to announce new investments to make it easier than ever to migrate your data to Microsoft 365 so you can take full advantage of these capabilities.

Introducing the Slack to Teams Migration Tool

When organizations explore adopting Microsoft 365, one of the first questions they ask is how they can migrate their data from their current platform to Microsoft 365. We have extensive experience and migration tools to support customers who want to move their emails, calendars, and files to Microsoft 365. But in the modern workplace, migrating chats and conversations can be just as important.

That’s why we’re introducing the Slack to Teams Migration Tool from Microsoft. This tool is designed to migrate public and private channels from Slack to Teams, where your most critical conversations take place. The tool provides first class integration with Teams so migrated messages appear as if they’ve been posted directly in Teams with the original author and creation timestamp preserved. It also maintains the rich message content you’d expect, including message formatting, links and images, @-mentions, standard emojis, threaded replies, and more. File attachments are no problem as those are migrated too.  And channel ownership and membership are preserved so that after the migration, participants can pick up right where they left off.

The Slack to Teams Migration Tool from Microsoft has no licensing cost. Customers simply need to provide an Azure Blob Storage subscription and container to temporarily store their Slack export files while the migration is taking place. (Azure Blob Storage subscriptions have their own cost.) Once the migration is complete, the Azure Blob Storage subscription is no longer needed.

This migration tool is a great option for customers interested in performing Slack migrations on their own, or as another tool for migration partners to help customers successfully transition to Microsoft 365. It's available now to all standard commercial customers worldwide. To learn more, see Introduction to the Slack to Teams Migration Tool.

Future investments in the Slack to Teams Migration Tool

These investments are an exciting start for this migration tool, but we’re not done yet. Over the coming year we plan to add even more features to the tool.  Some of those features include:

  • The ability to migrate group chats from Slack to Teams
  • The ability to migrate direct messages (1-on-1 chats) from Slack to Teams
  • The ability to migrate Slack canvas content into Microsoft Loop tabs in Teams
  • The ability to provide an experience in Teams that will be familiar to users migrating from Slack

The migration team would love to hear your feedback about any additional scenarios that are important to you. We operate preview programs for customers that want to engage closely with us to help shape the future investments in our migration tools. If you're interested in participating, please nominate yourself using this form:

Microsoft 365 Migration Preview Registration

New Microsoft 365 migration documentation center

Migration can be an intimidating project for some, especially if you have limited experience performing migrations. Having comprehensive yet easy-to-follow documentation at your fingertips to plan and execute your migration can be the key to a smooth experience.

That’s why we’ve just launched our new Microsoft 365 Migration documentation center. Here you’ll find consolidated documentation about all M365 migration scenarios. Whether it’s migration from external platforms to M365 or migrating from one M365 tenant to another, you’ll find the right documentation here. This documentation center covers the full set of features and workloads in M365, from emails, contacts, and calendars, to files, to chats and conversations.

Please check out our new migration documentation center at https://aka.ms/m365migrationdocs. As you read through the articles, let us know if they were helpful to you by giving them a thumbs up or thumbs down reaction. We take your feedback seriously and will use it to make sure our documentation meets your needs.

Summary

We're proud to add the Slack to Teams Migration Tool alongside our other migration tools, giving customers more options to accelerate their migration to Microsoft 365. And with our new migration documentation center guiding you along the way, you'll find the migration process much less intimidating than you may have expected.

Let's get migrating!

Troy Starr
Principal Product Manager
Microsoft 365 Migration Team

Read the whole story
alvinashcraft
14 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Validate APIs During Your Playwright Tests with Postman

1 Share

At the time of writing this article, Playwright ships over 8 million npm downloads a week, has over 88,000 GitHub stars, and lives inside 400,000+ repos on GitHub. It’s the default test runner for most new frontend projects in 2026.

But here’s the thing: every Playwright run already produces the most realistic API traffic your product makes, real auth, real payloads, real ordering, and almost every team throws it away. The UI test passes, the API layer underneath could be silently broken, and nobody notices until production.

We’re shipping two new Postman CLI commands and a Playwright plugin that fix that. You keep running Playwright the way you already do; Postman validates the API layer underneath in the same run.

Here’s the five-minute setup.

Make sure your project is already linked to a workspace through Postman’s native Git integration. If it isn’t, follow the setup guide first.

2. Install the CLI and the Playwright plugin

npm install -g postman-cli
npm install -D postman-playwright

3. Wrap your Playwright test fixture

Tell the plugin to attach network capture to your test fixture.

import { test as baseTest, expect } from '@playwright/test';
import { attachNetworkCapture } from 'postman-playwright';

const test = attachNetworkCapture(baseTest);

test('homepage has the expected title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

4. Initialize your application

In the root of your project, run:

postman app init

This is interactive. It asks which collections describe the APIs your app depends on, which environment to use, and which UI command to run. It writes the answers to postman.config.cjs in the root of your project. Commit it.

A typical postman.config.cjs looks like this:

module.exports = {
  command: 'npx playwright test',
  targets: {
    default: {
      environment: 'Local',
      collections: ['Event Service API'],
    },
    staging: {
      environment: 'Staging',
      collections: ['Event Service API'],
    },
  },
  filters: {
    urlPatterns: ['fonts.googleapis.com', 'fonts.gstatic.com', 'localhost:3007'],
    methods: [],
    headers: {},
  },
};

Targets are named runtime setups. The same repo can validate against default, staging, or prod without rewriting config, handy when you want different dependency surfaces in CI than on a developer laptop.

5. Run it

CI=true postman app test --command "npx playwright test"

CI=true makes the run non-interactive and pushes results to your workspace so they show up in Application Inventory. Without it, results stay on your terminal, useful while you’re iterating.

To run a different target:

postman app test --target staging

What you’ll see

In the terminal, you get a clear breakdown of:

  • which Playwright tests passed
  • which API calls were captured
  • which captured calls matched a collection request, and whether their pm.test assertions passed
  • which calls were unmatched (potential coverage gaps)

The same data lands in Application Inventory: a continuously-updated view of which APIs your application calls, which of those have test coverage, and which dependency contracts have drifted.

Keeping it usable: noise controls

Real apps are noisy. Fonts, analytics, hot-reload sockets, telemetry beacons, none of that should fail your build. Filter it out in postman.config.cjs:

filters: {
  urlPatterns: ['fonts.googleapis.com', 'localhost:3007', 'fonts.gstatic.com'],
  methods: ['OPTIONS'],
  headers: { 'x-client': 'analytics' },
},

Anything matching a filter is ignored before matching, so it can’t trigger an under- or over-coverage warning.

Don’t have API tests yet? Start by capturing them

The most common objection we hear is “we don’t have Postman collections for our APIs yet, so this isn’t useful to us.”

It is, and it’s the fastest way to get them.

postman app test --capture-only

In --capture-only mode, the CLI skips validation and instead generates a draft Postman collection from the traffic your UI tests produced. From there you can:

  • review the captured requests and trim the ones you don’t care about
  • ask Postman’s Agent Mode to generate pm.test assertions from the observed responses
  • save the result as a real collection and switch to postman app test for ongoing validation

The same UI traffic that powers validation today becomes the foundation of a growing API contract suite over time. No one has to sit down and write the first hundred tests by hand.

What this catches that UI tests don’t

A quick gut-check on why this is worth wiring up:

  • False green tests. UI looks correct, but the wrong endpoint was called or the payload is wrong.
  • Silent degradation. A backend error gets swallowed by a graceful UI fallback. The test passes; the user experience is broken.
  • Contract drift. Schema, header, or content-type changes the UI tolerates but downstream consumers won’t.
  • Poor diagnosability. A flaky failure becomes a 30-minute trace dive. Structured request/response captures turn it into a one-line answer.

The bottom line

You already have UI tests. You already have an API layer. You already have the most realistic traffic your product produces, every time CI runs.

postman app test makes that traffic do real work: validate contracts, surface drift, document dependencies, and stop UI-green-but-API-broken bugs before they ship.

If you want to try it on a real codebase, install the plugin, run postman app init, and let your next Playwright run do double duty.

Resources

The post Validate APIs During Your Playwright Tests with Postman appeared first on Postman Blog.

Read the whole story
alvinashcraft
29 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How HEFT Works in SharePoint Framework (SPFx)

1 Share

In the first article, we explored what HEFT is and why it was introduced into the SharePoint Framework. Now we turn to the practical side of the conversation: how HEFT actually works when you build a SharePoint Framework (SPFx) solution.

Although developers still run familiar commands such as build, serve, and package-solution, the underlying system executing those commands has changed. HEFT now orchestrates the entire toolchain behind the scenes. Understanding its workflow does not require deep technical knowledge of its internal configuration files. Instead, it requires understanding how HEFT structures work into phases and tasks, how it relies on the SPFx rig, and how it executes plugins in a predictable order.

The Foundation: Phases and Tasks

HEFT organizes work into phases, and each phase contains one or more tasks. This structure replaces the older script-driven approach previously used in SPFx.

A phase represents a logical stage in the build lifecycle. For example, there are phases for building, testing, and packaging. Each phase defines what types of work must occur at that stage.

Inside each phase are tasks. A task performs a specific responsibility, such as compiling TypeScript, processing Sass, running tests, or bundling assets. Tasks are implemented through plugins and are executed in a defined sequence.

This structured model means that HEFT does not simply run commands in order. Instead, it evaluates the current phase, identifies the tasks attached to it, and executes them according to the configuration defined by the SPFx rig and your project’s configuration files.

The SPFx Rig: Where the Defaults Come From

Every SPFx project references a rig configuration. The rig defines the default build behavior for SharePoint Framework projects.

The SPFx rig specifies:

  • Which phases are available
  • Which tasks belong to those phases
  • Which plugins implement those tasks
  • The default configuration for those plugins

Because your project references the rig, you inherit a complete build pipeline without having to define it manually. This is important because it ensures consistency across all SPFx projects and allows Microsoft to evolve the toolchain while maintaining compatibility.

When HEFT runs, it first loads the rig configuration, then applies any project-level configuration overrides. This layered approach ensures that the default pipeline remains stable while still allowing controlled customization.

What Happens When You Run a Build Command

From a developer’s perspective, building an SPFx project looks straightforward. You run a command such as npm run build. However, under the hood, HEFT follows a structured process.

  • First, HEFT loads your project configuration and resolves the rig reference. This tells HEFT which phases and tasks are available for execution.
  • Next, HEFT activates the requested phase. For a standard build, this is the build phase. The build phase contains tasks such as TypeScript compilation and asset processing.
  • HEFT then executes the tasks in the order defined by the rig. For example, TypeScript compilation must complete before bundling can occur. If a task fails, HEFT stops the phase and clearly reports the error.
  • Once compilation and preprocessing tasks are completed successfully, HEFT invokes the bundling step. This uses webpack internally, but HEFT controls when and how webpack is executed.
  • After bundling completes, HEFT produces the expected output artifacts, such as compiled JavaScript and processed assets.

Because the workflow is defined declaratively, every execution of the build phase follows the same sequence.

A Simple Practical Example

To make this clearer, consider a basic SPFx web part project created with the standard generator.

The project includes:

  • TypeScript source files
  • SCSS styles
  • A manifest file
  • Static assets

You run the build command.

Here is what HEFT does conceptually.

  • HEFT reads your project configuration and determines that it is an SPFx solution using the SPFx rig. It activates the build phase defined in that rig.
  • The first task compiles TypeScript files. If there are type errors, the build fails at this stage.
  • Once TypeScript compilation succeeds, HEFT runs the Sass processing task. Styles are transformed and prepared for bundling.
  • Next, HEFT runs the webpack task. The webpack configuration is generated according to the SPFx rig. If you have not customized it, the default configuration is used. Webpack bundles your solution into optimized output files.
  • Finally, HEFT completes the phase and produces the build output.

At no point are you writing custom build scripts or manually chaining tasks. The entire workflow is orchestrated through phases, tasks, and configuration.

Included Plugins in the Workflow

HEFT uses plugins to implement tasks. The SPFx toolchain includes built-in plugins for common activities such as:

  • TypeScript compilation
  • Sass processing
  • Jest testing
  • Webpack bundling
  • Running additional scripts

Each plugin is responsible for one aspect of the build. HEFT coordinates them rather than embedding their logic directly into the core engine. This plugin model improves modularity. If a plugin needs to be configured, you modify its configuration file. If a new capability is required, it can be added as a separate plugin instead of rewriting the pipeline.

How Custom Code Fits into the Workflow

The workflow also allows custom logic through documented extension points. If you need to run custom code during the build, the Run Script Plugin lets you register a script file to execute in a specific phase. HEFT treats that script as part of the pipeline.

If you need to adjust how webpack behaves, the Webpack Patch Plugin lets you modify the generated webpack configuration before bundling. Both mechanisms operate within the structured phase and task model. They do not replace the pipeline. They integrate into it.

This design preserves stability while allowing flexibility.

Migration Context: From Gulp to HEFT

In the previous Gulp-based toolchain, developers commonly modified gulpfile.js to inject custom behavior. That approach relied on imperative scripts and custom task definitions.

The HEFT model replaces that with:

  • Configuration-driven phase definitions
  • Plugin-based task execution
  • Structured extension points

The migration documentation emphasizes that although the underlying orchestration system has changed, the user-facing experience remains familiar. Developers still use standard commands, and SPFx projects still produce the same outputs.

The key difference is how those outputs are generated and managed internally.

Why This Workflow Is Important

The HEFT workflow improves predictability and maintainability. Because tasks are explicitly defined and executed in known phases, the build process is easier to reason about.

This structured approach also improves reliability in automated environments such as CI/CD pipelines. The same phase-based workflow runs locally and in build agents, reducing discrepancies between development and production builds.

Most importantly, the workflow is designed to evolve. Since tasks are implemented through plugins and configured declaratively, improvements to the toolchain can be delivered through updated rig definitions rather than rewriting individual projects.

Conclusion

HEFT’s workflow in SPFx is built around clear phases, modular tasks, and configuration-driven execution. When you run a build command, HEFT loads the SPFx rig, activates the appropriate phase, executes its tasks in order, and produces predictable output.

Although the visible developer experience has not changed dramatically, the underlying orchestration model is significantly more structured than before. That structure is what enables safer customization, smoother upgrades, and long-term sustainability for SPFx solutions.

Read the whole story
alvinashcraft
44 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

WebKit Features for Safari 26.5

1 Share

Safari 26.5 is here, delivering the :open pseudo-class, the element-scoped keyword for random(), color-interpolation for SVG gradients, the ToggleEvent.source property for popovers, and the Origin API.

Alongside new features, this release continues our ongoing efforts to greatly improve the quality of WebKit. There are 63 bug fixes in total — making this the biggest May release of WebKit yet. The improvements span SVG, WebRTC, networking, editing, and more. Scroll-driven animations and Anchor Positioning both get multiple fixes. Rendering at different zoom levels works better. And work continues improving the handling layout whenever a block-level element lives inside an inline element.

CSS

The :open pseudo-class

The new :open pseudo-class in CSS provides a clean way to style the open state of elements like <details>, <dialog>, <select>, and <input>.

Previously, you might have used the [open] attribute selector for <details> and <dialog>. It works on those elements, but doesn’t work on <select> or <input>. Plus, it’s an attribute selector doing the job better handled by a pseudo-class. Now :open provides a single, consistent pattern that works across all of these element types.

For <dialog>, it now matches when the dialog is showing — whether opened with showModal() or show(). And for <input>, it applies when an associated picker is displayed, like a date or color picker.

For <select>, :open matches when the drop-down is expanded.

select:open {
  border: 1px solid skyblue;
}

This is a practical improvement to everyday CSS. The progressive enhancement is straightforward — browsers that don’t yet support :open simply won’t apply those rules, and the underlying elements still function normally.

Improvements to CSS random()

We were proud to be the first browser to ship the new CSS random() function last December, in Safari 26.2. Since then, the CSS Working Group adjusted how named random values work. Using a named value in the syntax, like random(--size, 100px, 200px), now creates a global result, instead of something scoped to each individual element. Safari 26.5 implements these changes, including a new element-scoped keyword for when you need per-element behavior.

For example, imagine you have eight instances of <div class="box"> and apply the following CSS.

.box {
  width: random(100px, 200px); 
  height: random(100px, 200px);
  border: 2px solid black;
}

You get eight completely differently sized rectangles. This is because each time the random function is used, it generates a brand new number, in this case between 100px and 200px. This is how random() has worked since Safari 26.2.

If, instead, you want all eight boxes to be the same size with the same randomly generated height and width, you can write:

.box {
  width: random(--w, 100px, 200px); 
  height: random(--h, 100px, 200px);
}

This chooses a random number, names it (as --w or separately --h), and reuses the named number on each box. (Before Safari 26.5, a cache name was scoped to the individual element, each single box. Now it is global.)

If you want all eight boxes to be square, you can use:

.box {
  width: random(--s, 100px, 200px); 
  height: random(--s, 100px, 200px);
}

The --s ties the two sizes together. The height and width is random, but it’s the same number. Before Safari 26.5, this would give you eight differently-sized squares. Now it will give you a single random size for all eight squares.

If you want eight differently sized squares, you can now use the element-scoped keyword to scope the name to the element, like this:

.box {
  width: random(--s element-scoped, 100px, 200px); 
  height: random(--s element-scoped, 100px, 200px);
}

Now the name applies only to a single element. You can put element-scoped before or after the name, meaning this works too: random(element-scoped --s, 100px, 200px). Lastly, the element-shared keyword has been removed from Safari 26.5, since the new default behavior covers this use case and the CSS Working Group removed it from the specification.

Improvements to Anchor Positioning

CSS anchor positioning continues to mature in this release, with several fixes addressing real-world usage patterns.

  • Fixed an issue where media queries failed to re-evaluate during viewport resizing when CSS anchor positioning and viewport units were both in use. (172864699)
  • Fixed an issue where chains of three or more anchor-positioned elements didn’t resolve correctly. (173357622)
  • Fixed an issue where anchor() fallback values did not accept unitless zero. (173554237)
  • Fixed an issue where an element with display: contents did not establish an anchor scope when using anchor-scope. (173718365)
  • Fixed an issue where fixed-position boxes anchored to children of sticky-positioned boxes did not stick correctly. (173722628)

Improvements to Hanging Punctuation

This release includes two fixes for hanging-punctuation.

  • Fixed hanging-punctuation to correctly treat U+0027 (apostrophe) and U+0022 (quotation mark) as hangable quote characters. (172668971)
  • Fixed an issue where ideographic space did not hang when using hanging-punctuation: first. (172669250)

Improvements to Scroll-Driven Animations

Scroll-driven animations are a powerful recent addition to CSS, and this release includes four fixes that improve their reliability.

  • Fixed support for the scroll animation timeline range name in scroll-driven animations. (171630023)
  • Fixed an issue where scroll-driven animations were not properly paused when animation-play-state was dynamically set to paused. (171630127)
  • Fixed an issue where view timeline animations near the 0% and 100% thresholds reported incorrect progress values. (171630157)
  • Fixed an issue where animation timelines could fail to restore correctly after navigating back to a page from the back-forward cache. (174561577)

Improvements to Block-in-Inline Layout

Work continues on the layout engine rewrite for block-in-inline contexts, with several fixes in this release.

  • Fixed an issue where content inside inline elements with block-level children and rendering layers was not displayed correctly. (171101386)
  • Fixed an issue where getClientRects() could return rects with zero width and height for spans in multi-column layouts. (171101490)
  • Fixed an issue where an empty <span> with decoration was incorrectly positioned when a sibling block margin was present inside a block-in-inline context. (171101555)
  • Fixed an issue where a <br> element was incorrectly positioned inside a block-in-inline context when a block margin was present. (171101748)
  • Fixed a layout regression where absolutely positioned elements inside block-in-inline containers were incorrectly overlapping adjacent content. (171732203)

Improvements to Grid, Flexbox, Tables, Multicolumn

  • Fixed an issue where a display: grid subgrid inside a grid-lanes container incorrectly contributed its items’ intrinsic sizes to the parent’s track sizing algorithm. (171230544)
  • Fixed an issue in collapsed border tables where the border style of a cell adjacent to a rowspan cell was incorrectly applied across the full length of the spanning cell’s border. (171634786)
  • Fixed an issue where images could appear stretched inside certain flex and grid layout configurations. (172224411)
  • Fixed a regression where content with column-count: 1 could fail to display text. (172306151)

Improvements to zoom

This release includes a focused quality pass on rendering behavior at different zoom levels.

  • Fixed an issue where grid and flex layout could cause elements to shift position at certain zoom levels. (172118478)
  • Fixed an issue where text content could get cut off inside overflow containers when the page was zoomed in. (172118721)
  • Fixed an issue where pinch-to-zoom could cause web content to jump or disappear on some websites. (172507916)
  • Fixed an issue where lh and rlh units resolved with double-zoom when line-height is a number. (173515568)
  • Fixed an issue where the rlh unit was double-zoomed when resolving with evaluation-time zoom for unzoomed properties. (173518838)
  • Fixed an issue where aspect-ratio was not honored correctly when the page was zoomed in. (174498486)

Even more improvements to CSS

  • Fixed an issue where @font-face rules with different styles could incorrectly fall back to glyphs from other faces in the same family, rather than proceeding to the next family as specified by the font matching algorithm. (172390840)
  • Fixed an issue where images inside transformed containers were not properly centered. (172475726)
  • Fixed an issue where user-installed font variants could interfere with system font matching, causing incorrect fonts to be selected. (173345107)
  • Fixed a regression where animating to an implicit value for individual transform properties failed to animate. (173717819)
  • Fixed an issue where :has(:empty) was not invalidated when the content of a child element changed from empty to non-empty. (174501418)

SVG

WebKit for Safari 26.5 adds support for the color-interpolation attribute on SVG gradients, enabling linearRGB color space interpolation.

<linearGradient color-interpolation="linearRGB">
  <stop offset="0%" stop-color="red" />
  <stop offset="100%" stop-color="blue" />
</linearGradient>

By default, SVG gradients interpolate colors in the sRGB color space. Setting color-interpolation="linearRGB" on a gradient element now produces more perceptually even color transitions, especially noticeable in gradients between saturated colors, where sRGB interpolation can produce a darker or muddier midpoint than expected.

Improvements to SVG

The work continues making significant improvements to SVG.

  • Fixed an issue where removeAttribute for width or height on an SVG root element did not reset to the initial default values. (172132798)
  • Fixed event name mapping for onbegin, onend, and onrepeat on SVGAnimationElement and added the missing onend event handler. (172581017)
  • Fixed an issue where an SVG <image> element was not repainted when its href attribute was removed. (172875166)
  • Fixed an issue where UI events such as wheel failed to fire for inner SVG elements. (173009454)
  • Fixed an issue where SVG cursors set via cursor: url() appeared blurry on high DPI displays. (173950927)

Web API

ToggleEvent.source

Safari 26.5 adds support for the source property on ToggleEvent. Now, when a popover or other toggleable element is toggled, the event includes a reference to the element that triggered the action, such as the invoker button that opened a popover. This makes it straightforward to coordinate behavior between a trigger and its target without manually tracking that relationship in your own code.

popover.addEventListener("toggle", (e) => {                                                                           
  if (e.newState === "open") {                                                                                        
    console.log("Opened by:", e.source);                                                                              
  }                                                                                                                   
}); 

Origin API

Safari 26.5 also adds support for the Origin API, which exposes origin information as a structured Origin object rather than requiring string parsing. This also enables you to perform same-site comparisons between origins without having to pull in the Public Suffix List. Origin.from(value) allows you to construct an Origin object from a string or built-in object, such as MessageEvent. When the origins from built-in objects are opaque, you can still compare them. This wasn’t possible before as you only had access to the serialized origin, which is “null” for opaque origins.

const messageOrigin = Origin.from(messageEvent);
const localOrigin = Origin.from("https://social.example");
if (messageOrigin.isSameSite(localOrigin))
    grantAccess();

Improvements to Web API

  • Fixed an issue where DecompressionStream discarded valid decompressed output when extra trailing bytes were present after the compressed stream, instead of enqueuing the output before throwing. (171020155)
  • Fixed an issue where calling preventDefault() on pointerdown events did not prevent page scrolling when only passive touch event listeners are installed. (173988278)

Additional Resolved Issues

In addition to all the fixes described above, WebKit for Safari 26.5 also includes the following improvements:

Accessibility

  • Fixed an issue where the accessibility tree could permanently be empty if built during early page load when only a scroll area and web area were present. (174244620)

Editing

  • Fixed an issue where pressing backspace on a line below an image in a contenteditable region could place the cursor in the wrong position. (171850465)
  • Fixed an issue where emoji images copied from websites and pasted into other sites appeared broken due to cross-origin resource policy blocking the SVG image sources. (172775070)
  • Fixed an issue where pasting text into an empty list item created an extra blank bullet. (173275372)

Forms

  • Fixed an issue where a readonly date <input> could still be edited via keyboard using the date picker. (171535893)
  • Fixed an issue on iOS and iPadOS where datalist suggestions were presented directly over the associated input, obscuring it. (174264299)

HTML

  • Fixed dragenter and dragleave events to include relatedTarget in the event object. (172048448)
  • Fixed an issue on iOS where the drag thumbnail could show an incorrect image after long-pressing an image with an embedded link. (172293971)

Images

  • Fixed a regression where images with srcset and sizes attributes containing calc() expressions with division by zero were not displayed. (173954748)

JavaScript

  • Fixed TypedArray.prototype.sort() failing when the comparison function accesses the .buffer property of the typed array. (172516044)

Media

  • Fixed an issue where the media controls volume button was mispositioned and overlapped with other controls in right-to-left locales. (171182590)
  • Fixed an issue where MediaCapabilities.decodingInfo() always returned false for spatialRendering. (172689752)

Networking

  • Fixed an issue where downloaded files used the file extension from the URL path instead of the HTTP Content-Type header. (173705083)
  • Fixed an issue where downloaded files were saved with incorrect or missing file extensions when the URL path extension did not match the HTTP Content-Type header. (173945210)

Scrolling

  • Fixed an issue where scroll-snap re-snapping after layout changes could cause incorrect scroll positions, resulting in the wrong content being shown. (171541221)

Storage

  • Fixed an issue where IndexedDB connections could become permanently broken until the page was reloaded. (172247569)
  • Fixed an issue where document.hasStorageAccess() could return a Promise that never resolves. (172424614)

Web Extensions

  • Fixed an issue where extensions with a trailing comma in manifest.json failed to load in Safari. (172120877)

WebGL

  • Fixed WebGL shader compilation to properly handle NaN and infinity values. (166699074)

WebRTC

  • Fixed RTCIceCandidate.toJSON() to include the usernameFragment property in its serialized output. (172689343)
  • Fixed RTCRtpSender to allow a maxFramerate value of 0. (172689374)
  • Fixed RTCRtpSynchronizationSource.timestamp to use the correct time base. (172689387)
  • Fixed an issue where remote audio and video track IDs were incorrectly derived from SDP. (172689452)
  • Fixed RTCRtpTransceiver.setCodecPreferences() to accept codecs with case-insensitive mimeType matching. (172689477)
  • Fixed an issue where the camera did not turn on automatically in Google Meet when media permissions were set to “Allow”. (174023905)

Updating to Safari 26.5

Safari 26.5 is available on iOS 26.5, iPadOS 26.5, visionOS 26.5, macOS Tahoe 26.5, plus macOS Sequoia, and macOS Sonoma. On iOS, iPadOS, and visionOS, you can update to Safari 26.5 as part of the OS update in Settings > General > Software Update. On macOS, Safari updates are delivered through System Settings > General > Software Update.

Feedback

We love hearing from you. To share your thoughts, find us online: Jen Simmons on Bluesky / Mastodon, Saron Yitbarek on BlueSky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn.

If you run into any issues, we welcome your bug report. Filing issues really does make a difference.

You can also find this information in the Safari release notes.

Read the whole story
alvinashcraft
54 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Why Keyboard Users Can’t Scroll Your Overflow Containers

1 Share

So, you build a data table with long rows, lots of columns, and horizontal scroll on the container. It works fine with a mouse and you ship it.

But! When a keyboard user Tabs into the table, its focus lands on a cell. Then they press the arrow keys to read across the row but nothing happens. They try Tab again, but this time it jumps to the next interactive element entirely outside the table. The rest of the content is there and screen reader users can navigate it just fine, but there is no way to scroll the container without a mouse.

You may have never noticed this because you most likely only test with a mouse. Your screen reader users never noticed because they navigate the accessibility tree, not the scroll container. The only person it traps is the sighted keyboard user and most teams never test for that.

I didn’t either, until someone filed a bug I couldn’t reproduce until I put my mouse away.

Tab lands inside the table, but the wrapper was skipped entirely and there is no keyboard handle on the container.

Focus Management and Scroll Containers Are Not the Same Thing

Keyboard focus follows tab order. The tab order then follows interactive elements, like buttons, links, inputs — basically anything the browser considers actionable. That’s the system the spec built for keyboard navigation.

Scroll containers are a completely different system because they are layout primitive. What I mean by that is the browser handles overflowing content in different ways any time you set overflow to auto, scroll, or hidden on an element. The browser’s job is to manage overflowing content, not necessarily navigate it. The spec never classified overflow as interactive.

The gap between those two systems is where keyboard users fall through.

This is not a browser bug. Every major browser behaves identically here because they are all following the same spec and the scroll container and focus management are both doing their jobs, although they were not designed to work together.

The CSS Properties That Create Scroll Containers Without Warning

I briefly mentioned a few overflow property values that affect the way overflow is handled, but which values actually create scroll containers? auto is the obvious one, but the scroll containers have a habit of appearing where you least expect them.

For example, it’s worth remembering that overflow is a shorthand. And it’s a little weird how the constituents work. Like, setting the overflow-x constituent to auto to handle horizontal overflow implicitly sets overflow-y to auto. So, a container you’re managing for horizontal scrolling is also a vertical scroll container, and keyboard users can’t scroll either axis without a mouse.

/* This: */
.table-wrapper {
  overflow-x: auto;
}

/* ...is the equivalent of this: */
.table-wrapper {
  overflow-x: auto;
  overflow-y: auto;
}

The same thing happens with overflow: hidden which most developers use it to clip content or clear floats. It might not be obvious, but hidden still creates a scroll container. The content is clipped, not gone (which could be an accidental form of “data loss”), and keyboard users can still focus into it; they just can’t scroll to any of it.

There are less obvious triggers, too. More properties like transform, filter, perspective, will-change (referencing transform), contain (set to paint), and content-visibility (set to auto) all create scroll containers. You may have added one of these for a performance optimization or an animation and quietly created a keyboard trap at the same time.

/* Added for a smooth animation */
.panel {
  transform: translateZ(0);
  overflow: hidden; /* clipping the content */
}

/* Result: .panel is now a scroll container
    keyboard users can focus into it but can't scroll it */

One of the reasons this bug ships so often is that the scroll container wasn’t intentional and nobody audited it because nobody knew it was there.

The Fix

The “fix” is adding tabindex="0" to the scroll container in the markup:

<div class="scroll-container" tabindex="0">
  <!-- scrollable content -->
</div>

That puts the container in the tab order so keyboard users can now Tab to it. And once it has focus, the arrow keys scroll it. The browser handles the rest.

But that is only part of the fix. Adding tabindex without an accessible name means a screen reader encounters a focusable element with nothing to announce. A plain div with tabindex="0" has no name, and the screen reader says nothing useful. You need to add an aria-label that describes what the container holds.

​​role="region"​ and aria-label​ work together. The role tells assistive technology this is a landmark region. The label is what gets announced when focus lands on it. Without the label, screen readers announce”region” and nothing else. That tells the user nothing about what they’re about to scroll through.1

<!-- This is too vague -->
<div
  class="scroll-container"
  tabindex="0"
  aria-label="scrollable"
  role="region"
>
  <!-- scrollable content -->
</div>

<!-- This is more descriptive -->
<div
  class="scroll-container"
  tabindex="0"
  aria-label="Monthly sales data, scrollable"
  role="region"
>
  <!-- scrollable content -->
</div>

Another common problem when working with scroll containers that need to support keyboard tabbing is using visible and obvious focus styling. It’s common to see outline: none or outline: 0 as a design requirement. But then the container is selectable, but invisible. Keyboard users will land on it with no clear indication. Focus styles are indeed style-able, so we can still make then obvious and attractive if the design calls for something custom:

.scroll-container:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

The outline color doesn’t need to match your brand exactly, It just needs a two-color combination to ensure contrast with all components, per WCAG’s techniques:

When tabindex="0" Isn’t the Answer

Making a scroll container focusable isn’t always the right call.

If you can’t write a clear and useful aria-label for the container, that’s a signal the content might need restructuring rather than an interactive patch. A scroll container that wraps a single image, a decorative element, or content that’s already fully reachable by tab order doesn’t need to be in the tab order itself. Putting it there adds noise without adding value.

​​That said, if a container holds a large number of focusable items, fifty links in a sidebar or a long list of cards, a single Tab stop on the wrapper may actually be preferable to forcing keyboard users through every item individually. The trade-off shifts when the list is long enough that tabbing through it becomes its own burden. In those cases, tabindex="0"​ on the container is the right call even if the items inside are already focusable.

The harder call is when every item inside already has independent keyboard interaction, e.g., a list of buttons, set of links, or a group of form fields. All of these are already in the tab order so users can reach each one with Tab. If the container is just a visual wrapper around already-accessible content, making the wrapper focusable means users have to Tab through one extra stop to get to the things they actually want to interact with.

The test I run is a simple one. Something like can a keyboard user reach every piece of meaningful content inside the container without tabindex="0" on the wrapper? If yes, skip it. If no, add it.

When the right answer is skipping tabindex, restructuring the DOM is usually the better path. Breaking up long content, splitting it across sections, or using a disclosure pattern with progressive reveal often solves the problem at the layout level without creating accessibility compromise.

How to Audit This

The keyboard-only walkthrough is the fastest test. Unplug or disable your mouse, open the page, and press Tab through every interactive element and every scrollable container. If you reach a container with overflow content and can’t scroll it with the arrow keys after tabbing to it, it needs fixing. On most pages, this would only take about five minutes and the bugs are usually obvious the moment you stop using a mouse.

Note: ​​One thing worth being clear about is that this article is desktop-focused. Virtual keyboards on iOS and Android interact with scroll containers differently, and touch navigation has its own set of considerations. If mobile keyboard accessibility is a concern for your project, that warrants its own investigation.

Chrome’s accessibility panel gives you a structural view. Open DevTools, go to the “Accessibility” tab, and inspect a scroll container. If it shows no role and no accessible name, it’s invisible to assistive technology as a navigable element. That’s a quick way to confirm whether a container needs tabindex="0" and aria-label before you touch the code.

Tools like Deque’s Axe-core and WAVE can automatically catch some of these issues. For Axe-core specification you can use the scrollable-region-focusable rule to flag scroll containers that have focusable content but are not themselves focusable. Running axe-core in your CI pipeline means this class of bug gets caught before it reaches production rather than after a user files a ticket.

// axe-core in a Jest test
import axe from 'axe-core';

test('scroll containers are keyboard accessible', async () => {
  const results = await axe.run(document.body, {
    rules: { 'scrollable-region-focusable': { enabled: true } }
  });
  expect(results.violations).toHaveLength(0);
});

One thing axe-core misses is containers that have overflow content but no focusable children. Those won’t trigger the rule because there’s nothing to Tab into. The keyboard walkthrough catches these cases but the automated tool does not.

How I Approach This Now

Here are the three questions I ask myself:

  1. Does the scroll container hold content that can’t otherwise be reached by keyboard**?** That’s stuff like a data table, a code block, a chat log or a custom carousel — basically anything where the only way to see all the content is to scroll. If the answer is yes, add tabindex="0", an aria-label, and a visible :focus-visible style. All three together. Not just the attribute.
  2. Can every piece of meaningful content inside be reached by **Tab** without scrolling? If the container wraps a list of links or a group of buttons, the content is already keyboard-accessible. That is, unless there’s visually hidden content due to overflow. If the container has overflow content that’s not visually visible but is in tab order, users still need a way to scroll to see it. Making the wrapper focusable adds tab stops without adding access. Skip it.
  3. Is the container the result of an unintentional overflow trigger from transform, contain, or similar? If you added a property for reasons unrelated to scrolling and it created a scroll container as a side effect, consider removing the property if you can, or add tabindex="0" only if there’s content that genuinely needs it.

That’s the whole decision tree. The fix is simple once you know the container exists, but the hard part is knowing it’s there. That’s what the keyboard walkthrough is for.

The Test That Changes How You Build

The keyboard walkthrough takes no more than five minutes but most developers never run it because they assume their users use a mouse. Most of them are correct, most of the time. But the sighted keyboard user is real, and they’re using your product right now, and they’ve quietly learned which interfaces to avoid because they’re not worth the frustration.

Using tabindex="0" won’t fix everything. It won’t fix a poorly structured DOM, a missing accessible name, or a focus style that was stripped out in a global CSS reset. But it closes the gap between what looks accessible and what actually is, and it costs almost nothing to add.

The thing I keep coming back to is that this bug is invisible to the developer, invisible to the screen reader user, and invisible to automated testing until you configure it specifically to look. The only way to find it is to use the product the way the affected user does, which is the test so put your mouse away.

Further Reading


Why Keyboard Users Can’t Scroll Your Overflow Containers originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

Read the whole story
alvinashcraft
59 seconds ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories