Skip to content

feat: S2 SideNav#10306

Open
snowystinger wants to merge 30 commits into
mainfrom
s2-sidenav-rob
Open

feat: S2 SideNav#10306
snowystinger wants to merge 30 commits into
mainfrom
s2-sidenav-rob

Conversation

@snowystinger

@snowystinger snowystinger commented Jul 7, 2026

Copy link
Copy Markdown
Member

Closes

Early draft of SideNav for S2.

api is this right now:

<SideNav selectedRoute="/home"> // no onSelectionChange, control through router
  <SideNavItem href="/home">
    <SideNavContent>
      <SideNavLink>

decisions to make still

  • what are "categories"?
    • They are just items that don't have a link in them, don't participate in selection and don't have the selection hover state. This will be problematic with our API as we don't know ahead of time if there will be a link or not.
    • They can be mixed in the same SideNav, if it has children, it can either be a category or an item with a link.
  • how to stop focus from ever going to the "row/cell"
    • Solved by forwarding events and re-firing them on the row from the link. This way you don't need to navigate to the row.
  • what happens to selection indicator if it's on an item inside a collapsed item/category?
    • Figma shows that the item gets bolded and a different text color
  • does the selection indicator animate like Tabs do? what happens if it needs to move between levels?
    • Design says no due to how far the indicator may need to move and how it indents

✅ 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:

@snowystinger
snowystinger marked this pull request as draft July 7, 2026 05:14
@rspbot

rspbot commented Jul 7, 2026

Copy link
Copy Markdown

@snowystinger snowystinger changed the title S2 sidenav rob feat: S2 SideNav Jul 7, 2026
@rspbot

rspbot commented Jul 9, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 10, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 10, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 13, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 13, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 13, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 13, 2026

Copy link
Copy Markdown

Comment on lines +203 to +206
// RAC swallows arrow keys at the collection level (stopPropagation during capture), so a handler
// on the link never sees them. Intercept here on an ancestor, before RAC's row handler runs, and
// expand a collapsed row when the expand arrow is pressed while focus is on its link.
let onKeyDownCapture = (e: ReactKeyboardEvent) => {

@LFDanLu LFDanLu Jul 13, 2026

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.

I haven't pulled this down to try it myself, but perhaps you could make SideNav a keyboardNavigationBehavior = tab tree and have focusMode="child" and allowsArrowNavigation=true so that arrow up/down navigation still work but focus lands on the link by default. useGridList's handleTreeExpansionKeys will have to be updated to also check allowsArrowNavigation so that it can skip the
activeElement check below, but that might allow you to get rid of all this extra handling at this level

  if (!('expandedKeys' in state) || activeElement !== rowRef) {
    return false;
  }

note that the extra props mentioned above are in #10159, so will require you to rebase/pull in that PR

@snowystinger snowystinger Jul 14, 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.

Good news, textfield_gridlists is already my base :)
I attempted an implementation of the above and it appears to mostly work, so that's pretty awesome. I was missing that allowsArrowNavigation update when I tried those all together previously. Good thinking.

Just for posterity, there's a couple cases here:

Left/Right arrow to expand/collapse/traverse up that should still be accessible from the link.

In addition, focusMode="child" should only be the case for items with links (not categories) because then it might accidentally focus something like an ActionMenu that may be in the row regardless of if there is a Link.

Shift+Tab from a link should leave the SideNav.

Comment thread packages/@react-spectrum/s2/src/SideNav.tsx
@snowystinger
snowystinger marked this pull request as ready for review July 14, 2026 07:26
@rspbot

rspbot commented Jul 14, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

Comment thread packages/@react-spectrum/s2/src/SideNav.tsx Outdated
Comment thread packages/@react-spectrum/s2/src/SideNav.tsx Outdated
Comment thread packages/@react-spectrum/s2/src/SideNav.tsx Outdated
@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 15, 2026

Copy link
Copy Markdown

Base automatically changed from textfield_gridlists to main July 17, 2026 07:30
@rspbot

rspbot commented Jul 20, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 20, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown
## API Changes

react-aria-components

/react-aria-components:GridListHeaderProps

+GridListHeaderProps {
+  children?: ReactNode
+  className?: string
+  id?: string
+  render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
+  style?: CSSProperties
+}

@react-spectrum/s2

/@react-spectrum/s2:SideNav

+SideNav <T> {
+  UNSAFE_className?: UnsafeClassName
+  UNSAFE_style?: CSSProperties
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  autoFocus?: boolean | FocusStrategy
+  children?: ReactNode | (T) => ReactNode
+  defaultExpandedKeys?: Iterable<Key>
+  dependencies?: ReadonlyArray<any>
+  disabledKeys?: Iterable<Key>
+  expandedKeys?: Iterable<Key>
+  id?: string
+  items?: Iterable<T>
+  onAction?: (Key) => void
+  onExpandedChange?: (Set<Key>) => any
+  selectedRoute: string
+  slot?: string | null
+  styles?: StylesPropWithHeight
+}

