useRovingFocus

컨트롤 묶음에 탭 정지를 하나만 둡니다. Tab을 누르면 항목 하나로 묶음에 들어가고, 다음 Tab에서 묶음을 떠납니다. 방향 키, Home, End는 항목 사이를 이동합니다. Tabs와 Menu가 이 훅 위에 만들어졌고, 회사는 자체 툴바, 세그먼트 컨트롤, 카드 그리드에 이 훅을 씁니다.

묶음에는 getContainerProps()를, 각 항목에는 getItemProps(value, { disabled })를 펼치세요. 항목은 훅이 소유한 속성으로 DOM에서 찾으므로, 직접 만든 래퍼 안의 항목도 포함되며 순서는 문서 순서를 따릅니다. 비활성화된 항목은 건너뜁니다. 브라우저에는 아직 내장 포커스 그룹이 없으므로, 이 기능은 JavaScript에 남아 있습니다.

const roving = useRovingFocus({ orientation: 'horizontal', defaultActiveValue: 'bold' })

<div role="toolbar" aria-label="Text style" {...roving.getContainerProps()}>
	<Button.Root {...roving.getItemProps('bold')}>Bold</Button.Root>
	<Button.Root {...roving.getItemProps('italic')}>Italic</Button.Root>
</div>

orientation은 방향 키를 정합니다. vertical(기본값), horizontal, both 중 하나입니다. 컨테이너의 direction이 rtl이면 Left와 Right가 서로 바뀝니다. loop(기본값 true)는 끝에서 처음으로 돌아갑니다. typeahead는 입력한 글자로 시작하는 다음 항목으로 이동합니다. 항목의 텍스트가 레이블과 다르면 textValue를 넘기세요. tabStop: false는 모든 항목을 tabIndex={-1}로 만듭니다. 트리거에서 여는 메뉴처럼 코드로만 포커스가 들어가는 위젯에 씁니다. findItems(container)는 getItemProps 대신 항목을 찾습니다. Toolbar가 안에 있는 컨트롤을 이렇게 찾습니다. 이렇게 찾은 항목에는 값이 없으므로, 훅은 탭 정지를 두지 않고 항목 사이에서 포커스만 옮깁니다.

탭 정지는 activeValue를 따르며, 그 값이 null이거나 비활성화되었거나 렌더링되지 않았으면 활성화된 첫 항목으로 대체됩니다. 항목은 DOM에서 읽으므로, 서버에서 렌더링한 HTML에서는 activeValue나 defaultActiveValue가 가리키는 항목에만 탭 정지가 있습니다. 하이드레이션 전에도 묶음에 도달할 수 있어야 하면 둘 중 하나를 설정하세요. onActiveValueChange는 키, 클릭, 스크립트 중 무엇으로든 activeValue가 아닌 항목이 포커스를 받을 때마다 실행됩니다. 대체된 탭 정지도 포함되므로, 제어하는 쪽이 그 값을 유지할 수 있습니다. 네이티브 disabled 항목은 { disabled: true }를 넘긴 항목처럼 건너뜁니다. Shift와 방향 키, Home, End의 조합은 직접 처리합니다. 예를 들어 선택을 넓힐 때 씁니다. focus(value), focusFirst(), focusLast()는 직접 만든 핸들러에서 포커스를 옮기고, getItemNode(value)는 항목의 요소를 찾고, values()는 활성화된 값을 순서대로 읽고, reachableItems()는 지금 포커스가 닿을 수 있는 활성화된 항목을 반환합니다.

훅은 역할을 설정하지 않습니다. 컨테이너에는 toolbar, tablist, menu, radiogroup처럼 위젯에 필요한 역할을 주세요.

언제 쓰나요

언제: 탭 정지 하나와 방향 키가 기대되는 복합 위젯. 툴바, 세그먼트 컨트롤, 탭 목록, 메뉴, 카드 그리드.

쓰지 않을 때: 각 컨트롤이 각자 탭 정지인 폼이나 링크 목록. 입력에 포커스를 유지하는 listbox → OptionList와 Combobox 레시피처럼 aria-activedescendant.

Props

useRovingFocus props
이름타입필수기본값설명
orientation"horizontal" | "vertical" | "both"아니요"vertical"포커스를 옮기는 화살표 키. 오른쪽에서 왼쪽으로 쓰는(RTL) 컨테이너에서는 Left와 Right가 바뀝니다.
loopboolean아니요true마지막 항목에서 처음으로, 처음에서 마지막으로 순환합니다.
typeaheadboolean아니요false입력하면 입력한 문자로 텍스트가 시작하는 다음 항목에 포커스합니다.
tabStopboolean아니요true항목 하나를 탭으로 이동 가능하게 유지합니다. 메뉴처럼 포커스가 코드로만 들어올 때는 false이며, 모든 항목이 tabIndex -1을 받습니다.
findItems(container: HTMLElement) => HTMLElement[]아니요—getItemProps 대신 활성 여부와 관계없이 항목을 찾습니다. 이런 항목에는 값이 없으므로 탭 정지를 두지 않고, 훅은 포커스만 옮깁니다.
activeValuestring | null아니요—제어 탭 정지점. null이거나 비활성화되었거나 없으면 첫 번째 활성 항목으로 대체됩니다.
defaultActiveValuestring | null아니요—비제어일 때의 초기 탭 정지점.
onActiveValueChange(value: string) => void아니요—대체 탭 정지점을 포함해 activeValue가 아닌 항목이 포커스를 받으면 호출되므로, 제어하는 쪽이 이를 고정할 수 있습니다.