diff --git a/packages/dev/docs/pages/blog/building-a-combobox.mdx b/packages/dev/docs/pages/blog/building-a-combobox.mdx
index 81901e5ecb1..e45f0fb393f 100644
--- a/packages/dev/docs/pages/blog/building-a-combobox.mdx
+++ b/packages/dev/docs/pages/blog/building-a-combobox.mdx
@@ -19,14 +19,14 @@ export default BlogPostLayout;
---
keywords: [combobox, accessibility, mobile, react spectrum, react, spectrum, interactions, touch]
-description: After many months of research, development, and testing, we’re excited to announce that the React Spectrum [ComboBox](../react-spectrum/ComboBox.html) component and React Aria [useComboBox](../react-aria/useComboBox.html) hook are now available! In this post we'll take a deeper look into some of the challenges we faced when building an accessible and mobile friendly ComboBox.
+description: After many months of research, development, and testing, we’re excited to announce that the React Spectrum [ComboBox](../../s2/ComboBox.html) component and React Aria [useComboBox](../useComboBox.html) hook are now available! In this post we'll take a deeper look into some of the challenges we faced when building an accessible and mobile friendly ComboBox.
date: 2021-07-13
author: '[Daniel Lu](https://github.com/LFDanLu)'
---
# Creating an accessible autocomplete experience
-After many months of research, development, and extensive testing across browsers, devices, and assistive technology, we’re excited to announce that the React Spectrum [ComboBox](../react-spectrum/ComboBox.html) component and React Aria [useComboBox](../react-aria/useComboBox.html) hook are now available! We’ve focused on the following areas to help you build quality autocomplete experiences.
+After many months of research, development, and extensive testing across browsers, devices, and assistive technology, we’re excited to announce that the React Spectrum [ComboBox](../../s2/ComboBox.html) component and React Aria [useComboBox](../useComboBox.html) hook are now available! We’ve focused on the following areas to help you build quality autocomplete experiences.
- **Accessibility** — Our ComboBox has been tested with screen readers across desktop and mobile devices, and with many different input methods including mouse, touch, and keyboard. We encountered many screen reader differences, and worked hard to ensure announcements are clear and consistent.
- **Mobile** — On small screens, the React Spectrum ComboBox is automatically displayed in a tray, which improves the user experience by giving them a larger area to scroll. We also optimized our experience for touch screen interactions and on screen keyboards.
@@ -133,7 +133,7 @@ Special care was taken such that the messages themselves only contained relevant
When the user then moves to a different option in the same section, only the newly focused item name is announced. Similarly, the total option count is only announced when number of options available in the listbox changes. Since many of these messages were added to fill in gaps in VoiceOver's announcement,
we only trigger the `LiveAnnouncer` on Apple devices to avoid announcement overlap on other screen readers.
-If you are interested in using this `LiveAnnouncer` yourself, check out [LiveAnnouncer](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx) in `@react-aria/live-announcer`. Otherwise, the [useComboBox](../react-aria/useComboBox.html) hook provides you with all of the custom messaging out of the box. See the video below for a sneak peek!
+If you are interested in using this `LiveAnnouncer` yourself, check out [LiveAnnouncer](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx) in `@react-aria/live-announcer`. Otherwise, the [useComboBox](../useComboBox.html) hook provides you with all of the custom messaging out of the box. See the video below for a sneak peek!
diff --git a/packages/dev/s2-docs/pages/internationalized/number/NumberParser.mdx b/packages/dev/s2-docs/pages/internationalized/number/NumberParser.mdx
index 0ba0c3ce3f9..61da0b9bd1e 100644
--- a/packages/dev/s2-docs/pages/internationalized/number/NumberParser.mdx
+++ b/packages/dev/s2-docs/pages/internationalized/number/NumberParser.mdx
@@ -26,7 +26,7 @@ Numbers can be formatted in many different ways, including percentages, units, d
Parsing numbers while taking into account all locale-specific detail is quite complex and error-prone. `NumberParser` uses information about the locale and expected format for a number in order to parse it correctly. This means it is somewhat strict about the accepted formats. It is not designed to handle use cases where the user can enter numbers in an unknown format (e.g. either a unit value _or_ a percentage), this must be known up front or selected via an external UI control.
-Read our [blog post](https://react-spectrum.adobe.com/blog/how-we-internationalized-our-numberfield.html) for more details on how the number parser is implemented.
+Read our [blog post](how-we-internationalized-our-numberfield.html) for more details on how the number parser is implemented.
### Example
diff --git a/packages/dev/s2-docs/pages/react-aria/Button.mdx b/packages/dev/s2-docs/pages/react-aria/Button.mdx
index d2d201213c9..cae4699180f 100644
--- a/packages/dev/s2-docs/pages/react-aria/Button.mdx
+++ b/packages/dev/s2-docs/pages/react-aria/Button.mdx
@@ -45,7 +45,7 @@ export const tags = ['btn'];
## Events
-Use the `onPress` prop to handle interactions via mouse, keyboard, and touch. This is similar to `onClick`, but normalized for consistency across browsers, devices, and interaction methods. Read our [blog post](https://react-spectrum.adobe.com/blog/building-a-button-part-1.html) to learn more.
+Use the `onPress` prop to handle interactions via mouse, keyboard, and touch. This is similar to `onClick`, but normalized for consistency across browsers, devices, and interaction methods. Read our [blog post](building-a-button-part-1.html) to learn more.
The `onPressStart`, `onPressEnd`, and `onPressChange` events are also emitted as the user interacts with the button. Each of these handlers receives a , which provides information about the target and interaction method. See [usePress](https://react-spectrum.adobe.com/react-aria/usePress.html) for more details.
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/CalendarSystems.tsx b/packages/dev/s2-docs/pages/react-aria/blog/CalendarSystems.tsx
new file mode 100644
index 00000000000..6eca85701e8
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/CalendarSystems.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import {Calendar, Picker, PickerItem, Provider} from '@react-spectrum/s2';
+import React from 'react';
+import {useLocale} from '@react-aria/i18n';
+import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
+
+export default function CalendarSystems() {
+ let [calendar, setCalendar] = React.useState('gregory');
+ let {locale} = useLocale();
+ const calendars = [
+ {key: 'gregory', name: 'Gregorian'},
+ {key: 'japanese', name: 'Japanese'},
+ {key: 'buddhist', name: 'Buddhist'},
+ {key: 'roc', name: 'Taiwan'},
+ {key: 'persian', name: 'Persian'},
+ {key: 'indian', name: 'Indian'},
+ {key: 'islamic-umalqura', name: 'Islamic (Umm al-Qura)'},
+ {key: 'islamic-civil', name: 'Islamic Civil'},
+ {key: 'islamic-tbla', name: 'Islamic Tabular'},
+ {key: 'hebrew', name: 'Hebrew'},
+ {key: 'coptic', name: 'Coptic'},
+ {key: 'ethiopic', name: 'Ethiopic'},
+ {key: 'ethioaa', name: 'Ethiopic (Amete Alem)'}
+ ];
+
+ return (
+
+
+ {item => {item.name} }
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/DragBetweenListsExample.tsx b/packages/dev/s2-docs/pages/react-aria/blog/DragBetweenListsExample.tsx
new file mode 100644
index 00000000000..fdcbef4df00
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/DragBetweenListsExample.tsx
@@ -0,0 +1,128 @@
+'use client';
+
+import {GridList, GridListItem} from 'vanilla-starter/GridList';
+import {useDragAndDrop, isTextDropItem} from 'react-aria-components';
+import {useListData} from 'react-stately';
+import File from '@react-spectrum/s2/icons/File';
+import Folder from '@react-spectrum/s2/icons/Folder';
+import React from 'react';
+
+function BidirectionalDnDGridList(props) {
+ let {list} = props;
+ let {dragAndDropHooks} = useDragAndDrop({
+ acceptedDragTypes: ['custom-app-type-bidirectional'],
+ // Only allow move operations
+ getAllowedDropOperations: () => ['move'],
+ getItems(keys) {
+ return [...keys].map(key => {
+ let item = list.getItem(key);
+ // Setup the drag types and associated info for each dragged item.
+ return {
+ 'custom-app-type-bidirectional': JSON.stringify(item),
+ 'text/plain': item.name
+ };
+ });
+ },
+ onInsert: async (e) => {
+ let {
+ items,
+ target
+ } = e;
+ let processedItems = await Promise.all(
+ items
+ .filter(isTextDropItem)
+ .map(async item => JSON.parse(await item.getText('custom-app-type-bidirectional')))
+ );
+ if (target.dropPosition === 'before') {
+ list.insertBefore(target.key, ...processedItems);
+ } else if (target.dropPosition === 'after') {
+ list.insertAfter(target.key, ...processedItems);
+ }
+ },
+ onReorder: (e) => {
+ let {
+ keys,
+ target
+ } = e;
+
+ if (target.dropPosition === 'before') {
+ list.moveBefore(target.key, [...keys]);
+ } else if (target.dropPosition === 'after') {
+ list.moveAfter(target.key, [...keys]);
+ }
+ },
+ onRootDrop: async (e) => {
+ let {
+ items
+ } = e;
+ let processedItems = await Promise.all(
+ items
+ .filter(isTextDropItem)
+ .map(async item => JSON.parse(await item.getText('custom-app-type-bidirectional')))
+ );
+ list.append(...processedItems);
+ },
+ /*- begin highlight -*/
+ onDragEnd: (e) => {
+ let {
+ dropOperation,
+ keys,
+ isInternal
+ } = e;
+ // Only remove the dragged items if they aren't dropped inside the source list
+ if (dropOperation === 'move' && !isInternal) {
+ list.remove(...keys);
+ }
+ }
+ /*- end highlight -*/
+ });
+
+ return (
+
+ {item => (
+
+ {item.type === 'folder' ? : }
+ {item.name}
+
+ )}
+
+ );
+}
+
+export default function DragBetweenListsExample() {
+ let list1 = useListData({
+ initialItems: [
+ {id: '1', type: 'file', name: 'Adobe Photoshop'},
+ {id: '2', type: 'file', name: 'Adobe XD'},
+ {id: '3', type: 'folder', name: 'Documents'},
+ {id: '4', type: 'file', name: 'Adobe InDesign'},
+ {id: '5', type: 'folder', name: 'Utilities'},
+ {id: '6', type: 'file', name: 'Adobe AfterEffects'}
+ ]
+ });
+
+ let list2 = useListData({
+ initialItems: [
+ {id: '7', type: 'folder', name: 'Pictures'},
+ {id: '8', type: 'file', name: 'Adobe Fresco'},
+ {id: '9', type: 'folder', name: 'Apps'},
+ {id: '10', type: 'file', name: 'Adobe Illustrator'},
+ {id: '11', type: 'file', name: 'Adobe Lightroom'},
+ {id: '12', type: 'file', name: 'Adobe Dreamweaver'}
+ ]
+ });
+
+
+ return (
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/RangeCalendarExample.tsx b/packages/dev/s2-docs/pages/react-aria/blog/RangeCalendarExample.tsx
new file mode 100644
index 00000000000..97f914f95cc
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/RangeCalendarExample.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import {today, getLocalTimeZone} from '@internationalized/date';
+import {RangeCalendar} from '@react-spectrum/s2';
+import React from 'react';
+import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
+
+export default function RangeCalendarExample() {
+ let now = today(getLocalTimeZone()).set({day: 8});
+ let disabledRanges = [
+ [now, now.add({days: 2})],
+ [now.add({days: 10}), now.add({days: 14})],
+ [now.add({days: 23}), now.add({days: 28})],
+ ];
+
+ let isDateUnavailable = (date) => disabledRanges.some((interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0);
+
+ return (
+
+
+
+ );
+}
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/SubmenuAnimation.tsx b/packages/dev/s2-docs/pages/react-aria/blog/SubmenuAnimation.tsx
new file mode 100644
index 00000000000..b4fb3b27dfa
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/SubmenuAnimation.tsx
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2024 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+'use client';
+
+import {animate} from '../../../../docs/pages/react-aria/home/utils';
+import React, {JSX, useEffect, useRef, useState} from 'react';
+import {useResizeObserver} from '@react-aria/utils';
+import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
+
+export function SubmenuAnimation(): JSX.Element {
+ let ref = useRef(null);
+ let [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
+ let [hovered, setHovered] = useState('Option 1');
+ let isAnimating = useRef(false);
+ let mouseRef = useRef(null);
+ let [mouseWidth, setMouseWidth] = useState(12);
+ let option1Ref = useRef(null);
+ let option2Ref = useRef(null);
+ let submenuOptionRef = useRef(null);
+
+ let updateWidth = () => {
+ if (ref.current) {
+ setMouseWidth(Math.min(12, (window.innerWidth / 768) * 12));
+ }
+ };
+
+ useResizeObserver({ref: ref, onResize: updateWidth});
+
+ useEffect(() => {
+ let startAnimation = () => {
+ let cancel = animate([
+ {
+ time: 500,
+ perform() {
+ setTimeout(() => {
+ setHovered('Option 1');
+ }, 500);
+ }
+ },
+ {
+ time: 700,
+ perform() {
+ let option1Rect = option1Ref.current!.getBoundingClientRect();
+ let option2Rect = option2Ref.current!.getBoundingClientRect();
+ let x = option1Rect.left + option1Rect.width / 2 - ref.current!.getBoundingClientRect().left;
+ let y = option1Rect.top + option1Rect.height / 2 - ref.current!.getBoundingClientRect().top;
+ let y_target = option2Rect.top + option2Rect.height / 2 - ref.current!.getBoundingClientRect().top;
+ mouseRef.current!.animate({
+ transform: [
+ `translate(${x}px, ${y}px)`,
+ `translate(${x}px, ${y_target}px)`
+ ]
+ }, {duration: 1000, fill: 'forwards', easing: 'ease-in-out'});
+ setTimeout(() => {
+ setHovered('Option 2');
+ setIsSubmenuOpen(true);
+ }, 350);
+ }
+ },
+ {
+ time: 700,
+ perform() {}
+ },
+ {
+ time: 700,
+ perform() {
+ let option1Rect = option1Ref.current!.getBoundingClientRect();
+ let option2Rect = option2Ref.current!.getBoundingClientRect();
+ let submenuOptionRect = submenuOptionRef.current!.getBoundingClientRect();
+ let x = option1Rect.left + option1Rect.width / 2 - ref.current!.getBoundingClientRect().left;
+ let y = option2Rect.top + option2Rect.height / 2 - ref.current!.getBoundingClientRect().top;
+ let x_target = submenuOptionRect.left + submenuOptionRect.width / 2 - ref.current!.getBoundingClientRect().left;
+ let y_target = submenuOptionRect.top + submenuOptionRect.height / 2 - ref.current!.getBoundingClientRect().top;
+ mouseRef.current!.animate({
+ transform: [
+ `translate(${x}px, ${y}px)`,
+ `translate(${x_target}px, ${y_target}px)`
+ ]
+ }, {duration: 1000, fill: 'forwards', easing: 'ease-in-out'});
+ setTimeout(() => {
+ setHovered('Option 3');
+ setIsSubmenuOpen(false);
+ }, 350);
+ }
+ },
+ {
+ time: 700,
+ perform() {}
+ }
+ ]);
+
+ return () => {
+ cancel();
+ setIsSubmenuOpen(false);
+ setHovered('Option 1');
+ mouseRef.current!.getAnimations().forEach(a => a.cancel());
+ isAnimating.current = false;
+ };
+ };
+
+ startAnimation();
+
+ let interval = setInterval(startAnimation, 4000);
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {'More Actions'}
+
+
+
+
+
+ {'Option 3'}
+
+
+
+
+ {'Option 2'}
+
+
+
+
+ {'Option 1'}
+
+
+ {hovered === 'Option 1' && (
+
+
+
+
+ )}
+ {hovered === 'Option 2' && (
+
+
+
+
+ )}
+ {hovered === 'Option 3' && (
+
+
+
+
+ )}
+ {isSubmenuOpen && (
+ <>
+
+
+
+
+
+
+
+
+ {'Submenu Option 3'}
+
+
+
+
+ {'Submenu Option 4'}
+
+
+
+
+ {'Submenu Option 2'}
+
+
+
+
+ {'Submenu Option 1'}
+
+
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/accessible-color-descriptions.mdx b/packages/dev/s2-docs/pages/react-aria/blog/accessible-color-descriptions.mdx
new file mode 100644
index 00000000000..b598de46ba3
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/accessible-color-descriptions.mdx
@@ -0,0 +1,183 @@
+{/* Copyright 2025 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import bundleSizeImageUrl from 'url:../../../../docs/pages/assets/bundle-size.webp';
+import initialVideoUrl from 'url:../../../../docs/pages/assets/color-picker-initial.mp4';
+import finalVideoUrl from 'url:../../../../docs/pages/assets/color-picker-final.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['color picker', 'color', 'internationalization', 'localization', 'components', 'accessibility', 'react spectrum', 'react'];
+export const description = 'Recently, we released a suite of color picker components in React Aria and React Spectrum. Since colors are inherently visual, ensuring these components are accessible to users with visual impairments presented a significant challenge. In this post, we\'ll discuss how we developed an algorithm that generates clear color descriptions for screen readers in multiple languages, while minimizing bundle size.';
+export const date = '2024-10-02';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Accessible Color Descriptions for Improved Color Pickers
+
+Recently, we released a suite of color picker components in React Aria and React Spectrum. These components help users choose a color in various ways, including a 2D [ColorArea](../ColorArea.html), channel-based [ColorSlider](../ColorSlider.html), circular [ColorWheel](../ColorWheel.html), preset [ColorSwatchPicker](../ColorSwatchPicker.html), and a hex value [ColorField](../ColorField.html). You can compose these individual pieces together to create a full [ColorPicker](../ColorPicker.html) with whatever custom layout or configuration you need.
+
+## Initial accessibility experience
+
+Accessibility is at the core of all of our work on the React Spectrum team, and ColorPicker was no exception. However, these components presented a significant challenge: colors are inherently visual, so how should we make them accessible for users with visual impairments?
+
+Our initial implementation followed the typical ARIA patterns such as [slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) to implement ColorArea, ColorSlider, and ColorWheel, and [listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) to implement ColorSwatchPicker. This provided good support for mouse, touch, and keyboard input, but the screen reader experience left something to be desired. Out of the box, screen readers would only announce raw channel values like “Red: 182, Green: 96, Blue: 38”. I don’t know about you, but I can’t imagine what color that is just by hearing those numbers!
+
+
+
+## Improving screen reader announcements
+
+We set out to improve the screen reader experience using textual descriptions of the colors that a user selected. To do this, we compiled an extensive list of color names from sources such as [Procato](https://procato.com/rgb+index/?css) and the [CSS named color keywords](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color), and used the [Delta E](https://en.wikipedia.org/wiki/Color_difference) algorithm to match the user’s selected color to the closest color name. This resulted in much more intuitive screen reader announcements such as “Moderate Cornflower Blue” instead of numeric values like “Hue: 200 degrees, Saturation: 60%, Lightness: 62%”. This was a significant improvement!
+
+However, despite the improvement, this approach presented several challenges. First, it required a huge number of strings for all of the color descriptions. We had almost 700 named colors, each of which needed to be translated into the 34 different languages we support, resulting in almost 24,000 strings measuring over 1 MB gzipped in bundle size.
+
+
+
+It was also a question whether translating all of these color names between languages would even be feasible. Languages and cultures describe colors in different ways, and translating esoteric names like “Light brilliant amaranth” and “Pale persian blue” between languages might not make sense to people around the world. This would likely require creating different color lists for each language, rather than translating a single list – a monumental task that wouldn't scale as we added new languages.
+
+Finally, in terms of accessibility, some of the color names were difficult to understand, even for native English speakers. For example, I'm not sure I would know the difference between "Arctic Blue", "Cornflower Blue", "Cobalt Blue", or "Persian Blue" without looking at them. This would pose a challenge for users with limited or no vision.
+
+## Generating color descriptions
+
+Our final solution requires only 30 short strings per language to generate a description of any color. This includes 13 hues (pink, red, orange, brown, yellow, green, cyan, blue, purple, magenta, gray, white, and black), along with the halfway points between them (e.g. red orange, yellow green, and blue purple), and modifiers for lightness (very dark, dark, light, and very light), and chroma (grayish, pale, and vibrant). These strings are combined together to form a full color description.
+
+In addition to reducing the number of strings we need, these descriptions are also simpler, more universally understood, and more easily translated between languages. For example, the description of is “light pale cyan blue”, and the description of is “dark vibrant purple magenta”.
+
+Our algorithm for generating color descriptions works in the [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) color space, recently standardized by CSS. This color space offers the advantage of uniform lightness across all hues, unlike HSL, where hues like blue appear significantly darker than hues like green or yellow at the same lightness value. The difference between HSL and OKLCH is shown below.
+
+
+
+In HSL, certain hues also appear to shift as the lightness changes — for example, blue tends to shift toward purple as it gets lighter. This would lead to perceptually inaccurate descriptions, where colors that appear purple might be described as blue. OKLCH resolves this issue by maintaining a consistent hue across all lightness levels.
+
+
+
+These properties of OKLCH allow us to generate perceptually accurate descriptions for any color. Once a color is mapped to the OKLCH color space, we determine its hue name by dividing the color wheel into segments. Since the hue channel is measured in degrees from 0 to 360, it's simple to find the closest hue name using the angles of each segment.
+
+
+ {[
+ [334, 362, 'Pink'],
+ [2, 32, 'Red'],
+ [32, 71, 'Orange'],
+ [71, 115, 'Yellow'],
+ [115, 155, 'Green'],
+ [155, 220, 'Cyan'],
+ [220, 274, 'Blue'],
+ [274, 302, 'Purple'],
+ [302, 334, 'Magenta']
+ ].map(([start, end, name]) => {
+ let center = (360 - (start + (end - start) / 2) + 180) * Math.PI / 180;
+ let radius = 280 / 2;
+ return (
+
+
+ {name}
+
+ )
+ })}
+
+
+If a hue falls at least halfway between two segments, the names of both segments are combined. For example, a hue between red and orange would be described as “red orange”. These combinations are separate localized strings in order to account for languages that have specific terms for mid-hues, such as “Rotorange” in German.
+
+There are also a few additional special cases. For instance, dark orange is referred to as “brown”, while darker yellows tend to appear more green and are described as “yellow green”.
+
+
+
+
+
+
Yellow
+
+
Yellow Green
+
+
+
+
+The hue name is combined with lightness (very dark, dark, light, very light) and chroma (grayish, pale, and vibrant) descriptors based on ranges in the L and C channels of the OKLCH color space to create a complete color description.
+
+
+
+
+
Very Light
+
+
Light
+
+
+
Dark
+
+
Very Dark
+
+
Lightness
+
+
+
+
Grayish
+
+
Pale
+
+
+
Vibrant
+
+
Chroma
+
+
+
+The order that the hue, chroma, and lightness descriptors are combined varies by language. For example in English we say `"{lightness} {chroma} {hue}"`, but in Italian the order is `"{hue} {chroma} {lightness}"`. This flexibility is achieved by using placeholders, allowing our translators to determine the appropriate arrangement for each language.
+
+Check out the color picker below to see the results of this algorithm:
+
+```tsx render
+'use client';
+import {ColorPicker} from 'react-aria-components';
+import {ColorSwatch} from '@react-spectrum/s2';
+
+// TODO: No ColorEditor in S2
+
+ {({color}) =>
+
+
+
+ {color.getColorName(navigator.language || 'en-US')}
+
+
+ }
+
+```
+
+## Final result
+
+After developing this algorithm to generate color descriptions, we integrated it into all of our color picker components. Since the same description may be generated for a range of colors, our components also announce the precise numeric value of the channels being modified. For example, a hue slider may announce “260 degrees, blue purple, slider”. Numeric values are useful for fine adjustments, while the color descriptions provide an overall sense of the color, similar to how one would perceive it visually.
+
+The video below shows interacting with a ColorArea with color descriptions. You can also try it yourself with a screen reader in the example above.
+
+
+
+Check out our [ColorPicker](../ColorPicker.html) components in React Aria to build accessible, customizable, and styleable color pickers in your own applications.
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-1.mdx b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-1.mdx
new file mode 100644
index 00000000000..5d3a5cbc395
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-1.mdx
@@ -0,0 +1,117 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import draggingVideoUrl from 'url:../../../../docs/pages/assets/button-dragging.mp4';
+import buttonVideoUrl from 'url:../../../../docs/pages/assets/button-mobile.mp4';
+import textSelectionVideoUrl from 'url:../../../../docs/pages/assets/button-text-selection.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['react aria', 'react spectrum', 'react', 'spectrum', 'interactions', 'button', 'touch'];
+export const description = 'Buttons seem like simple components at first, but they hide a lot of complexity under the hood. In the first part of this series, we\'ll look at how React Spectrum and React Aria implement adaptive press interactions that work across a wide variety of devices and interaction models.';
+export const date = '2020-08-12';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Building a Button Part 1: Press Events
+
+UI development is really hard. While building components has become much easier with modern UI frameworks like React, handling interactions across devices and supporting proper accessibility and internationalization is still extraordinarily difficult. Building UIs has a very [long tail](https://en.wikipedia.org/wiki/Long_tail): it's fairly easy to get the basics for a given component working, but there are many details to consider, and these add up to a majority of the work.
+
+In this series of blog posts, we'll look at some of the details that we've considered in React Spectrum, React Aria, and React Stately, which improve the experience across many different interaction models.
+
+## Building a Button
+
+A button seems like a simple component at first. The `` element is built into the browser, and you can style it pretty easily with CSS. This gets you pretty far, but if you sit down and test this across various types of interaction models little details start to jump out. The experience could be better on touch devices, keyboard focus works differently across browsers, and the need to adapt the experience across interaction models becomes more apparent.
+
+Stepping back, buttons actually support quite a few different types of interactions. Of course, they support clicking with the mouse, but they also support tapping on a touch screen. Hover effects are supported when using a mouse, but not when interacting with a touch screen or keyboard. Buttons also support keyboard focus, and can be activated using the `Enter` or `Space` keys. Finally, they can be pressed with a virtual cursor by assistive technology such as screen readers.
+
+Today we'll be looking at handling press events across mouse, touch, keyboard, and screen readers. In the future posts, we'll also look at handling hover effects, and keyboard focus behavior.
+
+## Touch interactions
+
+The web was created before touch devices were widespread. As a result, web APIs are designed around mouse events. When touch devices were introduced, browsers added support for touch events. However, since existing web apps had not been designed with touch in mind, browsers needed to emulate mouse events on touch devices to ensure compatibility with them.
+
+To illustrate this, when tapping a button on a touch device, mobile browsers fire the following events:
+
+- onTouchStart
+- onTouchEnd
+- onMouseMove
+- onMouseEnter
+- onMouseDown
+- onMouseUp
+- onClick
+
+This way, if applications aren't designed to support touch events, they can still handle mouse events. However, for applications that support both, these duplicate events can be quite problematic.
+
+Touch events cannot emulate mouse events perfectly. Mice support several extra dimensions of interactions, such as multiple buttons, scroll wheels, and the ability to hover over a target without pressing it. Because these features are not available on touch screens, many types of gestures have been developed to offer similar functionality. For example, it's common to double tap to zoom, scroll by panning with one finger, and long press to select text or open a context menu. This makes it considerably more complex to handle touch events, because you need to disambiguate between these gestures.
+
+Mobile browsers often introduce delays before emulated mouse events like onClick in order to help with this. For example, in order to determine if the user will double tap to zoom, the onClick event is delayed to see if a second tap occurs. This has improved to some degree over the years, and there are now various ways to opt-out of this delay, however there are still cases where it can occur if your library is running in an unknown environment.
+
+The CSS `:active` and `:hover` pseudo-classes are also affected by mouse event emulation. For example, when tapping down on a button and dragging your finger off, the active state persists even when your finger is not over it. This makes it appear like lifting your finger will activate the button when it will not. This is not how native buttons behave, so it can feel inconsistent with user expectations.
+
+
+
+## Pointer events
+
+The [pointer events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) spec is designed to help with these issues. It unifies mouse, touch, and stylus interactions into a single event model. We can handle the `onPointerDown` and `onPointerUp` events, each of which has a `pointerType` property to indicate what type of interaction triggered it. This is very useful to allow other parts of the UI to adapt based on the interaction model. For example, selection of items in a list on a desktop occurs on mouse down but on mobile it occurs on touch up.
+
+While browser support is improving, React Aria also implements fallbacks for pointer events on top of mouse and touch events. We listen for both, and if a touch event occurs prior to the mouse event, we ignore it. This way we can determine what kind of device fired the event, and also ensure that we handle events as fast as possible without waiting for browser delays.
+
+Even with pointer events there are still some browser inconsistencies though. For example, Safari on early versions of iOS 13 had a [bug](https://bugs.webkit.org/show_bug.cgi?id=199803) where `onPointerEnter` and `onPointerLeave` were not implemented correctly, so we needed to implement our own hit testing using `onPointerMove` instead. In addition, iOS still sometimes fires `onPointerUp` even if your finger isn't over the target, so we need to double check ourselves as well.
+
+## Touch cancelation
+
+Another interesting thing about touch events is that they can be canceled. For example, if you're in the middle of touching a button and you get a phone call or other notification, your press cannot be completed due to an overlay covering the element you were touching.
+
+Touch events can also be canceled by scrolling. If you touch a button and then scroll the page, you likely did not intend to activate the button. So when you release your touch, it should not trigger the button's press action. We need to disambiguate between these events and cancel the press as appropriate in order to behave as the user expects.
+
+## Text selection
+
+Text selection gestures are another case where we need to determine the user's intent. On iOS, for example, a long press begins text selection. However, when pressing a button, you wouldn't usually expect text selection to start.
+
+
+
+It is possible to add the `user-select: none` CSS property to the button to make it non-selectable, but even with that enabled, Safari still tries to select elements nearby. The only way to avoid this is to add `user-select: none` to the entire page. We wouldn't want to do this all the time though, because some elements should allow text selection to occur. React Aria automatically handles adding `user-select: none` to the page on touch start on a pressable element, and removes it after a short delay on press up. The delay is necessary because iOS may begin selecting even after touch up within some threshold.
+
+## Keyboard interactions
+
+Buttons can also be pressed using the keyboard with the `Enter` or `Space` keys. This is fairly easy to implement, but there are a few details worth considering. For example, if the element is a link, it should only be triggered by the `Enter` key and not the `Space` key, except if it has the ARIA `button` role applied.
+
+There is also the challenge of repeating keyboard events. If you press and hold a key on the keyboard, the `onKeyDown` event will repeat periodically until you release the key. This is useful in text inputs, but not really on buttons and other pressable elements, and can even be problematic. For example, if you had a button which opened a menu of selectable items, pressing and holding the `Enter` key would cause the menu to open, and then the first menu item to be selected without the user intending to do this. This is because the event repeats, so once the menu opens on the first key down event, the menu item gains focus and is triggered by the repeated event. React Aria is careful to ignore these repeating events so this does not happen.
+
+## Virtual press events
+
+When interacting with a screen reader or other assistive technology, buttons may be activated by only an `onClick` event with no preceding pointer, mouse, or keyboard events. Because of this, we still need to handle the `onClick` event, but we need to ignore click events preceded by another user event to prevent duplication.
+
+In addition, we can use various properties of the events to infer that it was triggered by a virtual cursor. In most browsers, the `event.detail` property will be zero when triggered by a virtual cursor, but in Firefox, we use the `mozInputSource` property. This allows us to set an appropriate `pointerType` for our event, which enables other components to tailor their experience for screen reader interactions.
+
+## Unified press events
+
+All of this together encompasses React Aria's [usePress](../usePress.html) hook. It handles mouse, touch, keyboard, and screen reader interactions and provides a unified API for handling all of these. `onPressStart` is fired when the user starts pressing via any interaction model, and `onPressEnd` is fired when the user lifts their pointer or drags it off of the target. `onPressStart` can be called again if the user drags their pointer back over the target. Finally, `onPress` is fired if the user lifts their pointer over the target.
+
+Each of these events receive a unified `PressEvent` object rather than the underlying native events, which allows the application code to handle press events from any interaction model the same way. A `pointerType` property similar to the one available in pointer events is included in press events, but with additional keyboard and virtual pointer types. This allows applications to adapt their event handling to different devices if needed.
+
+With the [usePress](../usePress.html) hook, our buttons handle interactions consistently. Dragging your pointer off the button correctly removes the active state, text selection is canceled, and issues with emulated mouse events are avoided.
+
+
+
+Try a live example for yourself in our [Button](../../s2/Button.html) docs!
+
+## Conclusion
+
+As you can see, buttons are deceptively complicated once you consider all of the interactions they can support. The [useButton](../useButton.html) and underlying [usePress](../usePress.html) hooks in React Aria handle all of this complexity, and ensure that everything works as expected across devices. If you are building your own button component, I'd highly recommend checking them out!
+
+I'd also like to acknowledge the work of the React core team, particularly [Dominic Gannaway](https://x.com/trueadm) and [Nicolas Gallagher](https://x.com/necolas), in researching some of the interactions described in this post. We learned a lot from their implementation in building React Aria's press event handling.
+
+In the [next part](building-a-button-part-2.html) of this series, we'll cover how React Spectrum handles hover interactions across devices.
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-2.mdx b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-2.mdx
new file mode 100644
index 00000000000..6b8f0d57071
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-2.mdx
@@ -0,0 +1,85 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import hoverVideoUrl from 'url:../../../../docs/pages/assets/button-hover.mp4';
+import hoveriPadVideoUrl from 'url:../../../../docs/pages/assets/button-hover-ipad.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['react aria', 'react spectrum', 'react', 'interactions', 'button', 'touch', 'hover', 'web development', 'javascript', 'css'];
+export const description = 'This is the second post in our three part series on building a button component. In the [first post](building-a-button-part-1.html), we covered how React Spectrum and React Aria implement adaptive press events across mouse, touch, keyboard, and screen readers. Today, we\'ll cover hover interactions.';
+export const date = '2020-08-25';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Building a Button Part 2: Hover Interactions
+
+This is the second post in our three part series on building a button component. In the [first post](building-a-button-part-1.html), we covered how React Spectrum and React Aria implement adaptive press events across mouse, touch, keyboard, and screen readers. Today, we'll cover hover interactions.
+
+## Hover interactions
+
+Hover interactions allow a user to receive some feedback when they move their pointer over an element, without pressing it. For example, the color of a button might change to give an affordance to the user that the element is clickable, or a tooltip may appear to give the user more information about what an element represents.
+
+However, hover interactions are unique to mice. Most touch devices don't allow the user to hover with their finger over an element without touching it. Keyboards support focusing elements, which is similar in some ways to hovering, but not quite the same. This presents some challenges when handling hover interactions on the web, given that web apps can run across so many different types of devices.
+
+In the [last post](building-a-button-part-1.html), we discussed how web browsers emulate mouse events for backward compatibility with older websites that were only designed with mice in mind. In addition to affecting how press events are dispatched, mouse event emulation also applies to hover events.
+
+## The :hover pseudo-class
+
+The first thing that may come to mind when you think about implementing a hover state for a component is the `:hover` CSS pseudo-class. It's built right into the browser, requires no JavaScript to use, and seems like the perfect tool for the job. Unfortunately, it suffers from the same issues with emulated mouse events that we saw with the `:active` pseudo-class and mouse events in general.
+
+On touch devices, `:hover` is emulated for backward compatibility with older apps that weren't designed with touch in mind. Depending on the browser, `:hover` might never match, might match only while the user is touching an element, or may be sticky and act more like focus. On iOS for example, tapping once on an element shows the hover style, and tapping away from the element removes it.
+
+
+
+This is not how you'd usually expect a button to behave, but browsers need to do this kind of emulation for apps that may only show or hide content on hover (e.g. navigation menus). If they did not, then perhaps this content would not be accessible at all to touch users. Unfortunately, there is no built-in way of opting out of this behavior, so we need to find another way to apply our hover styles.
+
+## Media queries
+
+The [hover](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover) and [any-hover](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover) media queries offer some hope. `@media (hover: hover)` matches when the user's primary input device supports hover interactions, and `@media (any-hover: hover)` matches when any available input device supports hovering. This seems perfect – we can wrap our `:hover` pseudo classes in a media query, and only apply them when the device supports hover.
+
+In fact, this is exactly what React Spectrum did for quite some time. But then we started testing on more types of devices, including Windows laptops with touch screens, and more recently on iPadOS 13.4, which supports trackpads and mice in addition to touch. These hybrid devices are incompatible with the hover media queries because the user can change interaction modes at any time. The `hover` media query would never match because the primary interaction mode is touch, and `any-hover` would always match because an available input device supports hover. (In reality it's even more complicated because browsers and OS's differ in which input device they consider primary). We want the hover state to apply only when the user is currently interacting with a mouse, but not when interacting with touch, so media queries won't work.
+
+## JavaScript hover interactions
+
+Our only remaining option is to use JavaScript to apply our hover states instead of CSS. We'll need to handle mouse events and apply our styles while the user is hovering over an element.
+
+However, JavaScript mouse events are also subject to emulation on touch devices. `onMouseOver` and `onMouseEnter` are fired after `onTouchEnd`. In addition, `onMouseExit` and `onMouseOut` are not fired until the user taps on another element, just like with the `:hover` pseudo class. Because of this, we need to disambiguate between real mouse events and touch emulated mouse events.
+
+As discussed in the previous post, [pointer events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) are supposed to solve these issues by exposing a `pointerType` property that specifies what kind of device the user is interacting with. While `onPointerEnter` is fired even on devices that don't support hover, we should be able to ignore these events if they have `pointerType="touch"` set.
+
+Unfortunately, on iOS there is currently a [bug](https://bugs.webkit.org/show_bug.cgi?id=214609) where even pointer events are subject to mouse event emulation. iOS fires `onPointerEnter` twice – once with `pointerType="touch"` and again with `pointerType="mouse"`. The mouse event is fired just after `onPointerUp`, and before `onFocus`. We could try setting a flag during the event with `pointerType="touch"` and ignore the following event with `pointerType="mouse"`, but since this is a bug only on iOS, this would mean that we would ignore the next mouse event on other devices, which could be long in the future when the user switches interaction modes.
+
+The solution is a bit tricky. We listen for the `onPointerUp` event globally on the document, and set a flag if `pointerType="touch"` to ignore the following `onPointerEnter` event with `pointerType="mouse"`. After a short timeout (50ms), we reset this flag back to `false`. This means that we will ignore `onPointerEnter` events with `pointerType="mouse"` for 50ms following an `onPointerUp` event with `pointerType="touch"` – long enough to ignore the emulated mouse event on iOS, but short enough to not ignore real user events in the future.
+
+This handler must be global to the document rather than local to the element being hovered due to another iOS quirk – focus events, and the prior `onPointerEnter` event with `pointerType="mouse"`, are dispatched even when you didn't touch the element directly, but somewhere nearby. iOS attempts to determine the user's intent and focuses the nearest element to their tap within some threshold. In this case, the `onPointerUp` event with `pointerType="touch"` is not dispatched on the element since the user did not actually touch it. This means we would not be able to ignore the emulated mouse event, because our flag would never be set. Using a global event listener instead of a local one allows us to handle the `onPointerUp` event with `pointerType="touch"` and ignore the following `onPointerEnter` event with `pointerType="mouse"` even if the user touched nearby the element rather than directly on it.
+
+I hear that these bugs may already be fixed in the iOS 14 betas, so hopefully we'll be able to remove this code sometime in the future.
+
+## The useHover hook
+
+We've wrapped all of this behavior into the [useHover](../useHover.html) hook in React Aria. It provides a simple way to determine if an element is hovered, and exposes a set of events that you can handle as well. `onHoverStart` is fired when the user hovers over an element with a mouse, and `onHoverEnd` is fired when the user moves their mouse off of the element. We take care of all of the browser inconsistencies discussed above, and also include fallbacks for touch and mouse events to support older devices without pointer events.
+
+The [Button](../../s2/Button.html) component, and all other components in React Spectrum that support hover states, use the [useHover](../useHover.html) hook to handle interactions, and apply a CSS class when they are hovered. This ensures that hover states are only applied when interacting with a mouse, which avoids unexpected behavior on touch devices.
+
+
+
+Try a live example for yourself in our [Button](../../s2/Button.html) docs!
+
+## Conclusion
+
+As we've seen, cross-device interactions are difficult to handle across so many different types of devices. Even "simple" components like buttons are much more complicated than they seem at first. If you're building your own button component, I'd recommend checking out the [useButton](../useButton.html) and [useHover](../useHover.html) hooks, which will help ensure that everything works as expected across a wide variety of devices.
+
+In the [next part](building-a-button-part-3.html) of this series, we'll cover how React Spectrum and React Aria handle focus behavior across devices and browsers.
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-3.mdx b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-3.mdx
new file mode 100644
index 00000000000..6cd5dfc1ae9
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-3.mdx
@@ -0,0 +1,79 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import focusRingVideoUrl from 'url:../../../../docs/pages/assets/focus-ring.mp4';
+import keyboardSettingsImageUrl from 'url:../../../../docs/pages/assets/keyboard-settings.png';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['react aria', 'react spectrum', 'react', 'interactions', 'button', 'keyboard', 'focus', 'web development', 'javascript', 'css'];
+export const description = 'This is the last post in our three part series on building a button component. In the [first post](building-a-button-part-1.html), we covered how React Spectrum and React Aria implement adaptive press events across mouse, touch, keyboard, and screen readers. In the [second post](building-a-button-part-2.html), we covered hover interactions. Today, we\'ll cover keyboard focus behavior.';
+export const date = '2020-09-09';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Building a Button Part 3: Keyboard Focus Behavior
+
+This is the last post in our three part series on building a button component. In the [first post](building-a-button-part-1.html), we covered how React Spectrum and React Aria implement adaptive press events across mouse, touch, keyboard, and screen readers. In the [second post](building-a-button-part-2.html), we covered hover interactions. Today, we'll cover keyboard focus behavior.
+
+## Keyboard navigation
+
+Keyboard navigation allows users who cannot physically use a mouse or touch screen, for example due to a motor disability, to navigate an application. As an additional benefit, it can also allow power users to navigate your application more quickly, without lifting their hands from the keyboard.
+
+At a high level, keyboard navigation is broken into **tab stops**, which may be navigated by pressing the `Tab` key to move to the next tab stop, and `Shift` + `Tab` to move to the previous tab stop. A tab stop may be an atomic component like a text field or button, or a composite component like a listbox, radio group, grid, or toolbar. Composite components behave as a single tab stop. Elements within a composite component are typically navigated with the arrow keys, while the `Tab` key continues to navigate to the next/previous tab stop.
+
+Keyboard navigation relies on the concept of **focus**. At any given time, a single element on screen is considered the **active element**, which is the element that will receive keyboard events. As the user navigates around, either with the keyboard or via a pointer or assistive technology, the active element updates and the `focus` and `blur` events are fired. The browser only handles the `Tab` key by default, so any time we need more advanced keyboard behavior, we need to implement it in JavaScript. This is commonly referred to as **focus management**.
+
+There are many aspects of focus management, and perhaps we will cover more in future posts, but today we'll discuss focus rings, and normalizing browser differences in focus behavior.
+
+## Focus rings
+
+An important feature for keyboard users is a **focus ring**. This is a visual affordance for the currently focused element, which allows a keyboard user to know which element they are currently on. It may only be visible when navigating with a keyboard, however, so as not to distract mouse and touchscreen users.
+
+
+
+As you can see in the above video, the focus ring appears around each button when it receives keyboard focus, but when the user interacts with a mouse it does not appear. To implement this, we attach global event listeners for pointer, keyboard, and focus events at the document level and keep track of the most recent input modality that the user was interacting with. If the user most recently interacted with a keyboard or assistive technology, we show the focus ring, otherwise we do not show it.
+
+There are many nuances to this, however. For example, when clicking a text input to focus it with the mouse, and then typing into it, we want to keep the mouse focus style and not switch to keyboard focus. In this case, we only show the keyboard focus ring if the user presses a navigation key such as `Tab`. Another case where a keyboard event occurs but we do not show the keyboard focus ring is for keyboard shortcuts with modifiers such as `Ctrl`, `Cmd`, or `Alt`. These keys are likely performing a command rather than navigating, so it makes sense to keep the current input modality the same.
+
+Another challenge is that focus events may occur without any preceding user event. For example, when navigating through a form with the next and previous buttons on the software keyboard in iOS, only a focus event is fired, with no keyboard or pointer events before it. This can also occur when navigating with an assistive technology like a screen reader. In these cases, we don't know how the navigation occurred, so we default to showing the focus ring to ensure the user knows where focus went.
+
+However, we do not want programmatic `focus()` calls to affect the current input modality. The user may click on an element with the mouse, and in response focus is moved somewhere else programatically. For example, when clicking on a button to open a menu, focus is typically moved to the first menu item. However, because `focus` events are still fired when focusing an element programmatically, we need to ignore these events to ensure the focus ring does not appear or disappear based on programatic focus movement.
+
+There are also various inconsistencies in the number and order of focus events across browsers. For example, Firefox fires two extra focus events when the user first clicks on any element in an iframe: first on the window, then on the document. Finally, it fires a focus event on the element itself. We need to ignore these extra focus events so they don't unintentionally cause the focus ring to appear when using a mouse.
+
+In the future, the [:focus-visible](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) pseudo class in CSS may be able to replace this code. However, since the [spec](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) does not say when it should apply, browsers will likely implement different heuristics, which will mean it will behave inconsistently. Until browser support improves, the [useFocusVisible](../useFocusVisible.html) and [useFocusRing](../useFocusRing.html) hooks in React Aria can be used to implement focus rings that work consistently across browsers.
+
+## Ensuring consistent focus behavior
+
+In addition to focus rings, we also need to ensure buttons have consistent focus behavior. Believe it or not, browsers behave differently when it comes to the native `` element, as well as other form controls such as checkboxes and radios. These components should receive focus when the user presses down with their mouse or finger, but browsers sometimes don't do this.
+
+Ensuring buttons are focused when interacting with a mouse or touch is very important for event ordering consistency that other parts of an application may rely on, and also for features like restoring focus from a dialog or other overlay. When opening an overlay, we typically record where focus was on the page before it opened so that we can restore focus back to it when the overlay closes. If the button used to open the overlay never received focus, we would not be able to handle this properly.
+
+Unfortunately, Safari both on macOS and iOS reaaaally doesn't want to do this. Native buttons on these platforms typically do not receive focus at all, unless you enable an accessibility setting (shown below). This means that tabbing through elements will only show text inputs, and not other elements like buttons, checkboxes, radios, etc. However, Safari is the only browser on macOS that respects this setting – both Chrome and Firefox always allow tabbing to all of these elements. We do not do anything to normalize this behavior for Safari because it's likely that if you're a keyboard user, you already have this setting enabled.
+
+
+
+Even with this setting turned on, however, Safari still does not focus buttons and other native form elements on mouse down or touch start. A [bug](https://bugs.webkit.org/show_bug.cgi?id=22261) for this has been open against WebKit since 2008, and it seems unlikely to be fixed any time soon. In this case, we do need to normalize this to ensure browsers are consistent. We can handle focusing the element programmatically on mouse down ourselves.
+
+However, it gets even more tricky on iOS. While on macOS, Safari will respect our programatic focus, on iOS the browser attempts to forcibly blur the element *asynchronously* sometime after the `onClick` event is fired. This means that even programmatically, focusing the button will not work. 🤯
+
+The only solution is to call `event.preventDefault()` on all mouse and touch events on the element, and handle focusing ourselves. This ensures that the browser does not perform any of its default behavior, including this forced blur, but it means that we'll have to handle all of the default browser behavior ourselves.
+
+This focus normalization behavior is implemented by the [usePress](../usePress.html) hook in React Aria, which is used by [useButton](../useButton.html), [useCheckbox](../useCheckbox.html), [useRadio](../useRadioGroup.html), and many other hooks. If you're implementing your own components, I'd highly recommend checking them out to ensure the focus behavior is consistent across browsers.
+
+## Conclusion
+
+In this series, you've seen how complicated even "simple" components like buttons can be when you consider all of the interactions they can support. React Aria aims to simplify this complexity and provide consistent behavior out of the box, while giving you complete rendering and styling control for your own components. This lets you focus on your unique design requirements, and build high quality components much faster. If you're working on a design system, check it out!
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/building-a-combobox.mdx b/packages/dev/s2-docs/pages/react-aria/blog/building-a-combobox.mdx
new file mode 100644
index 00000000000..836a064fd34
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/building-a-combobox.mdx
@@ -0,0 +1,147 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import comboboxExampleImageUrl from 'url:../../../../docs/pages/assets/combobox-example.png';
+import screenreaderSpreadsheetImageUrl from 'url:../../../../docs/pages/assets/screenreader-spreadsheet-combobox.png';
+import comboboxAccessibilityUrl from 'url:../../../../docs/pages/assets/combobox-accessibility.mp4';
+import comboboxVideoUrl from 'url:../../../../docs/pages/assets/combobox.mp4';
+import comboboxVisualViewportUrl from 'url:../../../../docs/pages/assets/combobox-visual-viewport.mp4';
+import comboboxScrollingUrl from 'url:../../../../docs/pages/assets/combobox-scrolling-safari.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['combobox', 'accessibility', 'mobile', 'react spectrum', 'react', 'spectrum', 'interactions', 'touch'];
+export const description = 'After many months of research, development, and testing, we\'re excited to announce that the React Spectrum [ComboBox](../react-spectrum/ComboBox.html) component and React Aria [useComboBox](../useComboBox.html) hook are now available! In this post we\'ll take a deeper look into some of the challenges we faced when building an accessible and mobile friendly ComboBox.';
+export const date = '2021-07-13';
+export const author = 'Daniel Lu';
+export const authorLink = 'https://github.com/LFDanLu';
+export const hideNav = true;
+
+# Creating an accessible autocomplete experience
+
+After many months of research, development, and extensive testing across browsers, devices, and assistive technology, we're excited to announce that the React Spectrum [ComboBox](../react-spectrum/ComboBox.html) component and React Aria [useComboBox](../useComboBox.html) hook are now available! We've focused on the following areas to help you build quality autocomplete experiences.
+
+- **Accessibility** — Our ComboBox has been tested with screen readers across desktop and mobile devices, and with many different input methods including mouse, touch, and keyboard. We encountered many screen reader differences, and worked hard to ensure announcements are clear and consistent.
+- **Mobile** — On small screens, the React Spectrum ComboBox is automatically displayed in a tray, which improves the user experience by giving them a larger area to scroll. We also optimized our experience for touch screen interactions and on screen keyboards.
+- **Asynchronous loading** — Autocomplete suggestions can be loaded asynchronously, and large lists can be loaded on demand through infinite scrolling.
+- **Customizability** — React Aria hooks allow full control over the rendering and styling of your ComboBox component, while letting us take care of the behavioral complexities for you. Use our default filter or you can provide custom filtering for complete control.
+
+
+
+## Building a ComboBox
+
+For those who may be unfamiliar with a combobox (alternatively known as an "autocomplete"), it is an input field that has a popover listbox associated with it. The listbox contains
+a list of options within that a user may select as the value for the combobox and is often filtered to display possible matches to the user's current input text. Finally, there may be a button accompanying the input field used
+for toggling the open state of the popover listbox. An example of a combobox that you may be familiar with is the search bar found on Google, Youtube, or X.
+
+
+
+While the above may seem rather straight forward, a combobox contains a significant amount of complexity. The state logic of the combobox needs to track the input value and selected option, determining when to sync the two or allow each to
+diverge depending on the user interaction. This logic is complicated even further if the combobox allows the user to submit custom values or if the input value or selected item is controlled via props instead. Additionally, the combobox can be used
+on a variety of platforms that each support different interaction methods (touch, virtual clicks, keyboard and mouse), resulting in subtle nuances in the combobox's behavior.
+In this blog post, we'll be covering the challenges we encountered with regards to ComboBox's mobile experience and accessibility.
+
+## Mobile experience
+
+### Onscreen Keyboard
+
+Like many of our other components, ComboBox displays its options in a tray rather than a popover when rendered on a mobile device or a smaller screen. This allows for a better mobile experience
+since the tray can display more items on the screen and grants users a larger hit area to scroll through. To compensate for the fact that the tray covers the majority of the
+screen including the ComboBox, we included an input field within the tray so that end users could still type to filter the available items. So far so good. However,
+when we tested the ComboBox's tray input on iOS Safari, the tray's bottom half was covered by the onscreen keyboard and we could not scroll the hidden items into view.
+
+It turns out that iOS Safari does not shrink the browser window to accommodate the onscreen keyboard unlike other mobile browsers. Instead, Safari pushes the window upwards and
+partially off screen. At the time, we had been relying on `window.innerHeight` to inform us on how tall the tray should be but now we had to find a different way of tracking the available window
+space for the tray.
+
+Luckily for us, iOS 13 added support for the [VisualViewport](https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport) API. By querying `window.visualViewport.height` we could get a reliable measurement of how much vertical space was available on screen. Furthermore, we could track
+when the onscreen keyboard was opened or dismissed by listening to the VisualViewport's `resize` event. Leveraging these two allowed us to create a tray that properly adjusts to the presence of iOS onscreen keyboard. Check out the video below
+to see how the ComboBox tray worked before and after we switched to the VisualViewport API. If you'd like to track the visual viewport size in your own app, you can use the [useViewportSize](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/utils/src/useViewportSize.ts) hook available in the `@react-aria/utils` package.
+
+
+
+### Page Scrolling
+
+Another issue we encountered had to do with iOS Safari page scrolling behavior. When the onscreen keyboard is visible, iOS Safari makes the page scrollable so that users can still access content that is hidden behind the keyboard. However,
+now that our ComboBox tray sizes itself to fit in the visual viewport, users could now scroll the entire tray itself off screen. To stop this from happening, we prevent default on `touchmove` events that happen on the document body
+or root element of the document. This preserves the user's ability to scroll through the options in the tray but blocks any attempt to scroll the page itself until the tray is closed. The video below
+illustrates the difference in scrolling behavior before and after our fix. If you are building your own overlays and would like to prevent this kind of document scrolling behavior, check out the [usePreventScroll](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/usePreventScroll.ts) hook in the `react-aria/overlays` package.
+
+
+
+## Accessibility
+
+To ensure that our ComboBox is accessible, we followed the [WAI-ARIA 1.2 combobox example](https://www.w3.org/TR/wai-aria-practices-1.2/#combobox). Unfortunately, differences between our implementation and the spec arose
+as we built ComboBox and subsequent testing sessions revealed varied support across screen readers. Here are just a couple of the challenges we faced and the solutions we came up with to address them.
+
+### Portals
+
+One significant difference between the WAI-ARIA 1.2 ComboBox spec and our ComboBox implementation was the usage of React Portals for the listbox. In the [example](https://www.w3.org/TR/wai-aria-practices-1.2/examples/combobox/combobox-autocomplete-list.html)
+the listbox is a sibling to the combobox, allowing for easy navigation between the two elements when using a screen reader. The same couldn't be said for our listbox since
+it is portalled to the end of the document, meaning there could be any number of elements that a screen reader user would have to navigate through just to move from the ComboBox input to the listbox itself. For touch screen reader users, it would be particularly problematic
+since they may not have access to a physical keyboard and thus can't use the arrow keys to navigate through options.
+
+This put us in a bind because removing the portal strategy wasn't really an option. If we didn't portal the popover out to the end of the document, we'd run into the risk of the listbox being clipped if the combobox's parent had `overflow: hidden` or `overflow: scroll`
+set. Therefore, we had to figure out a way to make the input and listbox the only elements accessible to screen readers while the popover is open so that a screen reader user wouldn't accidentally leave the ComboBox context.
+
+The solution that we came up with was to crawl the DOM and apply `aria-hidden` to every element that wasn't the input or listbox. To crawl the DOM we used a [TreeWalker](https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker),
+setting up a node filter to determine if a node should be left visible, hidden, or skipped in the case where its parent was already hidden. In addition, we watch for any changes in the DOM while the listbox is open
+via a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver), hiding those new elements if need be. When the popover closes, every node that we modified is reverted back to its previous state.
+Check out our [ariaHideOutside](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/ariaHideOutside.ts) function in `@react-aria/overlays` if you'd like to learn more.
+
+### Mobile implementation divergence
+
+Another divergence between the WAI-ARIA 1.2 ComboBox example and our ComboBox implementation came as a result of our mobile implementation. Initially, we considered the outer input and the tray input as both being individual combo boxes that mirror each other's state. This became increasingly complicated as we
+tried to manage things like focus and internal state between the two inputs. The result included adding several tray input specific affordances to our `useComboBox` hook as well as reducing the aria hook's generality by introducing this React Spectrum specific behavior. Interacting with the tray input using a screen reader was also problematic
+since the screen reader would detect that the input had `role="combobox"` and thus erroneously announce "double tap to close", a touch action usually reserved for focusing an input. After some deliberation, we decided to alter the accessibility implementation for the mobile experience, removing the outer combo box input in favor of a button that
+resembles a combo box. Tapping the button opens the tray that contains the actual combo box input, albeit with `role="searchbox"` instead of `role="combobox"` to avoid the aforementioned screen reader announcement.
+
+### Differences in screen readers
+
+Just like browsers, screen readers can be quite varied in behavior and may require deliberate tooling to ensure a consistent degree of functionality. While automated accessibility tools can be useful for ensuring that your component meets baseline accessibility guidelines,
+manual testing is irreplaceable for surfacing these behavioral differences, illustrated in part by the image below.
+
+
+
+#### NVDA
+
+Initially, our ComboBox would automatically focus the first item in the listbox whenever opened so the user wouldn't have to issue another keypress to begin option navigation. However, when we tested the ComboBox using NVDA we discovered that
+character deletions and text cursor movement in the ComboBox input weren't being announced at all. NVDA would only resume announcing if no options had virtual focus via `aria-activedescendant`, so we ended up clearing option focus on any changes to the input text or
+left/right arrow key presses.
+
+#### VoiceOver
+
+VoiceOver has limited support for `aria-activedescendant`, failing to announce when the focused ComboBox item changed in a variety of situations. In Safari, changes to the `aria-activedescendant` aren't announced
+when the input is empty. In Chrome, VoiceOver only announces changes to `aria-activedescendant` if `aria-selected="true"` is applied to the focused item. If the list of options is organized into sections using `role="group"`,
+VoiceOver doesn't announce the focused item at all in any browser.
+
+Announcing item focus wasn't the only issue we encountered. VoiceOver doesn't announce the number of options currently available in the menu or the current section title, information that a user would find helpful when navigating the ComboBox options.
+When selecting an option from the listbox, an announcement of the item's name is made but the fact that it is now "selected" is omitted.
+
+As a workaround, we made use of [ARIA live-regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). When the ComboBox's listbox is opened for the first time, we create two visually hidden `aria-live` regions in the DOM with two `div` elements, one with `aria-live="polite"` and the other `aria-live="assertive"`
+so that we have control over how the message should be announced to the user. Then whenever updates occur in the ComboBox, we can have the screen reader announce custom messages by updating the contents of these elements. Thankfully, the bulk of this functionality was setup already in
+React Aria's `LiveAnnouncer`, allowing us to reuse it for our ComboBox after some updates.
+
+Special care was taken such that the messages themselves only contained relevant and concise information tailored to the current state of the ComboBox at any given time. For example, the ComboBox only announces the current section name and item count when the user enters a new section in the listbox.
+When the user then moves to a different option in the same section, only the newly focused item name is announced. Similarly, the total option count is only announced when number of options available in the listbox changes. Since many of these messages were added to fill in gaps in VoiceOver's announcement,
+we only trigger the `LiveAnnouncer` on Apple devices to avoid announcement overlap on other screen readers.
+
+If you are interested in using this `LiveAnnouncer` yourself, check out [LiveAnnouncer](https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/live-announcer/src/LiveAnnouncer.tsx) in `@react-aria/live-announcer`. Otherwise, the [useComboBox](../useComboBox.html) hook provides you with all of the custom messaging out of the box. See the video below for a sneak peek!
+
+
+
+## Conclusion
+
+When we first set off to build ComboBox, we had no idea of the complexity that awaited us. If you're building your own combo box component for your design system, I'd recommend checking out the [useComboBox](../useComboBox.html) hook which handles all of this complexity and has been tested across a wide variety of devices, browsers, and assistive technology combinations.
+It really just goes to show how important cross browser and device testing is when building a component.
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/creating-a-pointer-friendly-submenu-experience.mdx b/packages/dev/s2-docs/pages/react-aria/blog/creating-a-pointer-friendly-submenu-experience.mdx
new file mode 100644
index 00000000000..648000d5c5d
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/creating-a-pointer-friendly-submenu-experience.mdx
@@ -0,0 +1,115 @@
+{/* Copyright 2024 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import SubmenuSafeArea from '../../../../docs/pages/assets/submenu-safe-area.svg';
+import SubmenuAtan2 from '../../../../docs/pages/assets/submenu-atan2.svg';
+import SubmenuAtan2FromPointer from '../../../../docs/pages/assets/submenu-atan2-from-pointer.svg';
+import {SubmenuAnimation} from './SubmenuAnimation';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['react aria', 'react spectrum', 'react', 'spectrum', 'interactions', 'submenu', 'pointer'];
+export const description = 'We are excited to announce support for submenus in the latest release of [React Spectrum](../../s2/Menu.html#submenus) and [React Aria](../Menu.html#submenus)! In the process of adding this feature, we found ourselves solving some unique challenges while working to make submenus user-friendly and accessible across an array of devices and input types. In doing so, we wanted to share our thought process in solving one of the challenges we faced along the way.';
+export const date = '2024-05-01';
+export const author = 'Reid Barber';
+export const authorLink = 'https://github.com/reidbarber';
+export const hideNav = true;
+
+# Creating a pointer-friendly submenu experience
+
+We are excited to announce support of submenus in the latest release of [React Spectrum](../../s2/Menu.html#submenus) and [React Aria](../Menu.html#submenus)! In the process of adding this feature, we found ourselves solving some unique challenges while working to make submenus user-friendly and accessible across an array of devices and input types. In doing so, we wanted to share our thought process in solving one of the challenges we faced along the way.
+
+## The Shortest Path
+
+Submenus (or nested menus) enable multi-level exploration of menus, and even with a large number of options, users should be able to quickly find their desired option. A user should be able to hover over an item to see its submenu. Then, they should be able to move their pointer directly to any item in the newly opened submenu, following the shortest path. While doing so, the pointer may leave the original item entirely and hover an unrelated item in its path towards the submenu, causing the submenu to close. We need a way to know when they're moving their pointer to that submenu, so we can keep it open until they reach the submenu.
+
+
+
+## Predicting User Intent
+
+We can predict the user's intent by tracking:
+
+* Pointer movement **direction**
+* Pointer movement **speed**
+
+We can do this by listening for [pointermove](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) events and analyzing the changes in the pointer's position (also called delta).
+
+## Valid Movements
+
+Imagine two lines: one from the pointer to the top of the submenu, and one from the pointer to the bottom of the submenu. We now have an area where the user might move their pointer on its path to the submenu. Any movement outside of this range should be considered invalid and should close the submenu.
+
+
+
+## Pointer Direction
+
+We can measure the angle at which the pointer moved by using the 2-argument arctangent, or [atan2](https://en.wikipedia.org/wiki/Atan2). If we provide our pointer's delta x and delta y values as arguments, we'll get the angle (in radians) from the previous pointer position to the current pointer position in relation to the positive x-axis.
+
+
+
+Now we can use the atan2 function to measure the angles formed by three separate lines:
+
+* **Θtop ** : Angle formed by the line from the previous pointer position to the **top inside corner** of the submenu
+* **Θbottom ** : Angle formed by the line from the previous pointer position to the **bottom inside corner** of the submenu
+* **Θpointer ** : Angle formed by the line from the previous pointer position to the **current pointer** position (delta)
+
+
+
+If the pointer's delta angle is **between** the top and bottom angles, we know the user is moving their pointer in the direction of the submenu.
+
+Θtop > Θpointer > Θbottom
+
+## Pointer Speed
+
+In order to predict the user's intent, we also need to know the pointer's speed. Here are some things we know about how users move their pointer:
+
+* Users typically accelerate their pointer when moving towards the submenu, then decelerate as they reach their target.
+* Users sometimes stop or slow down their pointer to browse options in the submenu.
+* Users tend to move their pointer more quickly if they have a larger distance to cover (see [Fitts's Law](https://en.wikipedia.org/wiki/Fitts%27s_law)).
+* Users tend to move their pointer more quickly if they have a larger "tunnel" to navigate through (see [Steering Law](https://en.wikipedia.org/wiki/Steering_law)).
+
+We could check the pointer's speed continuously and use the above knowledge to predict the user's intent.
+
+Alternatively, we could simply use a **timeout**; if the pointer hasn't moved after a certain amount of time and is no longer over the submenu's parent menu item, we assume they are no longer intending to go to the submenu. This timeout can be reset after each pointer movement.
+Speed is about movement over time, so we use the timeout to detect if there is no movement over some specific time. Since users with motor impairments may take more time to move their pointer to the destination, we should lean towards using a larger timeout value.
+
+Although the timeout solution is simpler than tracking the pointer's speed, we found that it resulted in a good user experience, so we proceeded with this approach.
+
+## Fault Tolerance
+
+Since our users are human, we want to build in some fault tolerance, but not so much that we invalidate their intent. Here are some ways we did that:
+* **Widen our range of allowed angles**: We added 15 degrees of tolerance to the top and bottom angles. After testing different values, we found that this created the best experience.
+* **Allow invalid movements**: We require that two consecutive invalid pointer movements be made before closing the submenu. Users who experience [tremors](https://en.wikipedia.org/wiki/Tremor) may involuntarily move their pointer in other directions, so it is important to include this feature.
+
+## Optimizations
+
+We can introduce a few performance optimizations:
+
+* **Throttle**: Most devices refresh their screens 60 times per second. This means that we don't need to do these measurements more frequently than every 16 ms (1 second / 60 = 16.66 ms). We can also experiment with doing checks even less frequently while still maintaining a good user experience. For instance, lowering the sample rate may provide more accurate predictions for users who experience tremors.
+* **Track movements only when necessary**: We can start tracking pointer movements when the submenu opens and stop tracking when the submenu closes, or the pointer reaches the submenu.
+* **Calculate angles only when necessary**: If the pointer is moving in the opposite direction of the submenu, there's no need to calculate and compare angles. We can check this by comparing the pointer's delta x to the submenu's closest edge's x.
+* **Check the pointer event type**: We can check the pointer event's [pointerType](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType) property and ignore the event if it is type 'pen' or 'touch'.
+
+## Alternatives
+
+Let's compare our approach to a few other methods:
+
+* **Timeout only**: We could use just a timeout instead of incorporating the direction the pointer moves. The downside of this is that moving the pointer vertically between parent menu items would cause delayed interactions that could be unpleasant or unexpected for the user.
+* **Delay before opening submenus**: We could introduce a delay before opening each submenu, but that would introduce a similar negative user experience as the timeout-only method described above.
+* **Check if the point is within a triangle**: We could use one of the various [point-in-polygon](https://en.wikipedia.org/wiki/Point_in_polygon) algorithms to determine if the pointer is within the triangle shape we defined earlier. There are several methods described by Cédric Jules in [Accurate point in triangle test](https://totologic.blogspot.com/2014/01/accurate-point-in-triangle-test.html) that we could use to implement this.
+* **Compare the slopes of the various lines**: This method is very similar to the 2-argument arctangent method we used, and it is used by the [jQuery-menu-aim](https://github.com/kamens/jQuery-menu-aim) plugin. This was written by Ben Kamens and detailed in [Breaking down Amazon's mega dropdown](https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown).
+* **Render an invisible triangle over the parent menu**: We could use an absolute-positioned HTML element and use [clip-path](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path) to give it a triangle shape. There is an excellent post by Andreas Eldh called [Invisible Details](https://medium.com/linear-app/invisible-details-2ca718b41a44) that walks through how to implement this. Similarly, we could draw an [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG) instead, as outlined by Costa Alexoglou in [Better Context Menus With Safe Triangles](https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/). This method is also mentioned in [Building like it's 1984: A comprehensive guide to creating intuitive context menus](https://height.app/blog/guide-to-build-context-menus) by Michael Villar. We originally considered this approach but found it to be less reliable when moving your pointer more quickly than the triangle can get re-rendered. We also wanted to avoid the additional memory usage of rendering the extra element if possible.
+
+## Conclusion
+
+We hope this post has been helpful in understanding how we approached building a good submenu experience for mouse users. We are excited to see how you use submenus in your own projects. You can see this feature in action in the [React Spectrum Menu](../../s2/Menu.html#submenus) and [React Aria Menu](../Menu.html#submenus), including in the table on the [React Aria Home Page](../).
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/date-and-time-pickers-for-all.mdx b/packages/dev/s2-docs/pages/react-aria/blog/date-and-time-pickers-for-all.mdx
new file mode 100644
index 00000000000..2aeed8bd45f
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/date-and-time-pickers-for-all.mdx
@@ -0,0 +1,189 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import Anatomy from '../../../../docs/pages/assets/daterangepicker-anatomy.svg';
+import calendarMobileScreenReader from 'url:../../../../docs/pages/assets/calendar-mobile-screen-reader.mp4';
+import calendarMobileScreenReaderVTT from 'url:../../../../docs/pages/assets/calendar-mobile-screen-reader.vtt';
+import datepickerVideo from 'url:../../../../docs/pages/assets/datepicker.mp4';
+import datepickerScreenReader from 'url:../../../../docs/pages/assets/datepicker-screen-reader.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+import {DateField, RangeCalendar} from '@react-spectrum/s2';
+import {today, getLocalTimeZone} from '@internationalized/date';
+import RangeCalendarExample from './RangeCalendarExample';
+import CalendarSystems from './CalendarSystems';
+
+export const tags = ['date picker', 'date', 'time', 'calendar', 'components', 'accessibility', 'mobile', 'react spectrum', 'react', 'spectrum', 'interactions', 'touch'];
+export const description = 'We are very excited to announce the release of the [React Aria](../useDatePicker.html) and [React Spectrum](../../s2/DatePicker.html) date and time picker components! This includes a full suite of fully featured components and hooks including calendars, date and time fields, and range pickers, all with a focus on internationalization and accessibility. It also includes [@internationalized/date](../../internationalized/date/index.html), a brand new framework-agnostic library for locale-aware date and time manipulation.';
+export const date = '2022-06-21';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Date and Time Pickers for All
+
+We are very excited to announce the release of the [React Aria](../useDatePicker.html) and [React Spectrum](../../s2/DatePicker.html) date and time picker components! This includes a full suite of fully featured components and hooks including calendars, date and time fields, and range pickers, all with a focus on internationalization and accessibility. It also includes [@internationalized/date](../../internationalized/date/index.html), a brand new framework-agnostic library for locale-aware date and time manipulation.
+
+In building these components, we have focused on the following areas:
+
+- **Flexibility** – Our hooks support a wide variety of use cases and functionality including displaying custom date ranges in Calendar (e.g. multiple months, week views, etc.), support for marking dates as unavailable, non-contiguous range selections, validation, configurable granularity, time zone support, and more.
+- **Internationalization** – We have extensive support for internationalization, including 13 different calendar systems such as Gregorian, Buddhist, Islamic, Persian, and more. Locale-specific formatting, number systems, 12 and 24 hour time, and right-to-left support are available as well.
+- **Accessibility** – All of our date and time picker components have been tested across desktop and mobile devices, and with many different input methods including mouse, touch, and keyboard. We have worked hard to ensure screen reader announcements are clear and consistent.
+- **Customizability** – As with all of React Aria, our hooks give you full control over the rendering and styling of your components, while letting us handle the internationalization and accessibility challenges for you. We have examples using many different styling libraries, such as Tailwind CSS, Styled Components, CSS modules, and Chakra UI.
+
+
+
+## User experience
+
+Picking dates and times is a complex task, and designing a user experience that takes into account internationalization, accessibility, and usability across many types of devices is a huge challenge. We worked together with the Adobe Spectrum design, accessibility, internationalization, and product teams to meticulously research, design, test, and iterate on our components while taking into account each of these challenges.
+
+
+
+### Date fields
+
+Many date picker components include a simple text field where the currently selected date is displayed. Sometimes, a user can also type into this field to enter a date, but this is often difficult to use because the user needs to know what date format is expected, and it's easy to make mistakes. This also poses a challenge for internationalization, because users may enter dates in many different formats, languages, [numbering systems](how-we-internationalized-our-numberfield.html#internationalization), and more. Some of these formats may be ambiguous, for example, is "2/3/2022" "February 3rd" or "March 2nd"? The answer depends on your region of the world. In practice, it is nearly impossible to reliably parse free form text that a user might enter into a date field when you consider all of these possible variations.
+
+We took a different approach, and followed the lead of native date picker UIs on platforms like macOS, as well as many implementations of ` ` in browsers. Rather than a free-form text field, we render individually focusable segments for each date and time unit. Users can type a number into each segment, and focus is automatically advanced to the next segment as they go. They can also use the up and down arrow keys to increment or decrement a value, or use page up/page down to adjust the value by larger amounts. Try it out in the example below.
+
+
+
+
+
+This approach has benefits for internationalization and accessibility, as well as usability on mobile. For internationalization, individual segments avoid the problem of parsing dates in various formats entirely. The date format is automatically determined based on the locale, and the user only needs to fill in the values and not worry about messing up the separators or getting the order wrong. Each segment is also individually labeled for accessibility, so users always know which field they are on (e.g. "year", "month", "day", etc.). This is much easier to use for screen reader users than a plain text field where the expected format is unknown. Finally, on mobile, we can take advantage of the numeric software keyboard, which is nicer to use than a full QWERTY keyboard.
+
+
+
+The [useDateField](../useDateField.html) and [useTimeField](../useTimeField.html) hooks (or the [DateField](../../s2/DateField.html) and [TimeField](../../s2/TimeField.html) React Spectrum components) may be used standalone in cases where the user is likely to already know the date they need to enter, or the date is far in the past or future, e.g. a birthday or passport expiration date. In these cases, browsing through a calendar UI to find a date is tedious, and entering the date with a keyboard is much more efficient.
+
+### Calendars
+
+When a user doesn't know what date they will select, it can be useful to offer a browsing experience using a calendar component. This allows users to see dates organized into weeks and months, or with additional context such as which dates are unavailable for selection. Calendar follows the [ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/), which allows keyboard users to navigate using the arrow keys, and press the Enter key to select a date.
+
+Calendars can quickly get complicated, with many different states which need to be represented both visually and to screen reader users. For example, a [RangeCalendar](../../s2/RangeCalendar.html) allows users to select not only a single date, but a range of dates. Certain dates can be marked as unavailable, e.g. in an appointment booking application. When combined, a user may be allowed to select only contiguously available ranges (e.g. a rental house), or non-contiguous (e.g. a time off request where weekends are not included). Minimum and maximum allowed dates may also be defined. Finally, if a user makes an invalid selection external to the calendar, we may need to display an invalid state. You can play around with some of these states in the example below.
+
+
+
+All of these states posed a challenge for us to clearly communicate to assistive technology users, without overwhelming them or making the announcements too verbose. The `aria-label` for each calendar cell includes the date itself (localized in the user's language), and includes the day of the week. Visual users get this context from the column headers and layout of the grid itself, but screen reader users may navigate linearly through the dates and may not be able to easily infer it. We also include whether the date is selected, today, disabled, invalid, or the minimum or maximum available date (if such restrictions are imposed).
+
+For range calendars, we also ensure that the selected date range is clearly communicated. This is announced using an [ARIA live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) whenever a date or date range is selected, and is also included in the label on the first and last dates in a selected range. We also use the [Intl.DateTimeFormat#formatRange](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/formatRange) API to generate a minimal description of the date range to reduce verbosity of the announcements. For example, rather than announcing "Monday, May 9, 2022 to Friday, May 20, 2022" we announce "Monday, May 9 to Friday, May 20, 2022" (note that 2022 is not repeated). Thanks to `Intl.DateTimeFormat`, this is automatically localized into the expected format for every language.
+
+Another important area we considered with our calendar components was mobile. With touch screen readers, users access each control on screen using swipe gestures to move a virtual cursor forward and backward. Because of this, calendars can be quite tedious to navigate because they contain so many elements, especially when multiple months are displayed at once. We made sure to provide context when a user enters a calendar of the whole range of visible dates, and included an extra visually hidden "next" button at the end of the dates so a user doesn't need to swipe all the way back to the start to navigate to the next month. The column headers are also skipped to improve ease of navigation, since the day names are already included in the label of each cell.
+
+
+
+
+
+We went through many iterations of our calendar components to come to a solution that we think works well across a wide variety of devices and screen readers. [useCalendar](../useCalendar.html) and [useRangeCalendar](../useRangeCalendar.html) encapsulate this research and testing into reusable hooks that you can use in your own calendar components. These are also very flexible, including support for displaying multiple months at once, or other time ranges such as a week view.
+
+[useDatePicker](../useDatePicker.html) and [useDateRangePicker](../useDateRangePicker.html) combine both a date field and calendar to create a fully featured date picking experience. Check out the docs and examples to learn more.
+
+## Internationalization
+
+Dates and times are represented in many different ways by cultures around the world. This includes differences in calendar systems, time zones, daylight saving time rules, date and time formatting, weekday and weekend rules, and much more. When building applications that support users around the world, it is important to handle these aspects correctly for each locale.
+
+By default, JavaScript represents dates and times using the [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. However, `Date` has *many* problems, including a very difficult to use API, and lack of internationalization support. This has led to the development of [many](https://momentjs.com) [date](https://date-fns.org) [and](https://day.js.org) [time](https://moment.github.io/luxon/#/) manipulation libraries over the years, which offer a wrapper around `Date` with an easier to use API and useful utility functions. However, none of these existing solutions tackled the internationalization features we were looking to support, so we built our own library: [@internationalized/date](../../internationalized/date/index.html).
+
+### Introducing @internationalized/date
+
+[@internationalized/date](../../internationalized/date/index.html) takes a different approach from other JavaScript date libraries. Rather than wrapping a `Date` object and providing an API on top, it implements all date arithmetic and utilities from scratch. This allows it to have different object types for different purposes. For example, [CalendarDate](../../internationalized/date/CalendarDate.html) represents a date without a time, [Time](../../internationalized/date/Time.html) represents a time without a date, [CalendarDateTime](../../internationalized/date/CalendarDateTime.html) represents a date and time without a time zone, and [ZonedDateTime](../../internationalized/date/ZonedDateTime.html) puts them all together to represent a date and time in a particular time zone. Each of these objects have different use cases and behaviors, and representing them all using a JavaScript `Date` would have been difficult.
+
+```tsx
+import {CalendarDate} from '@internationalized/date';
+
+let date = new CalendarDate(2022, 2, 3);
+date = date.add({years: 1, months: 1, days: 1});
+date.toString(); // '2023-03-04'
+```
+
+Despite being implemented from scratch, and supporting [13 different calendar systems](../../internationalized/date/Calendar.html#implementations), and a number of locale-aware utility functions, `@internationalized/date` is only 8 kB minified with Brotli compression, and it is tree-shakeable to reduce this even further! This is significantly smaller than many other JavaScript date libraries, while offering improved internationalization support.
+
+In the future, the TC39 [Temporal](https://tc39.es/proposal-temporal/docs/index.html) proposal will be a replacement for the `Date` object in the JavaScript language. Temporal supports many of the internationalization requirements we have, and has a much nicer API as well. We were heavily inspired by its design, and hope to back the objects and functions in `@internationalized/date` with it once it is widely implemented in browsers.
+
+### Calendar systems
+
+While the Gregorian calendar is the most common, many other calendar systems are used throughout the world, for example, Buddhist, Islamic, Persian, Hebrew, Japanese, and more. Each calendar system defines how days are organized into months, years, and eras, rules for leap years, etc. Date math, such as adding or subtracting days, months, or years, is performed differently depending on the calendar system.
+
+There are three main types of calendar systems: lunar, solar, and lunisolar. [Lunar calendars](https://en.wikipedia.org/wiki/Lunar_calendar), such as the Islamic calendar systems, are based on the phases of the moon. [Solar calendars](https://en.wikipedia.org/wiki/Solar_calendar), such as the Gregorian and Persian calendar systems, follow the position of the sun relative to the stars. [Lunisolar calendars](https://en.wikipedia.org/wiki/Lunisolar_calendar), such as Hebrew and Chinese calendar systems, use a combination of the moon phase and solar year, often with leap months in some years to keep these synchronized. Since the Gregorian calendar has become so widespread around the world, many calendar systems are also derived from it, with minor differences such as a different epoch (e.g. the modern Buddhist, Indian, and Taiwanese calendar systems), or different eras (e.g. Japanese).
+
+You can see some of the differences between calendar systems in the example below.
+
+
+
+While the [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) object built into JavaScript has support for formatting dates in multiple calendar systems, the `Date` object only supports the Gregorian calendar. Because arithmetic using `Date`, such as adding or subtracting months or years, is performed using the Gregorian calendar, the resulting dates will appear incorrect to the user when displayed in a different calendar system.
+
+The [Calendar](../../internationalized/date/Calendar.html) interface in `@internationalized/date` provides an abstraction that allows date manipulation to support arbitrary calendar systems, and implementations for 13 different calendar systems are included. All date objects are constructed with a particular calendar system, and you can also convert dates from one calendar system to another. This allows our date picking components to work with dates in any calendar system without being concerned with the details of each one at the UI layer.
+
+```tsx
+import {toCalendar, HebrewCalendar, GregorianCalendar} from '@internationalized/date';
+
+let hebrewDate = new CalendarDate(new HebrewCalendar(), 5781, 1, 1);
+let gregorianDate = toCalendar(hebrewDate, new GregorianCalendar());
+gregorianDate.toString();
+// => '2020-09-19'
+```
+
+It's easy to make assumptions based on the calendar system you use every day and test with during development, which may lead to bugs when users in other parts of the world use your app. To make this easier, the APIs for all of our date and time picker components handle all of the details and calendar conversions for you. If you provide a date in the Gregorian calendar system as a value to a component, that's what you'll get back, no matter which calendar system the user is interacting with. This allows applications to deal with dates from all users consistently, even if users enter dates in a different calendar system than the app uses for storage.
+
+### Locale-specific queries
+
+Aside from the calendar system, many other aspects of date and time handling are also locale-specific. For example, the day considered the first day of the week changes depending on the country. In the United States, Sunday is considered the start of the week, but in France it is Monday. This affects the layout of dates in a calendar UI, including how many weeks are in a month in some cases.
+
+Another example of a locale-specific difference is which days of a week are considered weekends vs weekdays. In many countries, Saturday and Sunday are weekends, but in some such as Israel, the weekend is considered Friday and Saturday, and in Afghanistan it is Thursday and Friday.
+
+`@internationalized/date` provides functions that implement all of these details. Visit [the docs](../../internationalized/date/CalendarDate.html#queries) for more information.
+
+```tsx
+import {isWeekend, startOfWeek} from '@internationalized/date';
+
+// a Sunday
+let date = new CalendarDate(2022, 2, 6);
+
+isWeekend(date, 'en-US');
+// => true
+isWeekend(date, 'he-IL');
+// => false
+startOfWeek(date, 'en-US');
+// => 2022-02-06
+startOfWeek(date, 'fr-FR');
+// => 2022-01-31
+```
+
+### Time zones
+
+Time zones are another huge area of complexity for date and time manipulation. The JavaScript `Date` object only supports manipulating dates in the user's local time zone or UTC, and does not support arbitrary time zones. The time zone affects not only the UTC offset, but also daylight saving time rules. When performing date and time arithmetic with time zones, the time must be adjusted accordingly when a DST change occurs.
+
+Daylight saving time introduces ambiguity. In a "spring forward" transition, an hour is skipped, and in a "fall back" transition, an hour repeats. If a time is specified that doesn't exist, or exists twice, this ambiguity must be resolved. In `@internationalized/date`, this is done explicitly, giving you control over the behavior.
+
+```tsx
+import {parseZonedDateTime} from '@internationalized/date';
+
+// A "fall back" transition
+let date = parseZonedDateTime('2020-10-01T01:00-07:00[America/Los_Angeles]');
+
+date.set({ month: 11 }, 'earlier');
+// => 2020-11-01T01:00:00-07:00[America/Los_Angeles]
+
+date.set({ month: 11 }, 'later');
+// => 2020-11-01T01:00:00-08:00[America/Los_Angeles]
+```
+
+See [the docs](../../internationalized/date/ZonedDateTime.html#setting-fields) for more details on how this works.
+
+## Conclusion
+
+Correctly manipulating dates and times is *really hard*. Making assumptions about calendar systems, time zones, locales, date and time arithmetic, etc. is a recipe for bugs when users around the world interact with your app. [@internationalized/date](../../internationalized/date/index.html) provides a library of objects and functions that help handle these differences and allow you to manipulate dates from all users consistently. It is a completely independent library, so even if you aren't using React Aria, React Spectrum, or even React, you can still take advantage of it for all your date and time manipulation needs!
+
+In addition, React Aria hooks such as [useDatePicker](../useDatePicker.html) and [useCalendar](../useCalendar.html) can help you build international and accessible date and time picking components with completely customizable styles. We've been working on these components for a [long time](https://x.com/devongovett/status/1136402636754673664), and we really hope you like them!
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/drag-and-drop.mdx b/packages/dev/s2-docs/pages/react-aria/blog/drag-and-drop.mdx
new file mode 100644
index 00000000000..b3c68c940ce
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/drag-and-drop.mdx
@@ -0,0 +1,116 @@
+{/* Copyright 2022 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import Anatomy from '@react-aria/dnd/docs/Anatomy.svg';
+import BetweenDropPosition from '@react-aria/dnd/docs/BetweenDropPosition.svg';
+import OnDropPosition from '@react-aria/dnd/docs/OnDropPosition.svg';
+import RootDropPosition from '@react-aria/dnd/docs/RootDropPosition.svg';
+import heroVideo from 'url:../../../../docs/pages/assets/dnd.mp4';
+import dndKeyboard from 'url:../../../../docs/pages/assets/dnd-keyboard.mp4';
+import dndMobile from 'url:../../../../docs/pages/assets/dnd-mobile.mp4';
+import dndMobileVTT from 'url:../../../../docs/pages/assets/dnd-mobile.vtt';
+import DragBetweenListsExample from './DragBetweenListsExample';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['drag and drop', 'dnd', 'components', 'accessibility', 'keyboard', 'mobile', 'react spectrum', 'react', 'spectrum', 'interactions', 'touch'];
+export const description = 'We are excited to announce the release of drag and drop support in [React Aria](../dnd.html) and [React Spectrum](../../s2/dnd.html)! This includes a suite of hooks for implementing drag and drop interactions, with support for both mouse and touch, as well as full parity for keyboard and screen reader input.';
+export const date = '2022-11-16';
+export const author = 'Devon Govett';
+export const authorLink = 'https://x.com/devongovett';
+export const hideNav = true;
+
+# Taming the dragon: Accessible drag and drop
+
+We are excited to announce the release of drag and drop support in [React Aria](../dnd.html) and [React Spectrum](../../s2/dnd.html)! This includes a suite of hooks for implementing drag and drop interactions, with support for both mouse and touch, as well as full parity for keyboard and screen reader input. We've designed these hooks with the following features in mind:
+
+- **Flexibility** – Our hooks include high level APIs for building common interactions such as dragging between lists, reordering, inserting, moving, copying, and file/directory uploading, as well as lower level APIs for building custom experiences.
+- **Accessibility** – Full support for keyboard and screen reader interactions is included out of the box, ensuring that applications implementing drag and drop using React Aria and React Spectrum are accessible with no additional work.
+- **Interoperability** – Drag data can be provided in multiple data formats for compatibility with many targets and external applications via the native HTML drag and drop API. Drag and drop integrates with multiple selection to allow dragging many objects at once.
+- **Customizability** – Interactions such as hit testing, keyboard navigation, and drop operations can be customized, as well as UIs for drag previews and drop indicators.
+
+
+
+## Introduction
+
+Drag and drop is a common UI interaction that allows users to transfer data between locations by directly moving a visual representation on screen. It is a flexible, efficient, and intuitive way for users to perform a variety of tasks, and is widely supported across both desktop and mobile operating systems.
+
+While drag and drop has historically been mostly limited to mouse and touchscreen users, keyboard and screen reader friendly alternatives are important for people who are not able to use these interaction methods. For example, copy and paste can often be used as an alternative to drag and drop. However, it is much more limited, and it can be hard to discover where pasting is accepted. This leaves applications to build custom UIs to accomplish the same tasks as drag and drop in an alternative way, which isn't always easy to do. As a result, it is often omitted leading to an inaccessible experience.
+
+While alternative interactions may still be useful for discoverability, we wanted to make the drag and drop interactions provided by React Aria accessible out of the box. This way, keyboard and screen reader users have full feature parity with mouse and touchscreen users, and applications that implement drag and drop using React Aria are guaranteed to be accessible without any additional work.
+
+After years of research, development, and extensive testing across many devices and assistive technologies, the result is a unified drag and drop API in React Aria that works across mouse, touch, and keyboard interactions, and with both desktop and mobile screen readers. It can be used standalone, or integrated with existing React Aria and React Spectrum components. Check out the [documentation](../dnd.html) for more details!
+
+## Interactions
+
+Drag and drop starts with a drag source, implemented using the [useDrag](../useDrag.html) hook, which provides data to be dragged. Multiple items can be dragged at once, and each item can include several representations in different data formats so that they can be dropped in many compatible locations. The [useDrop](../useDrop.html) hook can be used to implement a drop target, which accepts dragged items containing specific data types.
+
+
+
+For mouse and touch screen users, React Aria uses the native [HTML drag and drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) under the hood. This means items can be dragged within the browser window, between browser windows, or even outside the browser into external native applications (e.g. email programs). External items such as files or directories from the user's device may also be dragged in.
+
+In addition to native drag and drop, we have also implemented keyboard and screen reader accessible interactions from scratch. Keyboard users can focus a draggable element and press the `Enter` key to start dragging it. This enters a drag and drop mode across the whole page, which allows the user to press `Tab` to navigate only between drop targets that accept the dragged data, while skipping over all other elements. This reduces the number of elements on the page that must be traversed to find a drop target, and removes the guess work often found with other alternatives such as copy and paste. Once a target is chosen, pressing the `Enter` key again performs the drop.
+
+
+
+For screen reader users, the interactions are similar. We've taken great care to include prompts and announcements to help guide the user through the process, which adapt to the device, and are localized into over 30 languages. Drag sources and drop targets include ARIA descriptions indicating that the user can press `Enter` or double tap to drag or drop, depending on their device. We also use an ARIA live region to announce when a drag starts, with instructions on how to navigate and perform a drop, and to announce when a drop is completed successfully or canceled by the user.
+
+In addition, while in drag and drop mode, all elements other than valid drop targets are hidden from screen readers. Desktop screen reader users can use the `Tab` key to navigate as described earlier, but touch screen readers navigate by swiping through elements using a virtual cursor, and double tapping to drop. Hiding all non-drop target elements makes it much easier to find valid places to drop, without needing to swipe through potentially hundreds of unrelated elements.
+
+
+
+
+
+All of these accessibility features are implemented behind the scenes using the same API as for mouse and touch interactions. There is no additional effort required by the developer to make drag and drop accessible.
+
+### Collections
+
+Collection components such as lists or tables are treated as a single drop target, so that users can easily tab past them to get to the next drop target without going through every item. Within a droppable collection, keys such as `ArrowDown` and `ArrowUp` can be used to select a drop position, such as on the collection itself, on an item, or between items. Drop indicator elements may be added between items to show the user where dropped data will be inserted, and include accessibility labels such as "Insert between item A and item B".
+
+
+
+
+
+
+
+ The "root", "on", and "between" drop positions.
+
+
+The [useDraggableCollection](../useDraggableCollection.html) and [useDroppableCollection](../useDroppableCollection.html) hooks can be used to implement drag and drop within components built with existing React Aria hooks such as [useListBox](../useListBox.html), [useTable](../useTable.html), and [useGridList](../useGridList.html). The drag and drop system integrates with our existing architecture for [collections](../collections.html) and [selection](../selection.html), which are used across all of these components. This enables multiple selected items to be dragged at once, allows keyboard navigation within a droppable collection to adapt based on the collection's layout, and facilitates automatic focus management when items are dropped.
+
+Try out an example below, which allows reordering and dragging elements between lists.
+
+
+
+## Challenges
+
+Drag and drop is a complex and flexible interaction pattern, and accessibility support is relatively uncharted territory, so building a complete drag and drop implementation was a huge challenge.
+
+The HTML drag and drop API is notoriously [quirky](https://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html), [difficult to use](https://horstmann.com/unblog/2018-12-16/index.html), and [lacking many features](http://tolmasky.com/2009/08/16/on-html-5-drag-and-drop/), and browser implementations often have differences and bugs. It was originally designed in the Internet Explorer 5 days (way back in 1999!), and after being standardized as part of HTML5 in 2009, has been relatively unchanged ever since. That said, it's the only way to support integration with the operating system, which is a requirement for features such as file uploading, inter-app drag and drop, and dragging between windows, iframes, and JS frameworks. We deal with at least 13 different [browser bugs](https://github.com/adobe/react-spectrum/wiki/Tracker-for-External-Browser-Bugs,-Library-Bugs,-and-Features) in our implementation, normalizing the behavior for applications that rely on our hooks. We also implement several features on top, such as support for multi-item drags.
+
+Designing accessible drag and drop interactions was also a huge challenge. While most of the components we have implemented so far have had patterns defined in the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) that we could adapt, nothing was available for drag and drop. Without a standard pattern we couldn't rely on familiarity, so guiding the user through the drag and drop process was crucial. We did a lot of research into approaches others had already built, and while there were many great libraries that implemented a specific pattern such as reordering a list, there were none that provided a complete accessible drag and drop system with full native parity. We drew inspiration from these implementations, and experimented with our own prototypes to find interactions that would work across a wide variety of devices and assistive technologies.
+
+We had to define how keyboard navigation should work, how focus should be managed, how drag and drop should work with touch screen readers when no keyboard is available, how to handle elements with conflicting interactions such as list selection and context menus, how to provide clear announcements to guide the user without being too verbose, and much more. And of course, we had to deal with lots of browser and screen reader bugs, limitations, and edge cases, especially since this is not a well established pattern. It took a lot of experimentation and trial and error, and sometimes felt like whenever we solved a problem on one device or screen reader, a new problem would arise somewhere else. After plenty of testing and iteration, we whittled down the bug list, and the feedback from users has been positive so far!
+
+Defining an API that is both flexible and easy to use was also difficult. We initially designed a low level API that allowed for many different use cases, but through testing and feedback we realized that it required a lot of boilerplate code to build common experiences like reordering items or moving items between lists. We went back to the drawing board, and built a higher level API on top of the low level one, with events such as `onReorder`, `onInsert`, and `onItemDrop` that would configure everything for you. You can mix and match these to create more complex behaviors, or combine them with the low level API to customize things even more. This provides the best of both worlds: it's easy to get started and build the most common UIs, but also possible to progressively dive in deeper and customize things as needed.
+
+## Try it out!
+
+We've been working on our drag and drop implementation for a while, and we're excited to share it with the community! Check out our documentation for [React Aria](../dnd.html) and [React Spectrum](../../s2/dnd.html) to get started.
+
+You can use our React Aria hooks standalone, or integrate with existing collection components for common use cases like list reordering, inserting items, or dropping into folders. You can also customize most of the behavior with lower level APIs as needed, allowing you to build more specialized experiences.
+
+For React Spectrum, the [useDragAndDrop](../../s2/dnd.html) hook can be used to enable drag and drop in the components that support it. This returns an object containing implementations of the React Aria hooks used to implement drag and drop, keeping bundle size small when drag and drop is unused. Currently support is limited to [ListView](https://react-spectrum.adobe.com/react-spectrum/ListView.html), but we will be adding drag and drop to other components soon.
+
+We hope these new hooks help make accessible drag and drop interactions easier to build, and we can't wait to see what you create!
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/how-we-internationalized-our-numberfield.mdx b/packages/dev/s2-docs/pages/react-aria/blog/how-we-internationalized-our-numberfield.mdx
new file mode 100644
index 00000000000..d5717eaed74
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/how-we-internationalized-our-numberfield.mdx
@@ -0,0 +1,158 @@
+{/* Copyright 2021 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import pinyinEntering from 'url:../../../../docs/pages/assets/NumberField-pinyin.mp4';
+import numberfieldIphoneDefault from 'url:../../../../docs/pages/assets/NumberField_Iphone_default.png';
+import numberfieldIphonePositive from 'url:../../../../docs/pages/assets/NumberField_Iphone_positive.png';
+import numberfieldIphoneInteger from 'url:../../../../docs/pages/assets/NumberField_Iphone_integer.png';
+import numberfieldAndroidPositive from 'url:../../../../docs/pages/assets/NumberField_Android_positive.jpg';
+import numberfieldAndroidInteger from 'url:../../../../docs/pages/assets/NumberField_Android_integer.jpg';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+import {NumberField} from '@react-spectrum/s2';
+
+export const tags = ['react aria', 'react spectrum', 'react', 'spectrum', 'interactions', 'numberfield', 'touch', 'spinbutton'];
+export const description = 'Number fields are commonly used form components, but are frequently not a great user experience. They often lack support for advanced formatting, such as currency and unit values, and do not provide a localized experience for users around the world. In this post, we\'ll discuss how we approached building our number field component with support for formatting and internationalization in mind.';
+export const date = '2021-05-05';
+export const author = 'Rob Snow';
+export const authorLink = 'https://x.com/snowystinger';
+export const hideNav = true;
+
+# How we internationalized our number field
+
+Number fields are commonly used form components, but are frequently not a great user experience. They often lack support for advanced formatting, such as currency and unit values, and do not provide a localized experience for users around the world. In this post, we'll discuss how we approached building our number field component with support for formatting and internationalization in mind.
+
+## What is a number field?
+
+A number field allows users to input and edit numeric values. For example, quantities, dimensions, currencies, percentages, or unit values may be edited with a number field. In addition to allowing the user to enter these values with their keyboard, number fields also often support incrementing and decrementing the value using stepper buttons, the arrow keys, or by scrolling with the mouse.
+
+Values such as credit card and phone numbers are not typically represented by a number field because while they do contain numbers, they are not incrementable, and have additional formatting and validation requirements.
+
+
+
+
+
+### Formatting
+
+Number fields can be used to represent many different types of numeric values, each of which have unique formatting requirements. For example, decimals can be rounded to a particular number of decimal places, percentages display a percent sign along with the number, currencies display a currency symbol or code, and dimension values may have a unit associated with them. In addition, formatting requirements for these values may differ between countries, languages, and numbering systems used around the world. For example, in the US, we use the "." character to represent a decimal point, while in Germany, the "," character is used.
+
+We use the [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) API built into browsers to handle all of these formatting requirements across all locales. Our number field component supports most of the formatting options that the Intl API supports, which ensures that it is international friendly and supports many common formatting options out of the box. The browser currently only provides formatting support, however, not parsing support, so we built a custom number parser using information from the formatter. We'll discuss how this works in detail later in the post.
+
+```tsx render
+import {NumberField} from '@react-spectrum/s2';
+
+
+```
+
+### Stepping
+
+Many number fields also support incrementing and decrementing their value using stepper buttons, arrow keys, mouse scroll wheel, or gestures in some screen readers. We support this via a `step` prop, which controls how much to increment or decrement by each time the buttons or arrow keys are pressed. The `step` also determines how to round the value to the nearest step on blur if the user enters a value that isn't on a step boundary.
+
+One area of complexity here is dealing with floating point rounding errors. All JavaScript numbers are double precision floating point, which means when you add or subtract two numbers, you can get rounding errors. For example, `0.1 + 0.2 === 0.30000000000000004`. This isn't really what users expect, however, so to fix this, we determine how many decimal places there are, multiply to convert the two numbers to integers, perform the addition or subtraction, and then divide again to get a decimal. A similar fix is necessary to perform step snapping on blur.
+
+```tsx render
+import {NumberField} from '@react-spectrum/s2';
+
+
+```
+
+### Allowed characters
+
+A number field should help the user enter a valid number and avoid accidental input. In order to do this, we only allow the user to type characters that meet the formatting requirements. For example, when the number is a percentage, we allow only numerals and the percent sign, and all other characters are ignored. The allowed characters are based on the formatting options as well as the current locale.
+
+In addition, we also support several different numbering systems, including the Latin, Arabic, and Han positional decimal systems. There are many different ways of entering numbers, including different hardware keyboard layouts, and various input method editors such as Pinyin, which uses combinations of Latin characters to represent Chinese logograms. These are supported via composition events, which the browser fires as the user enters each Latin character. While these characters by themselves are not valid numbers, we cannot validate them until the sequence is complete and the Latin characters are replaced by the Chinese logogram.
+
+
+
+ Entering the number 21 in the Han positional decimal system using the iOS Pinyin keyboard
+
+
+### Mobile
+
+On mobile devices, the keyboard should be as helpful as possible, providing only the characters that can be entered into the field. This can vary based on the formatting options provided to the number field. For example, if the minimum value is greater than zero, no minus key should be displayed, and if fractional values are not allowed, then no decimal point should appear.
+
+The `inputMode` DOM attribute can be used to control the on screen keyboard shown by mobile devices. However, standard numeric keyboards vary across devices and operating systems making it difficult to provide a unified experience. For example, the iOS numeric keyboard does not include a minus sign at all, which means we must use a full text keyboard instead. This is unfortunately not an ideal experience, but it is the only way to allow a user to enter all possible numbers.
+
+
+
+
+ With inputMode="numeric", iOS does not include a minus key or a decimal point.
+
+
+
+ Wth inputMode="decimal", iOS does include a decimal point, but no minus key.
+
+
+
+ inputMode="text" is the only way to get a minus key on iOS.
+
+
+
+ Android does not include a negative sign with inputMode="decimal".
+
+
+
+ Android includes both a negative sign and decimals with inputMode="numeric".
+
+
+
+In order to optimize the experience as much as possible, we detect the operating system and switch the value of the `inputMode` attribute according to the formatting options. For example, negative numbers are allowed, we use `inputMode="text"` on iOS, but `inputMode="numeric"` on Android.
+
+## Problems with native number inputs
+
+When considering how to implement a number field, the obvious solution is the built in ` ` element supported in browsers. However, we ran into several issues that led us to avoid it.
+
+1. Most browser implementations do not allow the level of formatting that we require. They typically only support decimals, and don't allow formatting options for number of decimal points, or display as a percentage, currency, or unit value.
+2. In addition, most browsers don't support numbering systems other than Latin, and may completely block input of any characters other than Latin numerals, minus and plus signs, decimal points, and the letter e for exponential notation.
+3. Browser implementations are also very inconsistent. Formatting and rounding behavior may vary, along with the UI presented to the user. Along with the keyboard differences mentioned previously, some browsers have steppers and others do not, and the mobile experience for incrementing and decrementing numbers is often lacking. In addition, these steppers often cannot be styled to match our design requirements.
+
+For these reasons, we decided to use an ` ` element along with the `inputMode` attribute to specify the mobile keyboard, and a custom ARIA role description, rather than ` `.
+
+## Internationalization
+
+Internationalization is an especially important feature for components like number field. Users around the world expect to enter numbers using their local numbering system and formats, so we needed to implement a number parser that could handle many numbering systems and locale combinations.
+
+### Locales and numbering systems
+
+A **locale** represents a set of preferences for users in a particular part of the world, such as the language, currency, and number format. For example, in the `en-US` locale, the default language is English and the decimal character is a period, but in the `de-DE` locale, the default language is German, and the decimal character is a comma.
+
+A **numbering system** is a way of representing numbers using written characters. For example, in the Latin numbering system, the number twelve is represented as "12", and in the Arabic decimal system, it is "١٢". Most commonly used numbering systems are decimal based, which means they have ten numeral symbols that are combined based on their position. An example of a non-positional numbering system is the Roman numeral system, in which digits are combined by addition or subtraction. Currently, we only support base-10 decimal systems, since these are most commonly used and the simplest to parse.
+
+While a locale may have a default numbering system associated with it, users may choose to use a different one. For example, the `ar-AE` locale defaults to Arabic numerals, but users may still wish to enter a Latin number. Our number field automatically detects the numbering system of the characters entered by the user, and parses it accordingly.
+
+### Localized number parsing
+JavaScript's `parseFloat` function only handles Latin numbers and US-style decimals, so in order to parse localized numbers we had to get creative. We use an `Intl.NumberFormat` object to format each digit in a locale/numbering system and generate a map between numeral characters and number values. We also use the number formatter to determine the allowed set of non-numeral characters such as the decimal point, group separator, and minus sign for the locale. This gives us enough information to validate and parse user input.
+
+The parsing process consists of removing all non-numeric characters, and replacing numerals, decimal points, and minus signs in the input string with their Latin equivalents. Then we can simply use the `parseFloat` function as usual. There is also some additional sanitization required in some locales where a formatted character like a minus sign may not be available on a user's keyboard. In these cases, we want to allow both the formatted character as well as the character on the keyboard to ensure numbers can both be typed manually and pasted from a pre-formatted value.
+
+This approach of using a number formatter to generate a parser avoids needing to download any heavy locale data, since it can rely on the data the browser already has. This means it works with many number formats, locales, and numbering systems out of the box and automatically supports more options as browsers add them.
+
+## Conclusion
+
+In this post, we covered how a number field can be internationalized and support advanced formatting, and how we improved the experience on mobile. If you need to implement localized number parsing in your own apps or components, check out the [@internationalized/number](http://npmjs.com/@internationalized/number) package on npm. And if you use the [useNumberField](../useNumberField.html) hook in React Aria, or the [NumberField](../../s2/NumberField.html) component in React Spectrum, all of this is built in.
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/index.mdx b/packages/dev/s2-docs/pages/react-aria/blog/index.mdx
new file mode 100644
index 00000000000..fdbe5e962fa
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/index.mdx
@@ -0,0 +1,22 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import {Layout} from '../../../src/Layout';
+import {BlogList} from '../../../src/BlogList';
+
+export default Layout;
+
+export const section = 'Blog';
+
+export const tags = ['blog', 'articles', 'posts'];
+
+# Blog
+
+ page.name.startsWith('react-aria/blog/') && !page.name.endsWith('index.html')) ?? []} />
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/introducing-react-spectrum.mdx b/packages/dev/s2-docs/pages/react-aria/blog/introducing-react-spectrum.mdx
new file mode 100644
index 00000000000..86772648eaa
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/introducing-react-spectrum.mdx
@@ -0,0 +1,169 @@
+{/* Copyright 2020 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import heroNarrow from 'url:../../../../docs/pages/assets/ReactAria_Mobile_976x1025_1x.png';
+import heroNarrow2x from 'url:../../../../docs/pages/assets/ReactAria_Mobile_976x1025_2x.png';
+import heroNarrowWebp from 'url:../../../../docs/pages/assets/ReactAria_Mobile_976x1025_1x.webp';
+import heroNarrow2xWebp from 'url:../../../../docs/pages/assets/ReactAria_Mobile_976x1025_2x.webp';
+import heroWide from 'url:../../../../docs/pages/assets/ReactAria_976x445_1x.png';
+import heroWide2x from 'url:../../../../docs/pages/assets/ReactAria_976x445_2x.png';
+import heroWideWebp from 'url:../../../../docs/pages/assets/ReactAria_976x445_1x.webp';
+import heroWide2xWebp from 'url:../../../../docs/pages/assets/ReactAria_976x445_2x.webp';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['react stately', 'react aria', 'react spectrum', 'hooks', 'react', 'spectrum'];
+export const description = 'Today, we\'re excited to announce [React Spectrum](https://react-spectrum.adobe.com/), a collection of libraries and tools that help you build adaptive, accessible, and robust user experiences. Check it out on [Github](https://github.com/adobe/react-spectrum)!';
+export const date = '2020-07-15';
+export const hideNav = true;
+
+# Introducing React Spectrum
+
+We're excited to announce [React Spectrum](https://react-spectrum.adobe.com/index.html), a collection of libraries and tools that help you build adaptive, accessible, and robust user experiences. [Check it out on Github](https://github.com/adobe/react-spectrum)! React Spectrum includes three libraries:
+
+- **React Spectrum** — A React implementation of Spectrum, Adobe's design system.
+- **React Aria** — A library of React Hooks that provides accessible UI primitives for your design system.
+- **React Stately** — A library of React Hooks that provides cross-platform state management and core logic for your design system.
+
+
+
+
+
+
+
+
+
+
+
+## Features
+
+- ♿️ [**Accessible**](../accessibility.html) – Accessibility and behavior is implemented according to [WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.2/), including full screen reader and keyboard navigation support. All components have been tested across a wide variety of screen readers and devices to ensure the best experience possible for all users.
+- 📱 [**Adaptive**](../interactions.html) – All components are designed to work with mouse, touch, and keyboard interactions. They're built with responsive design principles to deliver a great experience, no matter the device.
+- 🌍 [**International**](../internationalization.html) – Support for more than 30 languages is included out of the box, with support for right-to-left languages, date and number formatting, and more.
+- 🎨 [**Customizable**](../../s2/theming.html) – React Spectrum components support custom themes, and automatically adapt for dark mode. For even more customizability, you can build your own components with your own DOM structure and stying using the [React Aria](../index.html) and [React Stately](https://react-spectrum.adobe.com/react-stately/index.html) hooks to provide behavior, accessibility, and interactions.
+
+Adobe is a large company with thousands of engineers working on hundreds of products, which must all meet high standards for UI consistency, accessibility, internationalization, and usability. Meeting these standards at our scale has been a challenge, and we're excited to share what we've learned with the community and raise the bar for web applications together.
+
+## Motivation
+
+Design systems are now more popular than ever, and many companies both large and small are implementing their own component libraries from scratch. Modern view libraries like React allow teams to build and maintain these components more easily than ever before, but it is still **extraordinarily difficult** to do so in a fully accessible way with interactions that work across many types of devices. This represents **millions of dollars** of investment for each company as they **duplicate work** that could have been shared.
+
+While each design system is unique, there is often more in common between components than different. Most components typically found in a design system, like buttons, checkboxes, selects, and even tables, usually have very similar behavior and logic. The [WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.2/) describe how many of the most common components should behave in terms of [accessibility semantics](https://www.w3.org/TR/wai-aria-1.2/) and [keyboard interactions](https://www.w3.org/TR/wai-aria-practices-1.2/). The main difference between design systems is styling.
+
+Unfortunately, many companies and teams don't have the resources or time to prioritize features like accessibility, internationalization, full keyboard navigation, and touch interactions. This leads to many web apps having sub-par accessibility and interactions, and contributes to the perception of the web as an inferior app platform compared to native apps.
+
+We believe there is an opportunity to share much of the behavior and component logic between design systems and across platforms. For example, user interactions, accessibility, internationalization, and behavior can be reused, while allowing custom styling and rendering to live within individual design systems. This has the potential to improve the overall quality of applications, while saving companies money and time, and reducing duplicated effort across the industry.
+
+## Architecture
+
+To enable reusing component behavior between design systems, React Spectrum splits each component into three parts: state, behavior, and the rendered component. This architecture is made possible by [React Hooks](https://reactjs.org/docs/hooks-intro.html), which enable the ability to reuse behavior between multiple components.
+
+### React Stately
+
+[React Stately](https://react-spectrum.adobe.com/react-stately/index.html) is a collection of hooks that provide state management and core logic for each component. They make no assumptions about the platform they are running on, and have no theme or design system-specific logic.
+
+React Stately hooks accept common props from the component and provide state management. They implement the core logic for the component and return an interface for reading and updating the component state.
+
+React Stately can be used independently in your own components, or paired with React Aria hooks to get more of the behavior and user interactions for web applications out of the box. Learn more about React Stately, and how to [get started](https://react-spectrum.adobe.com/react-stately/getting-started.html) by reading the docs.
+
+### React Aria
+
+[React Aria](../index.html) implements behavior and [accessibility](../accessibility.html) for the web according to the [WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.2/). It includes full screen reader and keyboard navigation support, along with mouse and touch [interactions](../interactions.html) that have been tested across a wide variety of devices and browsers. It also implements [internationalization](../internationalization.html) for over 30 languages, with right-to-left specific behavior, localized date and number formatting, and more.
+
+React Aria does not contain any design system specific styling or logic. It implements event handling, accessibility, internationalization, etc. — all the parts of a component that could be shared across multiple design systems. It returns DOM props that can be spread onto the elements rendered by the component. These include semantic properties like [ARIA](https://www.w3.org/TR/wai-aria-1.2/), and event handlers. The event handlers in turn call methods on the state interface to implement the behavior for the component.
+
+React Aria gives you complete control over the rendering and styling of your components, but rather than building everything from scratch, you start with higher level primitives that have semantic meaning, behavior, and interactions built in. This allows you to **build components more quickly**, and ensures that they work well across devices and assistive technology.
+
+Building a component with React Aria and React Stately looks like this: call the hooks, and spread the resulting props onto the appropriate DOM elements.
+
+```tsx render
+'use client';
+
+import {useSwitch, useFocusRing, VisuallyHidden} from 'react-aria';
+import {useToggleState} from 'react-stately';
+
+function Switch(props) {
+ let state = useToggleState(props);
+ let {inputProps} = useSwitch(props, state);
+ let {isFocusVisible, focusProps} = useFocusRing();
+
+ return (
+
+
+
+
+
+
+
+ {isFocusVisible &&
+
+ }
+
+ {props.children}
+
+ );
+}
+
+Test
+```
+
+[Read more](../why.html) about React Aria and the problems it solves, and check out the docs to [get started](../getting-started.html) building your own design system.
+
+### React Spectrum
+
+[React Spectrum](../../s2/index.html) puts all of these pieces together and implements the Adobe-specific styling. It's designed to be adaptive, and works across mouse, touch, and keyboard interactions, on devices of any screen size. It supports [theming](../../s2/theming.html), including automatic support for dark mode, and responsive scaling for large hit targets on touch devices.
+
+If you're integrating with Adobe software or would like an end-to-end component library to use in your project, then React Spectrum is a great place to start. Save time by using [Adobe Developer App Builder](https://www.adobe.io/app-builder), our complete framework for building custom cloud-native Adobe apps. Enterprise customers and partners can extend the functionality of Adobe Experience Platform and Adobe Experience Cloud solutions in a custom app that solves specific business and workflow needs.
+
+We've designed the APIs in React Spectrum to be easy to use. The following example shows how simple it is to create a select element with support for sections and complex options.
+
+```tsx render
+'use client';
+
+import {Picker, PickerItem, PickerSection, Text} from '@react-spectrum/s2';
+import User from '@react-spectrum/s2/icons/User';
+import UserEdit from '@react-spectrum/s2/icons/UserEdit';
+import UserSettings from '@react-spectrum/s2/icons/UserSettings';
+
+
+
+
+
+ Read
+ Read Only
+
+
+
+ Write
+ Read and Write Only
+
+
+
+ Admin
+ Full access
+
+
+
+```
+
+[Get started](../../s2/getting-started.html) building an application with React Spectrum, and check out the docs for each component to learn more. In addition, if you're building your own component library, React Spectrum is a good example of how to use React Aria and React Stately to build a full design system.
+
+## Try it out!
+
+We're really excited to see how you use React Aria, React Stately, and React Spectrum in your own applications! We've started with an initial set of components, and many more will be added in the coming months.
+
+Check out [our documentation](https://react-spectrum.adobe.com/) to learn more. We've put a huge amount of effort into making it easy to get started, with API docs and examples for each component, and a clear list of all of the functionality we handle out of the box.
+
+- [Website](https://react-spectrum.adobe.com/)
+- [Github](https://github.com/adobe/react-spectrum)
+
diff --git a/packages/dev/s2-docs/pages/react-aria/blog/rtl-date-time.mdx b/packages/dev/s2-docs/pages/react-aria/blog/rtl-date-time.mdx
new file mode 100644
index 00000000000..cfe7183c200
--- /dev/null
+++ b/packages/dev/s2-docs/pages/react-aria/blog/rtl-date-time.mdx
@@ -0,0 +1,147 @@
+{/* Copyright 2025 Adobe. All rights reserved.
+This file is licensed to you under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License. You may obtain a copy
+of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under
+the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+OF ANY KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License. */}
+
+import RTLTimefield from '../../../../docs/pages/assets/rtl-timefield.svg';
+import RTLActualPlaceholder from '../../../../docs/pages/assets/rtl-actual-placeholder.svg';
+import localeVideoURL from 'url:../../../../docs/pages/assets/datefield-locales.mp4';
+import placeholderVideoURL from 'url:../../../../docs/pages/assets/datefield-placeholder.mp4';
+import keyboardVideoURL from 'url:../../../../docs/pages/assets/rtl-keyboard.mp4';
+
+import {BlogPostLayout} from '../../../src/Layout';
+export default BlogPostLayout;
+
+import docs from 'docs:@react-spectrum/s2';
+import React from 'react';
+
+export const tags = ['date picker', 'date', 'time', 'calendar', 'components', 'accessibility', 'react spectrum', 'react', 'spectrum'];
+export const description = 'Internationalization is a core feature of our Date and Time components. We support 13 different calendar systems as well as locale-specific formatting, number systems, and 12 and 24 hour time. However, we identified an issue with our right-to-left support where in some right-to-left (RTL) languages, the format of the date and time fields was incorrect. While investigating this bug, we faced several challenges in ensuring proper date and time representation in RTL languages and implemented various strategies that we\'d like to share.';
+export const date = '2025-06-06';
+export const author = 'Yihui Liao';
+export const authorLink = 'https://github.com/yihuiliao';
+export const hideNav = true;
+
+# Improving Internationalization Support in Our Date and Time Components
+
+Internationalization is a core feature of our Date and Time components. We support 13 different calendar systems as well as locale-specific formatting, number systems, and 12 and 24 hour time. However, we identified an issue in our support for several right-to-left (RTL) languages where in some, the format of the date and time fields was incorrect. While investigating this bug, we faced several challenges in ensuring accurate date and time representation in RTL languages and implemented various strategies that we'd like to share.
+
+## The Structure of Our Date and Time Components
+
+In a [previous blog post](date-and-time-pickers-for-all.html#date-fields), we discussed the reasoning behind the component structure of our date and time components. In short, we designed these components to render individually focusable segments for each date and time unit, eliminating the challenges of parsing various date formats — an issue commonly encountered with free-form text fields. Since the date and time format is automatically determined based on the user's locale, the user only needs to fill in the values without worrying about the appropriate separators or the order. This made for a smoother, more intuitive experience for the user, removing the guesswork associated with formatting and parsing dates in various locales.
+
+
+
+## Unicode Bidirectional Algorithm
+
+To format the segments according to the user locale, we relied on the [Intl.DateTimeFormat API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) to help localize our dates and flexbox to handle the layout. Flexbox conveniently handled the right-to-left layouts by mirroring the segment order provided by the API which typically works well for RTL languages. However, in some cases, it is more complex. RTL languages often have bidirectional text, meaning it contains a mix of right-to-left and left-to-right elements. For example, in Arabic and Hebrew, numbers are always written from left-to-right. In cases where numbers are combined with text, the general flow proceeds right to left, but the numbers are written from left to right.
+
+We found that dates behave bidirectionally in right-to-left languages and just mirroring the segment order was too simplistic for this complex behavior. As a result, some of our date fields were formatted incorrectly. For instance, in Hebrew (`he-IL`), the proper numeric date format should be `DD.MM.YYYY`, but our date component was displaying `YYYY.MM.DD`. This issue varied across different RTL languages for date fields, but for time fields, we observed a consistent problem across all RTL languages where time segments were flipped, rendering `MM:HH` instead of the correct `HH:MM` format.
+
+
+
+Instead of applying `display: flex` on the container wrapping the segments, we needed to use [normal CSS flow layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Flow_layout) instead and update each segment to be a `span`. This allows the browser to apply the [Unicode Bidirectional Algorithm](https://unicode.org/reports/tr9/), which reorders the characters in right-to-left text according to a set of standardized rules.
+
+While it seemed like a relatively simple fix, we later discovered through testing that this only corrected the format when segments contained numeric values. If they had placeholder values, the order was still incorrect, causing some undesirable behaviors. This was because the placeholders are typically non-numeric characters, e.g. `'شهر'` representing "month" in Arabic. As a result, when the user pressed the Backspace key to clear a segment to its placeholder, the order of the segments would change. Then, when a user entered a numeric value, the segment would shift back to its correct order. This posed an interesting challenge: how do we ensure consistent formatting regardless of whether a segment contained a placeholder or a user-entered value — all without hard coding segment order for each locale?
+
+Below is an example of how the dates were being formatted based on user-entered or placeholder values along with a video to demonstrate the shifting behavior:
+
+
+
+
+
+## TimeFields
+
+We first addressed time fields since they were easier to fix. As mentioned earlier, the segments in time fields for RTL languages were flipped. We learned that regardless of locale, all time fields should follow the `HH:MM` format. Knowing this, we could apply a direction of left-to-right (LTR) on the numeric values across all segments in a time field. We could do this by wrapping the time field in a [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/bdo) tag and setting the `dir` attribute to `ltr` which would override the current text direction from `rtl` to `ltr`.
+
+However, instead of changing the DOM structure and introducing potential side effects, we chose to use the [LRI (left-to-right isolate) Unicode character](https://www.w3.org/International/questions/qa-bidi-unicode-controls) to encapsulate the time segments and force an LTR direction. This sets the text direction to `ltr` and isolates the time field from the surrounding text. Adding this Unicode character is the equivalent of wrapping the time segments in a `` tag but offers several advantages. Since the character is invisible, there are no visual changes, and by adding it as a sibling to the segments, we avoided major structural changes to the DOM. Additionally, by enforcing a LTR direction, we no longer had to worry about whether the time field consisted of placeholders or numeric values. Lastly, it ensured that when a date field included a time, that the time field appeared in the correct order with respect to the date field (e.g. `8:45 1/31/2025` instead of `1/31/2025 8:45`).
+
+Below is a simplified code example of how we utilize Unicode characters to enforce a left-to-right direction on the segments:
+
+```tsx
+
+ {/*- begin highlight -*/}
+ {'\u2066'}
+ {/*- end highlight -*/}
+ 2
+ :
+ 45
+ {/*- begin highlight -*/}
+ {'\u2069'}
+ {/*- end highlight -*/}
+
+```
+
+## DateFields
+
+Date fields, on the other hand, were much more complicated to solve in comparsion. Since we were relying on `display: flex` to format the date segments, the resulting format appeared to mirror the order in which the segments were stored, as returned by [DateFormatter](../../internationalized/date/DateFormatter.html#dateformatter). This suggested that we could apply a similar approach to what we used for time fields — forcing a left-to-right direction on the date segments.
+
+However, this assumption proved too broad. In some locales, such as Arabic (`ar-AE`), the date segments were already correctly formatted. In particular, we found that in Arabic, the separators between date segments contained [right-to-left marks](https://en.wikipedia.org/wiki/Implicit_directional_marks) which were returned by the `Intl.DateTimeFormat` API. This causes the separators to be positioned to the left of the preceding text. When we tried enforcing a left-to-right direction like we did in time field using the same solution, the date field was formatted as `2 2022/ 12/` instead of `2022/12/2` due to the presence of the right-to-left marks interfering with the LRI Unicode. In contrast, Hebrew did not have such markers. Therefore, we had to adopt a different approach that accounted for these variations.
+
+```tsx
+// An example of a date field in ar-AE
+
+ 3
+ {/*- begin highlight -*/}
+ /
+ {/*- end highlight -*/}
+ 11
+ {/*- begin highlight -*/}
+ /
+ {/*- end highlight -*/}
+ 2020
+
+```
+
+```tsx
+// An example of a date field in he-IL
+
+ 3
+ {/*- begin highlight -*/}
+ .
+ {/*- end highlight -*/}
+ 11
+ {/*- begin highlight -*/}
+ .
+ {/*- end highlight -*/}
+ 2020
+
+```
+
+Through much trial and error, we discovered that appplying the [left-to-right embedding (LRE) Unicode](https://unicode.org/reports/tr9/#Explicit_Directional_Embeddings) on each date segment displayed date fields in Hebrew as left-to-right but also preserved the right-to-left marks on the separaters in Arabic, ensuring that dates in both languages were formatted correctly. While we could have added Unicode to the segments like we did with the time fields, we opted for the [equivalent CSS](https://unicode.org/reports/tr9/#Markup_And_Formatting) approach instead to avoid modifying the DOM. This CSS is applied on date segments with placeholder or actual values to avoid the behavior discussed earlier with shifting segments. Through additional testing, we found that we should only apply left-to-right embedding on numeric values. If the value was displayed as text (e.g. "November" instead of "11"), we did not apply this CSS.
+
+## Keyboard Navigation
+
+After fixing the formatting, we also needed to update the keyboard navigation. Previously, when pressing the left arrow key, you would go to the next node in the DOM and vice versa for the right arrow key. However, after these changes, visually adjacent elements were not necessarily adjacent in the DOM so this would not work anymore. For example, in Hebrew (`he-IL`), the day and minute segment are supposed to be visually adjacent (e.g. `HH:MM DD.MM.YYYY`), but in the DOM, there are several other segments between them.
+
+```tsx
+// An example of the DOM structure of a date field in he-IL
+
+ 3
+ .
+ 11
+ .
+ 2020
+ ,
+ 8
+ :
+ 45
+
+```
+
+Below is an example of a date field in Hebrew with the correct date format but incorrect keyboard navigation. Pressing the left arrow key should navigate you to the segment to the immediate left, while the right arrow key should navigate you to the segment to the immediate right.
+
+
+
+As a result, we updated the keyboard navigation in right-to-left langauges to calculate the distance between the currently focused segment and other segments to identify the closest node based on whether the left or right arrow key was pressed, rather than reying on the DOM order.
+
+## Conclusion
+
+As you can see, formatting dates correctly is quite challenging, especially in right-to-left languages. Fortunately, tools like the Unicode Bidirectional Algorithm help with formatting so we don't have to handle everything manually. However, as we discovered with this bug, it doesn't always work as expected and unexpected factors can interfere with it.
+
+After extensive testing, we developed a solution that ensures proper formatting for users of [React Spectrum](../../s2/DateField.html), [React Aria Components](../DateField.html), and our [hooks](../useDateField.html#usedatefield). If you haven't tried our date and time components yet, we encourage you to check them out! And if you're already using them, be sure to update to at least the March 2025 release to ensure correct formatting in right-to-left languages and to make the appropriate changes noted in the [release notes](https://react-spectrum.adobe.com/releases/2025-03-05.html#date-and-time-formatting-in-rtl-languages)!
+
diff --git a/packages/dev/s2-docs/pages/react-aria/styling.mdx b/packages/dev/s2-docs/pages/react-aria/styling.mdx
index 1c11d96bf68..6b679c4a38e 100644
--- a/packages/dev/s2-docs/pages/react-aria/styling.mdx
+++ b/packages/dev/s2-docs/pages/react-aria/styling.mdx
@@ -47,7 +47,7 @@ Components often support multiple UI states (e.g. pressed, hovered, selected, et
}
```
-In order to ensure high quality interactions across browsers and devices, React Aria Components includes states such as `data-hovered` and `data-pressed` which are similar to CSS pseudo classes such as `:hover` and `:active`, but work consistently between mouse, touch, and keyboard modalities. You can read more about this in our [blog post series](https://react-spectrum.adobe.com/blog/building-a-button-part-1.html) and our [Interactions](https://react-spectrum.adobe.com/react-aria/interactions.html) overview.
+In order to ensure high quality interactions across browsers and devices, React Aria Components includes states such as `data-hovered` and `data-pressed` which are similar to CSS pseudo classes such as `:hover` and `:active`, but work consistently between mouse, touch, and keyboard modalities. You can read more about this in our [blog post series](building-a-button-part-1.html) and our [Interactions](https://react-spectrum.adobe.com/react-aria/interactions.html) overview.
All states supported by each component are listed in the Styling section of their documentation.
diff --git a/packages/dev/s2-docs/src/BlogList.tsx b/packages/dev/s2-docs/src/BlogList.tsx
new file mode 100644
index 00000000000..bfe943671c6
--- /dev/null
+++ b/packages/dev/s2-docs/src/BlogList.tsx
@@ -0,0 +1,42 @@
+import {Link} from './Link';
+import type {Page} from '@parcel/rsc';
+import React from 'react';
+import {renderHTMLfromMarkdown} from './types';
+import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
+
+export function BlogList({pages}: {pages: Page[]}) {
+ let blogPosts = pages.sort((a, b) => {
+ return new Date(b.exports?.date).getTime() - new Date(a.exports?.date).getTime();
+ });
+
+ return (
+
+ {blogPosts.map(post => (
+
+
+ {post.tableOfContents?.[0]?.title || post.exports?.title}
+
+
+
{renderHTMLfromMarkdown(post.exports?.description, {forceInline: false, forceBlock: true})}
+
+ ))}
+
+ );
+}
+
+export function Byline({author, authorLink, date}: {author?: string, authorLink?: string, date: string}) {
+ let formattedDate = new Date(date).toLocaleDateString('en-US', {year: 'numeric', month: 'long', day: 'numeric'});
+
+ return (
+
+ {author && (
+ <>
+ By {authorLink ? {author} : author}
+ {' · '}
+ >
+ )}
+ {formattedDate}
+
+ );
+}
+
diff --git a/packages/dev/s2-docs/src/Layout.tsx b/packages/dev/s2-docs/src/Layout.tsx
index 5deae053fe0..a24a5fc66ea 100644
--- a/packages/dev/s2-docs/src/Layout.tsx
+++ b/packages/dev/s2-docs/src/Layout.tsx
@@ -309,3 +309,43 @@ function renderMobileToc(toc: TocNode[], seen = new Map()) {
);
});
}
+
+export function Time({date}: {date: string}) {
+ let dateObj = new Date(date);
+ return (
+
+ {dateObj.toLocaleDateString('en-US', {year: 'numeric', month: 'long', day: 'numeric'})}
+
+ );
+}
+
+export function BlogPostLayout(props: PageProps & {children: ReactElement}) {
+ let {currentPage, children} = props;
+ let date = currentPage.exports?.date as string | undefined;
+ let author = currentPage.exports?.author as string | undefined;
+ let authorLink = currentPage.exports?.authorLink as string | undefined;
+
+ // TODO: author/time not rendering
+ const blogComponents = {
+ ...components,
+ h1: ({children: h1Children, ...h1Props}) => (
+
+ {h1Children}
+ {author && (
+
+ By {authorLink ? {author} : author}
+
+ )}
+ {date && }
+
+ )
+ };
+
+ return (
+
+ {React.cloneElement(children, {components: blogComponents})}
+
+ );
+}