Navigation Menu
Navigation Menu
웹사이트를 탐색하기 위한 링크 모음 컴포넌트예요.
출처: 문서
본문
웹사이트 상단 내비게이션 바 같은 링크 모음 컴포넌트예요. 탭 포커스가 관리되는 유연한 레이아웃을 지원하고, 서브메뉴와 활성 아이템 인디케이터를 제공하며, 키보드 내비게이션을 완전히 지원해요. viewport와 CSS 변수를 통해 고급 애니메이션도 만들 수 있어요.
Features
- 제어(controlled)/비제어(uncontrolled) 방식 모두 지원.
- 탭 포커스가 관리되는 유연한 레이아웃 구조.
- 서브메뉴 지원.
- 선택적 활성 아이템 인디케이터.
- 완전한 키보드 내비게이션.
- 고급 애니메이션을 위한 CSS 변수 노출.
- 커스텀 타이밍 지원.
Anatomy
모든 파트를 임포트해 조립해요.
import { NavigationMenu } from "radix-ui";
export default () => (
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger />
<NavigationMenu.Content>
<NavigationMenu.Link />
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link />
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger />
<NavigationMenu.Content>
<NavigationMenu.Sub>
<NavigationMenu.List />
<NavigationMenu.Viewport />
</NavigationMenu.Sub>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Indicator />
</NavigationMenu.List>
<NavigationMenu.Viewport />
</NavigationMenu.Root>
);
API Reference
Root
내비게이션 메뉴의 모든 파트를 담아요.
| Prop | Type | Default |
|---|---|---|
defaultValue |
string |
No default value |
value |
string |
No default value |
onValueChange |
function |
No default value |
delayDuration |
number |
200 |
skipDelayDuration |
number |
300 |
dir |
enum |
No default value |
orientation |
enum |
"horizontal" |
| Data attribute | Values |
|---|---|
[data-orientation] |
"vertical" | "horizontal" |
Sub
서브메뉴를 나타내요. 중첩해서 서브메뉴를 만들 때 root 파트 대신 사용해요.
| Prop | Type | Default |
|---|---|---|
defaultValue |
string |
No default value |
value |
string |
No default value |
onValueChange |
function |
No default value |
orientation |
enum |
"horizontal" |
| Data attribute | Values |
|---|---|
[data-orientation] |
"vertical" | "horizontal" |
List
최상위 메뉴 아이템을 담아요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
| Data attribute | Values |
|---|---|
[data-orientation] |
"vertical" | "horizontal" |
Item
최상위 메뉴 아이템으로, 링크 또는 트리거와 콘텐츠의 조합을 담아요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
value |
string |
No default value |
Trigger
콘텐츠를 토글하는 버튼이에요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
| Data attribute | Values |
|---|---|
[data-state] |
"open" | "closed" |
[data-disabled] |
Present when disabled |
Content
각 트리거와 연결된 콘텐츠를 담아요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
onEscapeKeyDown |
function |
No default value |
onPointerDownOutside |
function |
No default value |
onFocusOutside |
function |
No default value |
onInteractOutside |
function |
No default value |
forceMount |
boolean |
No default value |
| Data attribute | Values |
|---|---|
[data-state] |
"open" | "closed" |
[data-motion] |
"to-start" | "to-end" | "from-start" | "from-end" |
[data-orientation] |
"vertical" | "horizontal" |
Link
탐색 링크예요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
active |
boolean |
false |
onSelect |
function |
No default value |
| Data attribute | Values |
|---|---|
[data-active] |
Present when active |
Indicator
리스트 아래에 렌더링되는 선택적 인디케이터 요소로, 현재 활성 트리거를 강조하는 데 사용해요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
forceMount |
boolean |
No default value |
| Data attribute | Values |
|---|---|
[data-state] |
"visible" | "hidden" |
[data-orientation] |
"vertical" | "horizontal" |
| CSS Variable | Description |
|---|---|
--radix-navigation-menu-indicator-translate-x |
The horizontal offset of the indicator, computed from the active trigger's position. Present when the menu is horizontal. |
--radix-navigation-menu-indicator-translate-y |
The vertical offset of the indicator, computed from the active trigger's position. Present when the menu is vertical. |
Viewport
리스트 바깥에 활성 콘텐츠를 렌더링하는 데 사용하는 선택적 뷰포트 요소예요.
| Prop | Type | Default |
|---|---|---|
asChild |
boolean |
false |
forceMount |
boolean |
No default value |
| Data attribute | Values |
|---|---|
[data-state] |
"open" | "closed" |
[data-orientation] |
"vertical" | "horizontal" |
| CSS Variable | Description |
|---|---|
--radix-navigation-menu-viewport-width |
The width of the viewport when visible/hidden, computed from the active content |
--radix-navigation-menu-viewport-height |
The height of the viewport when visible/hidden, computed from the active content |
Examples
세로 방향
orientation prop을 사용해 세로 메뉴를 만들 수 있어요.
<NavigationMenu.Root orientation="vertical">
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item one</NavigationMenu.Trigger>
<NavigationMenu.Content>Item one content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item two</NavigationMenu.Trigger>
<NavigationMenu.Content>Item Two content</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
유연한 레이아웃
Content가 어디에 렌더링될지에 대한 추가 제어가 필요하면 Viewport 파트를 사용해요. DOM 구조를 조정해야 하거나 고급 애니메이션을 위한 유연성이 필요할 때 유용해요. 탭 포커스는 자동으로 유지돼요.
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item one</NavigationMenu.Trigger>
<NavigationMenu.Content>Item one content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item two</NavigationMenu.Trigger>
<NavigationMenu.Content>Item two content</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
{/* NavigationMenu.Content will be rendered here when active */}
<NavigationMenu.Viewport />
</NavigationMenu.Root>
인디케이터와 함께
선택적 Indicator 파트로 현재 활성 Trigger를 강조할 수 있어요. Viewport와 함께 화살표나 하이라이트 같은 애니메이션 시각적 표시를 제공하고 싶을 때 유용해요.
// index.jsx
import { NavigationMenu } from "radix-ui";
import "./styles.css";
export default () => (
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item one</NavigationMenu.Trigger>
<NavigationMenu.Content>Item one content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item two</NavigationMenu.Trigger>
<NavigationMenu.Content>Item two content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Indicator className="NavigationMenuIndicator" />
</NavigationMenu.List>
<NavigationMenu.Viewport />
</NavigationMenu.Root>
);
/* styles.css */
.NavigationMenuIndicator {
background-color: grey;
}
.NavigationMenuIndicator[data-orientation="horizontal"] {
height: 3px;
transition: width, transform, 250ms ease;
}
서브메뉴와 함께
NavigationMenu을 중첩하고 Root 대신 Sub 파트를 사용해 서브메뉴를 만들어요. 서브메뉴는 Root 내비게이션 메뉴와 다르게 Tabs와 비슷해서 항상 하나의 아이템이 활성이어야 하므로 defaultValue를 지정해 주세요.
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item one</NavigationMenu.Trigger>
<NavigationMenu.Content>Item one content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item two</NavigationMenu.Trigger>
<NavigationMenu.Content>
<NavigationMenu.Sub defaultValue="sub1">
<NavigationMenu.List>
<NavigationMenu.Item value="sub1">
<NavigationMenu.Trigger>Sub item one</NavigationMenu.Trigger>
<NavigationMenu.Content>Sub item one content</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item value="sub2">
<NavigationMenu.Trigger>Sub item two</NavigationMenu.Trigger>
<NavigationMenu.Content>Sub item two content</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Sub>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
클라이언트 사이드 라우팅과 함께
라우팅 패키지가 제공하는 Link 컴포넌트를 써야 한다면, 커스텀 컴포넌트로 NavigationMenu.Link와 조합하는 것을 권장해요. 접근성과 일관된 키보드 제어가 유지돼요. Next.js 예시:
// index.jsx
import { usePathname } from "next/navigation";
import NextLink from "next/link";
import { NavigationMenu } from "radix-ui";
import "./styles.css";
const Link = ({ href, ...props }) => {
const pathname = usePathname();
const isActive = href === pathname;
return (
<NavigationMenu.Link asChild active={isActive}>
<NextLink href={href} className="NavigationMenuLink" {...props} />
</NavigationMenu.Link>
);
};
export default () => (
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<Link href="/">Home</Link>
</NavigationMenu.Item>
<NavigationMenu.Item>
<Link href="/about">About</Link>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
);
/* styles.css */
.NavigationMenuLink {
text-decoration: none;
}
.NavigationMenuLink[data-active] {
text-decoration: "underline";
}
고급 애니메이션
--radix-navigation-menu-viewport-[width|height]와 data-motion['from-start'|'to-start'|'from-end'|'to-end'] 속성을 노출해, 진입/퇴장 방향에 따라 Viewport 크기와 Content 위치를 애니메이션할 수 있어요.
이들을 position: absolute;와 결합하면 항목 간 이동 시 부드러운 오버랩 애니메이션 효과를 만들 수 있어요.
// index.jsx
import { NavigationMenu } from "radix-ui";
import "./styles.css";
export default () => (
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item one</NavigationMenu.Trigger>
<NavigationMenu.Content className="NavigationMenuContent">
Item one content
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item two</NavigationMenu.Trigger>
<NavigationMenu.Content className="NavigationMenuContent">
Item two content
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
<NavigationMenu.Viewport className="NavigationMenuViewport" />
</NavigationMenu.Root>
);
/* styles.css */
.NavigationMenuContent {
position: absolute;
top: 0;
left: 0;
animation-duration: 250ms;
animation-timing-function: ease;
}
.NavigationMenuContent[data-motion="from-start"] {
animation-name: enterFromLeft;
}
.NavigationMenuContent[data-motion="from-end"] {
animation-name: enterFromRight;
}
.NavigationMenuContent[data-motion="to-start"] {
animation-name: exitToLeft;
}
.NavigationMenuContent[data-motion="to-end"] {
animation-name: exitToRight;
}
.NavigationMenuViewport {
position: relative;
width: var(--radix-navigation-menu-viewport-width);
height: var(--radix-navigation-menu-viewport-height);
transition: width, height, 250ms ease;
}
@keyframes enterFromRight {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enterFromLeft {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exitToRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exitToLeft {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
Accessibility
navigation role 요구사항을 준수해요.
menubar와의 차이점
NavigationMenu는 menubar와 혼동해서는 안 돼요. 이 primitive는 일상적인 의미로 menu라는 이름을 공유해 탐색 링크 집합을 가리키지만, WAI-ARIA menu role을 사용하지 않아요. menu와 menubar는 데스크톱 애플리케이션 창에서 흔히 볼 수 있는 네이티브 OS 메뉴처럼 동작해서, 복합 포커스 관리·첫 문자 내비게이션 같은 복잡한 기능을 갖기 때문이에요.
이런 기능들은 웹사이트 탐색에는 불필요하다고 여겨지며, 최악의 경우 확립된 웹사이트 패턴에 익숙한 사용자를 혼란스럽게 할 수 있어요.
자세한 내용은 W3C Disclosure Navigation Menu 예시를 참고하세요.
링크 사용과 aria-current
메뉴 안의 모든 탐색 링크에 NavigationMenu.Link를 사용하는 것이 중요해요. 주 리스트뿐 아니라 NavigationMenu.Content로 렌더링되는 콘텐츠 안에서도 마찬가지예요. 이렇게 해야 일관된 키보드 상호작용과 접근성이 유지되고, active prop으로 aria-current와 활성 스타일도 설정할 수 있어요. 서드파티 라우팅 컴포넌트와의 사용법은 이 예시를 참고하세요.
키보드 상호작용
| Key | Description |
|---|---|
Space``Enter |
When focus is on NavigationMenu.Trigger, opens the content. |
Tab |
Moves focus to the next focusable element. |
ArrowDown |
When horizontal and focus is on an open NavigationMenu.Trigger, moves focus into NavigationMenu.Content. Moves focus to the next NavigationMenu.Trigger or NavigationMenu.Link. |
ArrowUp |
Moves focus to the previous NavigationMenu.Trigger or NavigationMenu.Link. |
ArrowRight``ArrowLeft |
When vertical and focus is on an open NavigationMenu.Trigger, moves focus into its NavigationMenu.Content. Moves focus to the next / previous NavigationMenu.Trigger or NavigationMenu.Link. |
Home``End |
Moves focus to the first/last NavigationMenu.Trigger or NavigationMenu.Link. |
Esc |
Closes open NavigationMenu.Content and moves focus to its NavigationMenu.Trigger. |
더 알아보기 (Learn more)
- NavigationMenu는 nav 요소(WAI-ARIA
navigationrole) 기반이라 menubar의menurole과 다르게 동작해요. - 서브메뉴(Sub)는 Tabs처럼 항상 하나가 활성이어야 하므로
defaultValue를 지정하는 것이 좋아요.