/@react-spectrum/s2:SideNavItem

+SideNavItem {
+  aria-label?: string
+  children: ReactNode
+  download?: boolean | string
+  hasChildItems?: boolean
+  href?: Href
+  hrefLang?: string
+  id?: Key
+  isDisabled?: boolean
+  onHoverChange?: (boolean) => void
+  onHoverEnd?: (HoverEvent) => void
+  onHoverStart?: (HoverEvent) => void
+  onPress?: (PressEvent) => void
+  onPressChange?: (boolean) => void
+  onPressEnd?: (PressEvent) => void
+  onPressStart?: (PressEvent) => void
+  onPressUp?: (PressEvent) => void
+  ping?: string
+  referrerPolicy?: HTMLAttributeReferrerPolicy
+  rel?: string
+  routerOptions?: RouterOptions
+  target?: HTMLAttributeAnchorTarget
+  textValue: string
+}

/@react-spectrum/s2:SideNavItemContent

+SideNavItemContent {
+  children: ReactNode
+}

/@react-spectrum/s2:SideNavItemLink

+SideNavItemLink {
+  children?: ReactNode
+}

/@react-spectrum/s2:SideNavSection

+SideNavSection <T extends {}> {
+  aria-label?: string
+  children?: ReactNode | (T) => ReactElement
+  dependencies?: ReadonlyArray<any>
+  id?: Key
+  items?: Iterable<T>
+}

/@react-spectrum/s2:SideNavHeader

+SideNavHeader {
+  children?: ReactNode
+  id?: string
+}

/@react-spectrum/s2:SideNavProps

+SideNavProps <T> {
+  UNSAFE_className?: UnsafeClassName
+  UNSAFE_style?: CSSProperties
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  autoFocus?: boolean | FocusStrategy
+  children?: ReactNode | (T) => ReactNode
+  defaultExpandedKeys?: Iterable<Key>
+  dependencies?: ReadonlyArray<any>
+  disabledKeys?: Iterable<Key>
+  expandedKeys?: Iterable<Key>
+  id?: string
+  items?: Iterable<T>
+  onAction?: (Key) => void
+  onExpandedChange?: (Set<Key>) => any
+  selectedRoute: string
+  slot?: string | null
+  styles?: StylesPropWithHeight
+}

/@react-spectrum/s2:SideNavItemProps

+SideNavItemProps {
+  aria-label?: string
+  children: ReactNode
+  download?: boolean | string
+  hasChildItems?: boolean
+  href?: Href
+  hrefLang?: string
+  id?: Key
+  isDisabled?: boolean
+  onHoverChange?: (boolean) => void
+  onHoverEnd?: (HoverEvent) => void
+  onHoverStart?: (HoverEvent) => void
+  onPress?: (PressEvent) => void
+  onPressChange?: (boolean) => void
+  onPressEnd?: (PressEvent) => void
+  onPressStart?: (PressEvent) => void
+  onPressUp?: (PressEvent) => void
+  ping?: string
+  referrerPolicy?: HTMLAttributeReferrerPolicy
+  rel?: string
+  routerOptions?: RouterOptions
+  target?: HTMLAttributeAnchorTarget
+  textValue: string
+}

/@react-spectrum/s2:SideNavItemContentProps

+SideNavItemContentProps {
+  children: ReactNode
+}

/@react-spectrum/s2:SideNavItemLinkProps

+SideNavItemLinkProps {
+  children?: ReactNode
+}

/@react-spectrum/s2:SideNavSectionProps

+SideNavSectionProps <T> {
+  aria-label?: string
+  children?: ReactNode | (T) => ReactElement
+  dependencies?: ReadonlyArray<any>
+  id?: Key
+  items?: Iterable<T>
+}

/@react-spectrum/s2:SideNavHeaderProps

+SideNavHeaderProps {
+  children?: ReactNode
+  id?: string
+}

@rspbot

rspbot commented Jul 21, 2026

Copy link
Copy Markdown

Agent Skills Changes

Added (1)
Modified (7)
Install

React Spectrum S2:

npx skills add https://d1pzu54gtk2aed.cloudfront.net/pr/86ccd7457f8b07f9c878fe3c8bd8f1838a3e9740/

React Aria:

npx skills add https://d5iwopk28bdhl.cloudfront.net/pr/86ccd7457f8b07f9c878fe3c8bd8f1838a3e9740/

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