ScrollArea
ScrollArea.Root는 스크롤되면서 안에 든 요소의 포커스 링을 온전히 지키는 div입니다. 스크롤 컨테이너는 패딩 상자 밖으로 나간 것을 모두 잘라 내므로, 가장자리에 있는 컨트롤의 링이 잘립니다. ScrollArea.Root는 자식을 링의 너비와 오프셋만큼 패딩을 준 콘텐츠 상자에 넣습니다. 이 상자는 영역에 준 패딩의 안쪽에 있으므로 여백은 늘 남고, 영역의 가장자리 밖으로 나가는 것도 없습니다. 포커스를 받은 컨트롤의 링이 여전히 잘린다면 나머지 링이 보이도록 스크롤합니다.
이 페이지의 내용
<nav aria-label="Guide sections">
<ScrollArea.Root style={{ blockSize: '9rem' }}>
<Stack.Root as="ul" gap="condensed">
<li>
<Link.Root href="#overview">Overview</Link.Root>
</li>
…
</Stack.Root>
</ScrollArea.Root>
</nav>
axis는 스크롤할 방향을 고르며 기본값은 block이고, 다른 방향은 잘라 냅니다. 크기와 패딩은 다른 div처럼 주면 됩니다. 자식은 콘텐츠 상자 안에 있으므로, 자식의 레이아웃은 안쪽 요소로 잡으세요. 서버용 엔트리가 없으므로 Server Component에서는 클라이언트 컴포넌트 안에서 렌더링하세요.
컨트롤이 없는 콘텐츠의 Tab 정지점
콘텐츠가 넘치는데 안에 Tab 정지점이 없으면 키보드로는 나머지에 닿을 수 없습니다. 그래서 이때는 region 역할로 Tab 정지점이 되고, 화살표 키로 스크롤됩니다. aria-label이나 aria-labelledby로 이름을 붙이세요. 이름 없이 정지점이 되면 개발 중에 경고합니다. 자신의 링은 scroll.ring.focus로 가장자리 안쪽에 그립니다.
<ScrollArea.Root aria-label="Release notes" style={{ blockSize: '7rem' }}>
<Text.Root>…</Text.Root>
</ScrollArea.Root>
Tokens now follow DTCG 2025.10 and alias with JSON Pointer. Density modes scale spacing and control heights together. Every overlay renders in place on a native dialog or popover, so it inherits the theme of the page around it. Focus rings draw from focus.ring.width and focus.ring.offset, and scroll containers leave room for them, so no ring is cut at an edge.
focusable은 정지점을 어느 쪽으로든 정합니다. true는 안에 컨트롤이 있어도 넘치는 동안 정지점을 두고, false는 정지점을 두지 않습니다. 포커스가 이미 스크롤해 주는 링크 목록에 씁니다.
접근성을 지키는 조합
Todae가 맡는 것: 모든 가장자리에 포커스 링을 위한 여백을 두고, 포커스가 링 전체를 보이게 하도록 스크롤합니다. 콘텐츠가 넘치고 안에 Tab 정지점이 없는 동안 region 역할의 Tab 정지점이 되며, 포커스가 있는 동안에는 크기가 바뀌어도 포커스를 잃지 않도록 유지하고, 콘텐츠가 들어맞으면 없앱니다. 자신의 포커스 링은 가장자리 안쪽에 그립니다.
직접 제공할 것: Tab 정지점이 될 수 있을 때는 aria-label이나 aria-labelledby로 이름을 주세요. 스크롤이 생기도록 높이나 너비를 주세요.
주의할 점: ScrollArea.Root에 준 display: grid나 gap 같은 레이아웃 스타일. 유일한 자식이 콘텐츠 상자이므로 여러분의 자식에는 닿지 않습니다. 안쪽 요소에 주세요. 가로로 스크롤할 때 너비를 지켜야 하는 항목 행: 스크롤되는 일반 div에서처럼, flex와 grid 항목은 행이 스크롤되기 전에 가장 좁은 너비까지 줄어듭니다. flex: none이나 고정된 트랙 크기를 주세요.
언제 쓰나요
언제: 사이드바, 패널 안의 목록, 옆으로 스크롤되는 카드 줄처럼 따로 스크롤되는 영역.
쓰지 않을 때: 페이지 자체가 스크롤됨 → 스크롤 영역 없이 그대로 두세요. 스크롤 대신 줄을 접어야 하는 콘텐츠 → ChipList나 Field.ChipInput의 maxRows. 시트의 본문 → 이미 스크롤되고 같은 여백을 두는 BottomSheet의 BottomSheet.Body.
Props
| 이름 | 타입 | 설명 |
|---|---|---|
axis | 'block' | 'inline' | 'both' | 스크롤할 방향. 다른 방향은 잘라 냅니다. 기본값: 'block' |
focusable | boolean | 콘텐츠가 넘치는 동안 키보드로 스크롤할 수 있도록 Tab 정지점이 될지 정합니다. 주지 않으면 안에 포커스를 받을 요소가 없을 때만 됩니다. 정지점이 될 수 있으면 aria-label이나 aria-labelledby로 이름을 붙이세요. |