Skip to content

feat: add escape key behavior to tags #8399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

snowystinger
Copy link
Member

Closes

Found in testing, go to RAC landing page and try to use the filters in the plant table with only the keyboard. Previously Esc would clear the tags AND close the filter popover. Now it should keep the selection so that the filters still apply after closing the popover.

✅ 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

📝 Test Instructions:

🧢 Your Project:

@rspbot
Copy link

rspbot commented Jun 17, 2025

Build successful! 🎉

@rspbot
Copy link

rspbot commented Jun 17, 2025

## API Changes

react-aria-components

/react-aria-components:TagGroup

 TagGroup {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode
   className?: string
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   onRemove?: (Set<Key>) => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldSelectOnPressUp?: boolean
   slot?: string | null
   style?: CSSProperties
 }

/react-aria-components:TagGroupProps

 TagGroupProps {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode
   className?: string
   defaultSelectedKeys?: 'all' | Iterable<Key>
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   onRemove?: (Set<Key>) => void
   onSelectionChange?: (Selection) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionMode?: SelectionMode
   shouldSelectOnPressUp?: boolean
   slot?: string | null
   style?: CSSProperties
 }

@react-aria/tag

/@react-aria/tag:AriaTagGroupProps

 AriaTagGroupProps <T> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: CollectionChildren<T>
   defaultSelectedKeys?: 'all' | Iterable<Key>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   errorMessage?: ReactNode
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   id?: string
   items?: Iterable<T>
   label?: ReactNode
   onRemove?: (Set<Key>) => void
   selectedKeys?: 'all' | Iterable<Key>
   selectionBehavior?: SelectionBehavior
   selectionMode?: SelectionMode
   shouldSelectOnPressUp?: boolean
 }

@react-spectrum/s2

/@react-spectrum/s2:TagGroup

 TagGroup <T extends {}> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (T) => ReactNode
   contextualHelp?: ReactNode
   defaultSelectedKeys?: 'all' | Iterable<Key>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   errorMessage?: ReactNode
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   groupActionLabel?: string
   id?: string
   isEmphasized?: boolean
   isInvalid?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxRows?: number
   onGroupAction?: () => void
   onRemove?: (Set<Key>) => void
   onSelectionChange?: (Selection) => void
   renderEmptyState?: () => ReactNode
   selectedKeys?: 'all' | Iterable<Key>
   selectionBehavior?: SelectionBehavior
   selectionMode?: SelectionMode
   shouldSelectOnPressUp?: boolean
   size?: 'S' | 'M' | 'L' = 'M'
   slot?: string | null
   styles?: StylesProp
 }

/@react-spectrum/s2:TagGroupProps

 TagGroupProps <T> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children?: ReactNode | (T) => ReactNode
   contextualHelp?: ReactNode
   defaultSelectedKeys?: 'all' | Iterable<Key>
   description?: ReactNode
   disabledKeys?: Iterable<Key>
   disallowEmptySelection?: boolean
   errorMessage?: ReactNode
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   groupActionLabel?: string
   id?: string
   isEmphasized?: boolean
   isInvalid?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxRows?: number
   onGroupAction?: () => void
   onRemove?: (Set<Key>) => void
   onSelectionChange?: (Selection) => void
   renderEmptyState?: () => ReactNode
   selectedKeys?: 'all' | Iterable<Key>
   selectionBehavior?: SelectionBehavior
   selectionMode?: SelectionMode
   shouldSelectOnPressUp?: boolean
   size?: 'S' | 'M' | 'L' = 'M'
   slot?: string | null
   styles?: StylesProp
 }

@react-spectrum/tag

/@react-spectrum/tag:TagGroup

 TagGroup <T extends {}> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   actionLabel?: string
   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
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   description?: ReactNode
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   id?: string
   isHidden?: Responsive<boolean>
   isInvalid?: 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>
   maxRows?: number
   maxWidth?: Responsive<DimensionValue>
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   onAction?: () => void
   onRemove?: (Set<Key>) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   renderEmptyState?: () => JSX.Element
   right?: Responsive<DimensionValue>
   shouldSelectOnPressUp?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

/@react-spectrum/tag:SpectrumTagGroupProps

 SpectrumTagGroupProps <T> {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   actionLabel?: string
   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
   bottom?: Responsive<DimensionValue>
   children: CollectionChildren<T>
   contextualHelp?: ReactNode
   description?: ReactNode
   end?: Responsive<DimensionValue>
   errorMessage?: ReactNode
+  escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
   flex?: Responsive<string | number | boolean>
   flexBasis?: Responsive<number | string>
   flexGrow?: Responsive<number>
   flexShrink?: Responsive<number>
   gridColumn?: Responsive<string>
   gridColumnEnd?: Responsive<string>
   gridColumnStart?: Responsive<string>
   gridRow?: Responsive<string>
   gridRowEnd?: Responsive<string>
   gridRowStart?: Responsive<string>
   height?: Responsive<DimensionValue>
   id?: string
   isHidden?: Responsive<boolean>
   isInvalid?: 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>
   maxRows?: number
   maxWidth?: Responsive<DimensionValue>
   minHeight?: Responsive<DimensionValue>
   minWidth?: Responsive<DimensionValue>
   onAction?: () => void
   onRemove?: (Set<Key>) => void
   order?: Responsive<number>
   position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
   renderEmptyState?: () => JSX.Element
   right?: Responsive<DimensionValue>
   shouldSelectOnPressUp?: boolean
   start?: Responsive<DimensionValue>
   top?: Responsive<DimensionValue>
   width?: Responsive<DimensionValue>
   zIndex?: Responsive<number>
 }

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.

2 participants