Skip to content

feat: StepList round two - #10794

Draft
snowystinger wants to merge 4 commits into
mainfrom
wizard-and-stepper
Draft

snowystinger wants to merge 4 commits into
mainfrom
wizard-and-stepper

Conversation

@snowystinger

Copy link
Copy Markdown
Member

Closes

From discussion, split into two components. A "readonly" StepList based on html ol and an interactive Wizard based on tabs.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices
  • I understand every change in this PR and can explain why it's there.
  • If AI-assisted, I followed our AI contribution guidance and pointed my assistant at AGENTS.md.

📝 Test Instructions:

🧢 Your Project:

@rspbot

rspbot commented Oct 16, 2026

Copy link
Copy Markdown

<InternalStepListContext.Provider value={{selectedId, groupId}}>
<dom.ol
{...mergeProps(DOMProps, renderProps)}
role="list"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the roles needed here, aren't they implicit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, TIL Safari removes semantics when you add list-style: none https://www.havardbrynjulfsen.design/writing/a-new-way-to-remove-list-styles-in-safari

@snowystinger snowystinger Oct 17, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah... that was a really annoying one to track down and fix, I think it's easier than the strange display empty string in that blog, though another interesting approach. Can't ensure it for RAC though since we are generally style-less


// Need this so I can get an id on the contents so that the announcement makes sense. It lands on this element, not the `li` itself.
export const StepListItemContent = /*#__PURE__*/ (forwardRef as forwardRefType)(
function StepListItemContent(props: StepListItemContentProps, ref: ForwardedRef<HTMLDivElement>) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have render props?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I'm still trying to get rid of this component entirely, open to ideas. One idea I had was using Text and its context.

Comment on lines +146 to +147
// A step is completed if it is rendered before the current step. This can only be determined once
// all of the steps in the group have been rendered, so it is resolved against the DOM.

@nwidynski nwidynski Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for not using a collection instead? This means SSR is not supported, no? That would probably also help with textValue to skip StepListItemContent?

Also just for my planning, is the plan to ship the "readonly" version first, then the wizard later? I'm waiting on #10630 and #10556 before I was going to open a draft for the state, because I don't want to spam you guys. Those are technically not required for async nav, but I don't like to have this many PRs open at once.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, still more to sort out, but I plan to add the "wizard" version in this PR as well. Hopefully will find some time to work on that today.

Reason for not using the collection was the hopes that this readonly version that must be sequential and is literally just a simple html list (it's silly it even needs a component) wouldn't need everything that goes along with Collections. The point about SSR is a good thing to consider however. Will need to rethink if there's some other way I can predict the items before vs after the current one.

Right now, the perceived benefits of using the readonly component over just making your own list are:

  • we handle adding the completed/current/not completed labels
  • group all the text within an item so it announces cohesively and in Safari

But that's really about it.

textValue isn't really for display (or announcements), it's more like a value and is typically used for typeahead/type select.

For the "wizard" btw, we're finding that the use case that will cover is more akin to tabs, random access is permitted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

textValue isn't really for display (or announcements), it's more like a value and is typically used for typeahead/type select.

Oh, good to know. I knew it wasn't meant for display, but there are a couple precedents for announcement (e.g. useComboBox, useDropIndicator, etc.), so that was new to me.

For the "wizard" btw, we're finding that the use case that will cover is more akin to tabs, random access is permitted.

Hm, that's interesting. So even forwards? What sets it apart from Tabs then? I was imagining something along the lines of a "guided tour", where each step is tied to remote state (e.g. URL), thereby also implying async actions/navigation to progress. Conceptually, what I have in mind for state thinks of Tab as the marker to a TabPanel. This maps well to other uses of async navigation, e.g. ToC, Carousel or Pagination, etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a couple precedents for announcement (e.g. useComboBox, useDropIndicator, etc.), so that was new to me.

Hmm may want to revisit those at some date then. In general I'd try not to add more of them though until I sort that out. That's been my own mental model for them.

So even forwards?

Yes, though we'll probably give a prop or make that fall under "controlled" behaviour. The name "wizard" is still pending, we're still hashing it out.
The main use case for this seems to be more about authoring steps for a user to take.

What sets it apart from Tabs then

A great question. We're still on the fence about it. The main difference would be again handling completed/current/uncompleted and making the announcements nice.

And if you wanted a the scenario where you must go in order and the current state is tracked outside, then you'd use the (currently called) readonly steplist. Hopefully with links if I can figure that out.

This whole pattern comes from probably the 90s and the aria example is out of date, so we're still trying to figure out where it fits in the aria world.

Some prior art:
https://reui.io/docs/components/base/stepper
https://reui.io/blocks/application/wizard
https://stylexblocks.com/components/base-ui/steps/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatted about it today, moving readonly to use collections so APIs match more easily and we can handle the SSR case.

{...DOMProps}
ref={objRef}
id={id}
role="link" // No role === default group which means three arrow keys to get through each item

@nwidynski nwidynski Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

img, perhaps with localized aria-roledescription, to avoid VO+Space announcement?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, both of them feel like a lie :-/ but i like the idea of img, less implied interaction. I'd still like for people to be able to pass a link in here (Step by step example) and neither link nor img would allow for that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested some more with img and actual links, I think it's a better experience with img, lets users know it's not interactive and it's just representing a future step I think. I'll get our a11y team to review it as well, but tentatively moving to it now.

@rspbot

rspbot commented Oct 19, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Oct 23, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Oct 23, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Oct 23, 2026

Copy link
Copy Markdown
## API Changes

react-aria-components

/react-aria-components:GridListItem

 GridListItem <T> {
   allowsArrowNavigation?: boolean
   children?: ChildrenOrFunction<GridListItemRenderProps>
   className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
   onClick?: (MouseEvent<FocusableElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   onPress?: (PressEvent) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   onPressUp?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, GridListItemRenderProps>
   routerOptions?: RouterOptions
   style?: StyleOrFunction<GridListItemRenderProps>
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
 }

/react-aria-components:Select

 Select <M extends SelectionMode = 'single', T> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<SelectRenderProps>
   className?: ClassNameOrFunction<SelectRenderProps> = 'react-aria-Select'
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isRequired?: boolean
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectRenderProps>
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   slot?: string | null
   style?: StyleOrFunction<SelectRenderProps>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

/react-aria-components:GridListItemProps

 GridListItemProps <T = {}> {
   allowsArrowNavigation?: boolean
   children?: ChildrenOrFunction<GridListItemRenderProps>
   className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
   onClick?: (MouseEvent<FocusableElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   onPress?: (PressEvent) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   onPressUp?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, GridListItemRenderProps>
   routerOptions?: RouterOptions
   style?: StyleOrFunction<GridListItemRenderProps>
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
 }

/react-aria-components:SelectProps

 SelectProps <M extends SelectionMode = 'single', T = {
   
 }> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<SelectRenderProps>
   className?: ClassNameOrFunction<SelectRenderProps> = 'react-aria-Select'
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isRequired?: boolean
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectRenderProps>
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   slot?: string | null
   style?: StyleOrFunction<SelectRenderProps>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

@react-aria/select

/@react-aria/select:AriaSelectProps

 AriaSelectProps <M extends SelectionMode = 'single', T> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children: CollectionChildren<T>
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   value?: ValueType<SelectionMode>
 }

/@react-aria/select:AriaSelectOptions

 AriaSelectOptions <M extends SelectionMode = 'single', T> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   keyboardDelegate?: KeyboardDelegate
   label?: ReactNode
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   value?: ValueType<SelectionMode>
 }

@react-spectrum/ai

/@react-spectrum/ai:Attachment

 Attachment {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: ReactNode
   density?: 'compact' | 'regular' | 'spacious' = 'regular'
   download?: boolean | string
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   isInvalid?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TagRenderProps>
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StyleString
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   uploadProgress?: number
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

/@react-spectrum/ai:AttachmentProps

 AttachmentProps {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: ReactNode
   density?: 'compact' | 'regular' | 'spacious' = 'regular'
   download?: boolean | string
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   isInvalid?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TagRenderProps>
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StyleString
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   uploadProgress?: number
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

@react-spectrum/picker

/@react-spectrum/picker:Picker

 Picker <T extends {}> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: Alignment = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   defaultOpen?: boolean
   defaultSelectedKey?: Key | null
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   gridArea?: Responsive<string>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   id?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isInvalid?: boolean
   isLoading?: boolean
   isOpen?: boolean
   isQuiet?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxWidth?: Responsive<DimensionValue>
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean) => void
   onSelectionChange?: (Key | null) => void
   order?: Responsive<number>
   placeholder?: string
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   selectedKey?: Key | null
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/picker:SpectrumPickerProps

 SpectrumPickerProps <T> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   align?: Alignment = 'start'
   alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   defaultOpen?: boolean
   defaultSelectedKey?: Key | null
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   gridArea?: Responsive<string>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   id?: string
   isDisabled?: boolean
   isHidden?: Responsive<boolean>
   isInvalid?: boolean
   isLoading?: boolean
   isOpen?: boolean
   isQuiet?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   left?: Responsive<DimensionValue>
   margin?: Responsive<DimensionValue>
   marginBottom?: Responsive<DimensionValue>
   marginEnd?: Responsive<DimensionValue>
   marginStart?: Responsive<DimensionValue>
   marginTop?: Responsive<DimensionValue>
   marginX?: Responsive<DimensionValue>
   marginY?: Responsive<DimensionValue>
   maxHeight?: Responsive<DimensionValue>
   maxWidth?: Responsive<DimensionValue>
   menuWidth?: DimensionValue
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean) => void
   onSelectionChange?: (Key | null) => void
   order?: Responsive<number>
   placeholder?: string
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   right?: Responsive<DimensionValue>
   selectedKey?: Key | null
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'aria' | 'native' = 'aria'
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

@react-spectrum/s2

/@react-spectrum/s2:Card

 Card {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   density?: 'compact' | 'regular' | 'spacious' = 'regular'
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

/@react-spectrum/s2:AssetCard

 AssetCard {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

/@react-spectrum/s2:UserCard

 UserCard {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary'
 }

/@react-spectrum/s2:ProductCard

 ProductCard {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary'
 }

/@react-spectrum/s2:ListViewItem

 ListViewItem {
   allowsArrowNavigation?: boolean
   children: ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   hasChildItems?: boolean
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   onPress?: (PressEvent) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   onPressUp?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
 }

/@react-spectrum/s2:Picker

 Picker <M extends SelectionMode = 'single', T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children: ReactNode | (T) => ReactNode
   contextualHelp?: ReactNode
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isQuiet?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   renderValue?: (Array<T>) => ReactNode
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

/@react-spectrum/s2:CardProps

 CardProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   density?: 'compact' | 'regular' | 'spacious' = 'regular'
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

/@react-spectrum/s2:AssetCardProps

 AssetCardProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
 }

/@react-spectrum/s2:ProductCardProps

 ProductCardProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary'
 }

/@react-spectrum/s2:UserCardProps

 UserCardProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsArrowNavigation?: boolean
   children: ReactNode | (CardRenderProps) => ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onPress?: (PressEvent) => void
   onPressChange?: (boolean) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
   variant?: 'primary' | 'secondary' | 'tertiary'
 }

/@react-spectrum/s2:ListViewItemProps

 ListViewItemProps {
   allowsArrowNavigation?: boolean
   children: ReactNode
   download?: boolean | string
   focusMode?: 'child' | 'row'
   hasChildItems?: boolean
   href?: Href
   hrefLang?: string
   id?: Key
   isDisabled?: boolean
   onAction?: () => void
-  onBlur?: (FocusEvent<HTMLDivElement>) => void
-  onFocus?: (FocusEvent<HTMLDivElement>) => void
-  onFocusChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   onPress?: (PressEvent) => void
   onPressEnd?: (PressEvent) => void
   onPressStart?: (PressEvent) => void
   onPressUp?: (PressEvent) => void
   ping?: string
   referrerPolicy?: HTMLAttributeReferrerPolicy
   rel?: string
   routerOptions?: RouterOptions
   target?: HTMLAttributeAnchorTarget
   textValue?: string
   value?: T
 }

/@react-spectrum/s2:PickerProps

 PickerProps <M extends SelectionMode = 'single', T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children: ReactNode | (T) => ReactNode
   contextualHelp?: ReactNode
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
+  excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isQuiet?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   renderValue?: (Array<T>) => ReactNode
   selectionMode?: SelectionMode = 'single'
   shouldCloseOnSelect?: boolean
   shouldFlip?: boolean = true
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

@rspbot

rspbot commented Oct 23, 2026

Copy link
Copy Markdown

Agent Skills Changes

Modified (17)
Install

React Spectrum S2:

npx skills add https://d1pzu54gtk2aed.cloudfront.net/pr/83487e79d30080aa806e3a1c99da78c6b7daa7d6/

React Aria:

npx skills add https://d5iwopk28bdhl.cloudfront.net/pr/83487e79d30080aa806e3a1c99da78c6b7daa7d6/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants