메뉴
메뉴 (Menu)
좋은 내비게이션 구성으로 사용자가 사이트를 빠르고 효율적으로 이동하게 해 주는 컴포넌트예요. 상단과 사이드 두 가지 내비게이션 옵션을 제공해요.
출처: 문서
본문
언제 사용하나요 (When To Use)
내비게이션은 모든 웹사이트에서 중요한 부분이에요. 좋은 내비게이션 구성은 사용자가 사이트를 빠르고 효율적으로 이동하게 해 주거든요. Ant Design은 상단과 사이드 두 가지 내비게이션 옵션을 제공해요. 상단 내비게이션은 웹사이트의 모든 카테고리와 기능을 제공하고, 사이드 내비게이션은 웹사이트의 다단계 구조를 제공해요.
내비게이션이 있는 더 많은 레이아웃: Layout.
개발자 참고 (Notes for developers)
- Menu는
ul요소로 렌더링되므로 자식 노드로li와script-supporting요소만 지원해요. 커스터마이즈된 노드는Menu.Item으로 감싸야 해요. - Menu는 노드 구조를 수집해야 하므로 자식은
Menu.*또는 이를 캡슐화한 HOC여야 해요.
예시 (Examples)
상단 내비게이션 (Top Navigation)
가로 상단 내비게이션 메뉴예요.
import React, { useState } from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
label: 'Navigation One',
key: 'mail',
icon: <MailOutlined />,
},
{
label: 'Navigation Two',
key: 'app',
icon: <AppstoreOutlined />,
disabled: true,
},
{
label: 'Navigation Three - Submenu',
key: 'SubMenu',
icon: <SettingOutlined />,
children: [
{
type: 'group',
label: 'Item 1',
children: [
{ label: 'Option 1', key: 'setting:1' },
{ label: 'Option 2', key: 'setting:2' },
],
},
{
type: 'group',
label: 'Item 2',
children: [
{ label: 'Option 3', key: 'setting:3' },
{ label: 'Option 4', key: 'setting:4' },
],
},
],
},
{
key: 'alipay',
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Navigation Four - Link
</a>
),
},
];
const App: React.FC = () => {
const [current, setCurrent] = useState('mail');
const onClick: MenuProps['onClick'] = (e) => {
console.log('click ', e);
setCurrent(e.key);
};
return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
};
export default App;
인라인 메뉴 (Inline menu)
인라인 서브메뉴가 있는 세로 메뉴예요.
import React from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: 'g2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
type: 'divider',
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
{
key: 'grp',
label: 'Group',
type: 'group',
children: [
{ key: '13', label: 'Option 13' },
{ key: '14', label: 'Option 14' },
],
},
];
const App: React.FC = () => {
const onClick: MenuProps['onClick'] = (e) => {
console.log('click ', e);
};
return (
<Menu
onClick={onClick}
style={{ width: 256 }}
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
mode="inline"
items={items}
/>
);
};
export default App;
접히는 인라인 메뉴 (Collapsed inline menu)
인라인 메뉴는 접을 수 있어요.
여기 sider 레이아웃이 있는 완전한 데모가 있어요.
import React, { useState } from 'react';
import {
AppstoreOutlined,
ContainerOutlined,
DesktopOutlined,
MailOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Button, Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{ key: '1', icon: <PieChartOutlined />, label: 'Option 1' },
{ key: '2', icon: <DesktopOutlined />, label: 'Option 2' },
{ key: '3', icon: <ContainerOutlined />, label: 'Option 3' },
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
],
},
];
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleCollapsed = () => {
setCollapsed(!collapsed);
};
return (
<div style={{ width: 256 }}>
<Button type="primary" onClick={toggleCollapsed} style={{ marginBottom: 16 }}>
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</Button>
<Menu
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
mode="inline"
theme="dark"
inlineCollapsed={collapsed}
items={items}
/>
</div>
);
};
export default App;
메뉴 툴팁 (Menu tooltip)
인라인 접힘 모드에서 tooltip을 설정하거나 비활성화해요.
import React, { useState } from 'react';
import {
AppstoreOutlined,
ContainerOutlined,
DesktopOutlined,
MailOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Button, Menu, Space, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{ key: '1', icon: <PieChartOutlined />, label: 'Option 1' },
{ key: '2', icon: <DesktopOutlined />, label: 'Option 2' },
{ key: '3', icon: <ContainerOutlined />, label: 'Option 3' },
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
],
},
];
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const [tooltipEnabled, setTooltipEnabled] = useState(true);
return (
<div style={{ width: 256 }}>
<Space style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={() => setCollapsed((prev) => !prev)}
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
/>
<Switch
checked={tooltipEnabled}
onChange={setTooltipEnabled}
checkedChildren="Tooltip On"
unCheckedChildren="Tooltip Off"
/>
</Space>
<Menu
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
mode="inline"
theme="dark"
inlineCollapsed={collapsed}
tooltip={tooltipEnabled ? { placement: 'left' } : false}
items={items}
/>
</div>
);
};
export default App;
현재 하위 메뉴만 열기 (Open current submenu only)
메뉴를 클릭하면 다른 모든 메뉴가 접혀 전체 메뉴가 컴팩트하게 유지되는 걸 볼 수 있어요.
import React, { useState } from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
key: '1',
icon: <MailOutlined />,
label: 'Navigation One',
children: [
{ key: '11', label: 'Option 1' },
{ key: '12', label: 'Option 2' },
{ key: '13', label: 'Option 3' },
{ key: '14', label: 'Option 4' },
],
},
{
key: '2',
icon: <AppstoreOutlined />,
label: 'Navigation Two',
children: [
{ key: '21', label: 'Option 1' },
{ key: '22', label: 'Option 2' },
{
key: '23',
label: 'Submenu',
children: [
{ key: '231', label: 'Option 1' },
{ key: '232', label: 'Option 2' },
{ key: '233', label: 'Option 3' },
],
},
{
key: '24',
label: 'Submenu 2',
children: [
{ key: '241', label: 'Option 1' },
{ key: '242', label: 'Option 2' },
{ key: '243', label: 'Option 3' },
],
},
],
},
{
key: '3',
icon: <SettingOutlined />,
label: 'Navigation Three',
children: [
{ key: '31', label: 'Option 1' },
{ key: '32', label: 'Option 2' },
{ key: '33', label: 'Option 3' },
{ key: '34', label: 'Option 4' },
],
},
];
interface LevelKeysProps {
key?: string;
children?: LevelKeysProps[];
}
const getLevelKeys = (items1: LevelKeysProps[]) => {
const key: Record<string, number> = {};
const func = (items2: LevelKeysProps[], level = 1) => {
items2.forEach((item) => {
if (item.key) {
key[item.key] = level;
}
if (item.children) {
func(item.children, level + 1);
}
});
};
func(items1);
return key;
};
const levelKeys = getLevelKeys(items as LevelKeysProps[]);
const App: React.FC = () => {
const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
const onOpenChange: MenuProps['onOpenChange'] = (openKeys) => {
const currentOpenKey = openKeys.find((key) => !stateOpenKeys.includes(key));
// open
if (currentOpenKey !== undefined) {
const repeatIndex = openKeys
.filter((key) => key !== currentOpenKey)
.findIndex((key) => levelKeys[key] === levelKeys[currentOpenKey]);
setStateOpenKeys(
openKeys
// remove repeat key
.filter((_, index) => index !== repeatIndex)
// remove current level all child
.filter((key) => levelKeys[key] <= levelKeys[currentOpenKey]),
);
} else {
// close
setStateOpenKeys(openKeys);
}
};
return (
<Menu
mode="inline"
defaultSelectedKeys={['231']}
openKeys={stateOpenKeys}
onOpenChange={onOpenChange}
style={{ width: 256 }}
items={items}
/>
);
};
export default App;
세로 메뉴 (Vertical menu)
서브메뉴가 팝업으로 열려요.
import React from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
key: 'sub1',
icon: <MailOutlined />,
label: 'Navigation One',
children: [
{
key: '1-1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
{
key: '1-2',
label: 'Item 2',
type: 'group',
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
],
},
{
key: 'sub2',
icon: <AppstoreOutlined />,
label: 'Navigation Two',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
];
const onClick: MenuProps['onClick'] = (e) => {
console.log('click', e);
};
const App: React.FC = () => (
<Menu onClick={onClick} style={{ width: 256 }} mode="vertical" items={items} />
);
export default App;
메뉴 테마 (Menu Themes)
light와 dark 두 가지 내장 테마가 있어요. 기본값은 light예요.
import React, { useState } from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps, MenuTheme } from 'antd';
import { Menu, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
{
key: 'sub1',
label: 'Navigation One',
icon: <MailOutlined />,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
],
},
{
key: 'sub2',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
{
key: 'sub3',
label: 'Submenu',
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
],
},
],
},
{
key: 'sub4',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
{ key: '11', label: 'Option 11' },
{ key: '12', label: 'Option 12' },
],
},
];
const App: React.FC = () => {
const [theme, setTheme] = useState<MenuTheme>('dark');
const [current, setCurrent] = useState('1');
const changeTheme = (value: boolean) => {
setTheme(value ? 'dark' : 'light');
};
const onClick: MenuProps['onClick'] = (e) => {
console.log('click ', e);
setCurrent(e.key);
};
return (
<>
<Switch
checked={theme === 'dark'}
onChange={changeTheme}
checkedChildren="Dark"
unCheckedChildren="Light"
/>
<br />
<br />
<Menu
theme={theme}
onClick={onClick}
style={{ width: 256 }}
defaultOpenKeys={['sub1']}
selectedKeys={[current]}
mode="inline"
items={items}
/>
</>
);
};
export default App;
하위 메뉴 테마 (Sub-menu theme)
theme prop으로 SubMenu 테마를 설정해 서로 다른 테마 색 효과를 낼 수 있어요. 이 예시는 루트는 dark, SubMenu는 light예요.
import React, { useState } from 'react';
import { MailOutlined } from '@ant-design/icons';
import type { MenuProps, MenuTheme } from 'antd';
import { Menu, Switch } from 'antd';
type MenuItem = Required<MenuProps>['items'][number];
const App: React.FC = () => {
const [menuTheme, setMenuTheme] = useState<MenuTheme>('light');
const [current, setCurrent] = useState('1');
const changeTheme = (value: boolean) => {
setMenuTheme(value ? 'dark' : 'light');
};
const onClick: MenuProps['onClick'] = (e) => {
setCurrent(e.key);
};
const items: MenuItem[] = [
{
key: 'sub1',
icon: <MailOutlined />,
label: 'Navigation One',
theme: menuTheme,
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
],
},
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
];
return (
<>
<Switch
checked={menuTheme === 'dark'}
onChange={changeTheme}
checkedChildren="Dark"
unCheckedChildren="Light"
/>
<br />
<br />
<Menu
onClick={onClick}
style={{ width: 256 }}
openKeys={['sub1']}
selectedKeys={[current]}
mode="vertical"
theme="dark"
items={items}
getPopupContainer={(node) => node.parentNode as HTMLElement}
/>
</>
);
};
export default App;
메뉴 타입 전환 (Switch the menu type)
동적 전환 모드(inline과 vertical 사이)를 보여 줘요.
import React, { useState } from 'react';
import {
AppstoreOutlined,
CalendarOutlined,
LinkOutlined,
MailOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { Divider, Menu, Switch } from 'antd';
import type { GetProp, MenuProps } from 'antd';
type MenuTheme = GetProp<MenuProps, 'theme'>;
type MenuItem = GetProp<MenuProps, 'items'>[number];
const items: MenuItem[] = [
{
key: '1',
icon: <MailOutlined />,
label: 'Navigation One',
},
{
key: '2',
icon: <CalendarOutlined />,
label: 'Navigation Two',
},
{
key: 'sub1',
label: 'Navigation Two',
icon: <AppstoreOutlined />,
children: [
{ key: '3', label: 'Option 3' },
{ key: '4', label: 'Option 4' },
{
key: 'sub1-2',
label: 'Submenu',
children: [
{ key: '5', label: 'Option 5' },
{ key: '6', label: 'Option 6' },
],
},
],
},
{
key: 'sub2',
label: 'Navigation Three',
icon: <SettingOutlined />,
children: [
{ key: '7', label: 'Option 7' },
{ key: '8', label: 'Option 8' },
{ key: '9', label: 'Option 9' },
{ key: '10', label: 'Option 10' },
],
},
{
key: 'link',
icon: <LinkOutlined />,
label: (
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
Ant Design
</a>
),
},
];
const App: React.FC = () => {
const [mode, setMode] = useState<'vertical' | 'inline'>('inline');
const [theme, setTheme] = useState<MenuTheme>('light');
const changeMode = (value: boolean) => {
setMode(value ? 'vertical' : 'inline');
};
const changeTheme = (value: boolean) => {
setTheme(value ? 'dark' : 'light');
};
return (
<>
<Switch onChange={changeMode} /> Change Mode
<Divider vertical />
<Switch onChange={changeTheme} /> Change Style
<br />
<br />
<Menu
style={{ width: 256 }}
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
mode={mode}
theme={theme}
items={items}
/>
</>
);
};
export default App;
시맨틱 DOM 스타일링 (Custom semantic dom styling)
classNames와 styles로 객체나 함수를 전달해 Menu의 시맨틱 DOM 스타일을 커스터마이즈할 수 있어요.
import React from 'react';
import { Flex, Menu } from 'antd';
import type { GetProp, MenuProps } from 'antd';
import { createStaticStyles } from 'antd-style';
const classNames = createStaticStyles(({ css }) => ({
root: css`
border: 1px solid #f0f0f0;
max-width: 600px;
padding: 8px;
border-radius: 4px;
`,
item: css`
color: #1677ff;
`,
}));
const items: Required<MenuProps>['items'] = [
{
key: 'SubMenu',
label: 'Navigation One',
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
],
},
],
},
{ key: 'mail', label: 'Navigation Two' },
];
const styles: MenuProps['styles'] = {
root: { border: '1px solid #f0f0f0', padding: 8, borderRadius: 4 },
item: { color: '#1677ff' },
subMenu: { list: { color: '#fa541c' } },
};
const stylesFn: MenuProps['styles'] = (info): GetProp<MenuProps, 'styles', 'Return'> => {
const hasSub = info.props.items?.[0];
return {
root: {
backgroundColor: hasSub ? 'rgba(240,249,255, 0.6)' : 'rgba(255,255,255)',
},
};
};
const App: React.FC = () => {
const shareProps: MenuProps = {
classNames,
items,
};
return (
<Flex vertical gap="medium">
<Menu {...shareProps} styles={styles} />
<Menu mode="inline" {...shareProps} styles={stylesFn} />
</Flex>
);
};
export default App;
커스텀 하위 메뉴 렌더 (Custom Submenu Render)
popupRender prop으로 하위 메뉴 팝업 렌더링을 커스터마이즈해요.
import React from 'react';
import type { MenuProps } from 'antd';
import { Col, ConfigProvider, Flex, Menu, Row, Space, Typography } from 'antd';
import { createStyles } from 'antd-style';
const { Title, Paragraph } = Typography;
const useStyles = createStyles(({ token }) => ({
navigationPopup: {
padding: token.padding,
minWidth: 480,
background: token.colorBgElevated,
borderRadius: token.borderRadiusLG,
boxShadow: token.boxShadowSecondary,
},
menuItem: {
borderRadius: token.borderRadius,
transition: `all ${token.motionDurationSlow}`,
cursor: 'pointer',
'&:hover': {
background: 'rgba(0, 0, 0, 0.02)',
},
},
menuItemSpace: {
padding: token.paddingSM,
},
leadingHeader: {
margin: '0 !important',
paddingBottom: token.paddingXS,
borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
},
marginLess: {
margin: '0 !important',
},
}));
const MenuItem = ({ title, description }: { title: string; description: string }) => {
const { styles } = useStyles();
return (
<div className={styles.menuItem}>
<Space vertical size={4} className={styles.menuItemSpace}>
<Title level={5} className={styles.marginLess}>
{title}
</Title>
<Paragraph type="secondary" className={styles.marginLess}>
{description}
</Paragraph>
</Space>
</div>
);
};
const menuItems = [
{
key: 'home',
label: 'Home',
},
{
key: 'features',
label: 'Features',
children: [
{
key: 'getting-started',
label: (
<MenuItem title="Getting Started" description="Quick start guide and learn the basics." />
),
},
{
key: 'components',
label: <MenuItem title="Components" description="Explore our component library." />,
},
{
key: 'templates',
label: <MenuItem title="Templates" description="Ready-to-use template designs." />,
},
],
},
{
key: 'resources',
label: 'Resources',
children: [
{
key: 'blog',
label: <MenuItem title="Blog" description="Latest updates and articles." />,
},
{
key: 'community',
label: <MenuItem title="Community" description="Join our developer community." />,
},
],
},
];
const App: React.FC = () => {
const { styles } = useStyles();
const popupRender: MenuProps['popupRender'] = (_, { item }) => {
return (
<Flex className={styles.navigationPopup} vertical gap="medium">
<Typography.Title level={3} className={styles.leadingHeader}>
{item.title}
</Typography.Title>
<Row gutter={16}>
{React.Children.map(item.children as React.ReactNode, (child) => {
if (!React.isValidElement(child)) {
return null;
}
return (
<Col span={12} key={child.key}>
{child}
</Col>
);
})}
</Row>
</Flex>
);
};
return (
<ConfigProvider
theme={{
components: {
Menu: {
popupBg: '#fff',
horizontalItemSelectedColor: '#1677ff',
horizontalItemHoverColor: '#1677ff',
},
Typography: {
titleMarginBottom: 0,
titleMarginTop: 0,
},
},
}}
>
<Menu mode="horizontal" items={menuItems} popupRender={popupRender} />
</ConfigProvider>
);
};
export default App;
API
공통 props는 Common props를 참고해요.
Menu
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) | 글로벌 설정 |
|---|---|---|---|---|---|
| classNames | 컴포넌트 내부의 각 시맨틱 구조에 대한 class를 지정해요. 객체 또는 함수를 지원해요. | Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> | - | 6.0.0 | |
| defaultOpenKeys | 기본으로 열린 하위 메뉴 키 배열 | string[] | - | × | |
| defaultSelectedKeys | 기본 선택된 메뉴 항목 키 배열 | string[] | - | × | |
| expandIcon | 하위 메뉴의 커스텀 확장 아이콘 | ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode |
- | 4.9.0 | 5.15.0 |
| forceSubMenuRender | 하위 메뉴를 보이기 전에 DOM으로 렌더링해요. | boolean | false | × | |
| inlineCollapsed | 메뉴가 인라인 모드일 때 접힘 상태를 지정해요. | boolean | - | × | |
| inlineIndent | 각 단계의 인라인 메뉴 항목 들여쓰기(픽셀) | number | 24 | × | |
| items | 메뉴 항목 콘텐츠 | ItemType[] | - | 4.20.0 | × |
| mode | 메뉴 타입 | vertical | horizontal | inline |
vertical |
× | |
| multiple | 여러 항목 선택 허용 | boolean | false | × | |
| openKeys | 현재 열린 하위 메뉴 키 배열 | string[] | - | × | |
| overflowedIndicator | 메뉴가 가로로 접히면 생략 부호 아이콘 커스터마이즈 | ReactNode | <EllipsisOutlined /> |
× | |
| selectable | 메뉴 항목 선택 허용 | boolean | true | × | |
| selectedKeys | 현재 선택된 메뉴 항목 키 배열 | string[] | - | × | |
| styles | 컴포넌트 내부의 각 시맨틱 구조에 대한 인라인 스타일을 지정해요. 객체 또는 함수를 지원해요. | Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> | - | 6.0.0 | |
| subMenuCloseDelay | 마우스가 떠날 때 하위 메뉴를 숨기는 지연 시간(초) | number | 0.1 | × | |
| subMenuOpenDelay | 마우스가 들어올 때 하위 메뉴를 보여 주는 지연 시간(초) | number | 0 | × | |
| tooltip | 인라인 접힘 모드의 메뉴 항목에 대한 tooltip props 설정. false로 비활성화. |
false | TooltipProps | - | 6.3.0 | × |
| theme | 메뉴의 색 테마 | light | dark |
light |
× | |
| triggerSubMenuAction | 하위 메뉴 열기/닫기를 트리거하는 동작 | hover | click |
hover |
× | |
| onClick | 메뉴 항목이 클릭될 때 호출 | function({ key, keyPath, domEvent, itemData }) | - | × | |
| onDeselect | 메뉴 항목이 선택 해제될 때 호출 (multiple 모드만) | function({ key, keyPath, selectedKeys, domEvent, itemData }) | - | × | |
| onOpenChange | 하위 메뉴가 열리거나 닫힐 때 호출 | function(openKeys: string[]) | - | × | |
| onSelect | 메뉴 항목이 선택될 때 호출 | function({ key, keyPath, selectedKeys, domEvent, itemData }) | - | × | |
| popupRender | 하위 메뉴의 커스텀 팝업 렌더러 | (node: ReactElement, props: { item: SubMenuProps; keys: string[] }) => ReactNode | - | × |
더 많은 옵션은 @rc-component/menu 참고.
ItemType
type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType;
MenuItemType
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| danger | 위험 스타일 표시 | boolean | false | |
| disabled | 메뉴 항목 비활성화 여부 | boolean | false | |
| extra | 메뉴 항목의 추가 요소 | ReactNode | - | 5.21.0 |
| icon | 메뉴 항목의 아이콘 | ReactNode | - | |
| key | 메뉴 항목의 고유 ID | string | - | |
| label | 메뉴 라벨 | ReactNode | - | |
| title | 접힌 항목의 표시 title 설정 | string | - |
SubMenuType
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| children | 하위 메뉴 또는 하위 메뉴 항목 | ItemType[] | - | |
| disabled | 하위 메뉴 비활성화 여부 | boolean | false | |
| icon | 하위 메뉴의 아이콘 | ReactNode | - | |
| key | 하위 메뉴의 고유 ID | string | - | |
| label | 메뉴 라벨 | ReactNode | - | |
| popupClassName | 하위 메뉴 class 이름. mode="inline"에서는 동작하지 않아요. |
string | - | |
| popupOffset | 하위 메뉴 오프셋. mode="inline"에서는 동작하지 않아요. |
[number, number] | - | |
| theme | SubMenu의 색 테마 (기본적으로 Menu에서 상속) | light | dark |
- | |
| onTitleClick | 하위 메뉴 제목이 클릭될 때 실행되는 콜백 | function({ key, domEvent }) | - | |
| popupRender | 현재 하위 메뉴의 커스텀 팝업 렌더러 | (node: ReactElement, props: { item: SubMenuProps; keys: string[] }) => ReactNode | - |
MenuItemGroupType
type을 group으로 정의해 그룹으로 만들어요.
const groupItem = {
type: 'group', // Must have
label: 'My Group',
children: [],
};
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| children | 하위 메뉴 항목 | MenuItemType[] | - | |
| label | 그룹의 제목 | ReactNode | - |
MenuDividerType
메뉴 항목 사이의 구분선이에요. 세로 팝업 Menu나 Dropdown Menu에서만 사용해요. type을 divider로 정의해야 해요.
const dividerItem = {
type: 'divider', // Must have
};
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| dashed | 선이 점선인지 여부 | boolean | false |
FAQ
왜 Menu의 children이 두 번 렌더링되나요? {#faq-render-twice}
Menu는 twice-render로 구조 정보를 수집해 HOC 사용을 지원해요. 단일 렌더링으로 합치면 로직이 훨씬 복잡해질 수 있어요. 수집 로직 개선에 기여하는 것을 환영해요.
왜 Menu가 Flex 레이아웃에서 반응형 접힘을 하지 않나요? {#faq-flex-layout}
Menu는 flex 레이아웃에서 항목을 완전히 렌더링한 다음 접어요. 반응형을 활성화하려면 flex가 Menu 너비를 고려하지 않도록 해야 해요. (온라인 데모)
<div style={{ flex }}>
<div style={{ ... }}>Some Content</div>
<Menu style={{ minWidth: 0, flex: "auto" }} />
</div>
시맨틱 DOM (Semantic DOM)
시맨틱 DOM 구조는 https://ant.design/components/menu/semantic.md 에서 확인할 수 있어요.
디자인 토큰 (Design Token)
컴포넌트 토큰 (Menu) (Component Token)
| 토큰 이름 (Token Name) | 설명 (Description) | 타입 (Type) | 기본값 (Default Value) |
|---|---|---|---|
| activeBarBorderWidth | 메뉴 항목 활성 바의 테두리 너비 | string | number | 1 |
| activeBarHeight | 메뉴 항목 활성 바의 높이 | number | 2 |
| activeBarWidth | 메뉴 항목 활성 바의 너비 | string | number | 0 |
| collapsedIconSize | 접힘 시 아이콘 크기 | number | 16 |
| collapsedWidth | 접힘 시 너비 | string | number | 80 |
| dangerItemActiveBg | 활성 위험 메뉴 항목의 배경색 | string | #fff2f0 |
| dangerItemColor | 위험 메뉴 항목 텍스트의 색 | string | #ff4d4f |
| dangerItemHoverColor | 위험 메뉴 항목 텍스트의 hover 색 | string | #ff4d4f |
| dangerItemSelectedBg | 선택된 위험 메뉴 항목의 배경색 | string | #fff2f0 |
| dangerItemSelectedColor | 선택된 위험 메뉴 항목 텍스트의 색 | string | #ff4d4f |
| darkDangerItemActiveBg | 다크 모드에서 활성 위험 메뉴 항목의 배경 | string | #ff4d4f |
| darkDangerItemColor | 다크 모드에서 위험 메뉴 항목 텍스트의 색 | string | #ff4d4f |
| darkDangerItemHoverColor | 다크 모드에서 hover된 위험 메뉴 항목의 배경 | string | #ff7875 |
| darkDangerItemSelectedBg | 다크 모드에서 활성 위험 메뉴 항목의 배경 | string | #ff4d4f |
| darkDangerItemSelectedColor | 다크 모드에서 선택된 위험 메뉴 항목의 색 | string | #fff |
| darkGroupTitleColor | 다크 모드에서 그룹 제목 텍스트의 색 | string | rgba(255,255,255,0.65) |
| darkItemBg | 다크 모드에서 메뉴 항목의 배경 | string | #001529 |
| darkItemColor | 다크 모드에서 메뉴 항목 텍스트의 색 | string | rgba(255,255,255,0.65) |
| darkItemDisabledColor | 다크 모드에서 비활성 메뉴 항목의 색 | string | rgba(255,255,255,0.25) |
| darkItemHoverBg | 다크 모드에서 hover된 메뉴 항목의 배경 | string | transparent |
| darkItemHoverColor | 다크 모드에서 hover된 메뉴 항목의 색 | string | #fff |
| darkItemSelectedBg | 다크 모드에서 활성 메뉴 항목의 배경 | string | #1677ff |
| darkItemSelectedColor | 다크 모드에서 선택된 메뉴 항목의 색 | string | #fff |
| darkPopupBg | 다크 모드에서 오버레이 메뉴의 배경색 | string | #001529 |
| darkSubMenuItemBg | 다크 모드에서 하위 메뉴 항목의 배경 | string | #000c17 |
| dropdownWidth | 팝업 메뉴의 너비 | string | number | 160 |
| groupTitleColor | 그룹 제목 텍스트의 색 | string | rgba(0,0,0,0.45) |
| groupTitleFontSize | 그룹 제목의 글자 크기 | number | 14 |
| groupTitleLineHeight | 그룹 제목의 줄 높이 | string | number | 1.5714285714285714 |
| horizontalItemBorderRadius | 가로 메뉴 항목의 테두리 반경 | number | 0 |
| horizontalItemHoverBg | hover 시 가로 메뉴 항목의 배경색 | string | transparent |
| horizontalItemHoverColor | 가로 메뉴 항목 텍스트의 hover 색 | string | #1677ff |
| horizontalItemSelectedBg | 선택 시 가로 메뉴 항목의 배경색 | string | transparent |
| horizontalItemSelectedColor | 선택된 가로 메뉴 항목 텍스트의 색 | string | #1677ff |
| horizontalLineHeight | 가로 메뉴 항목의 LineHeight | LineHeight<string | number> | undefined | 46px |
| iconMarginInlineEnd | 아이콘과 텍스트 사이 간격 | MarginInlineEnd<string | number> | undefined | 10 |
| iconSize | 아이콘 크기 | number | 14 |
| itemActiveBg | 활성 메뉴 항목의 배경색 | string | #e6f4ff |
| itemBg | 메뉴 항목 배경 | string | #ffffff |
| itemBorderRadius | 메뉴 항목의 반경 | number | 8 |
| itemColor | 메뉴 항목 텍스트의 색 | string | rgba(0,0,0,0.88) |
| itemDisabledColor | 비활성 메뉴 항목 텍스트의 색 | string | rgba(0,0,0,0.25) |
| itemHeight | 메뉴 항목의 높이 | string | number | 40 |
| itemHoverBg | hover 시 메뉴 항목의 배경색 | string | rgba(0,0,0,0.06) |
| itemHoverColor | 메뉴 항목 텍스트의 hover 색 | string | rgba(0,0,0,0.88) |
| itemMarginBlock | 메뉴 항목의 margin-block | MarginBlock<string | number> | undefined | 4 |
| itemMarginInline | 메뉴 항목의 가로 여백 | number | 4 |
| itemPaddingInline | 메뉴 항목의 padding-inline | PaddingInline<string | number> | undefined | 16 |
| itemSelectedBg | 선택 시 메뉴 항목의 배경색 | string | #e6f4ff |
| itemSelectedColor | 선택된 메뉴 항목 텍스트의 색 | string | #1677ff |
| popupBg | 팝업의 배경색 | string | #ffffff |
| subMenuItemBg | 하위 메뉴 항목의 배경색 | string | rgba(0,0,0,0.02) |
| subMenuItemBorderRadius | 하위 메뉴 항목의 반경 | number | 4 |
| subMenuItemSelectedColor | 하위 메뉴에 선택된 항목이 있을 때 하위 메뉴 제목의 색 | string | #1677ff |
| zIndexPopup | 팝업 메뉴의 z-index | number | 1050 |
글로벌 토큰 (Global Token)
| 토큰 이름 (Token Name) | 설명 (Description) | 타입 (Type) | 기본값 (Default Value) |
|---|---|---|---|
| borderRadius | 기본 컴포넌트의 테두리 반경 | number | |
| borderRadiusLG | LG 크기 테두리 반경이에요. Card, Modal 등 큰 테두리 반경을 가진 컴포넌트에 사용돼요. | number | |
| boxShadowSecondary | 요소의 2차 박스 섀도우 스타일을 제어해요. | string | |
| colorBgElevated | 팝업 레이어의 컨테이너 배경색이에요. 다크 모드에서는 이 토큰의 색이 colorBgContainer보다 약간 밝아요. 예: modal, pop-up, menu 등. |
string | |
| colorPrimaryBorder | 메인 색 그라데이션 아래의 스트로크 색이에요. Slider 같은 컴포넌트의 stroke에 사용돼요. | string | |
| colorSplit | 구분자 색으로 사용돼요. colorBorderSecondary와 같은 색이지만 투명도를 가져요. | string | |
| colorText | W3C 표준을 따르는 기본 텍스트 색이에요. 가장 어두운 중성색이기도 해요. | string | |
| colorTextLightSolid | 배경색이 있는 텍스트의 강조 색을 제어해요. Primary Button 컴포넌트의 텍스트 등. | string | |
| controlHeightLG | LG 컴포넌트 높이 | number | |
| fontFamily | Ant Design의 글꼴은 시스템의 기본 인터페이스 글꼴을 우선시하고, 화면 표시에 적합한 대체 글꼴 라이브러리를 제공해 플랫폼과 브라우저에 따라 가독성을 유지하며 친근하고 안정적이며 전문적인 특성을 반영해요. | string | |
| fontSize | 디자인 시스템에서 가장 널리 쓰이는 글자 크기로, 여기서 텍스트 그라데이션이 파생돼요. | number | |
| fontSizeLG | 큰 글자 크기 | number | |
| lineHeight | 텍스트의 줄 높이예요. | number | |
| lineType | 기본 컴포넌트의 테두리 스타일 | string | |
| lineWidth | 기본 컴포넌트의 테두리 두께 | number | |
| lineWidthFocus | 컴포넌트가 포커스 상태일 때 선의 너비를 제어해요. | number | |
| margin | 중간 크기의 요소 여백을 제어해요. | number | |
| marginXS | 작은 크기의 요소 여백을 제어해요. | number | |
| motionDurationMid | 동작 속도, 중간 속도예요. 중간 요소의 애니메이션 상호작용에 사용돼요. | string | |
| motionDurationSlow | 동작 속도, 느린 속도예요. 큰 요소의 애니메이션 상호작용에 사용돼요. | string | |
| motionEaseInOut | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseInOutCirc | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseInQuint | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseOut | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseOutCirc | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseOutQuint | 미리 정의된 모션 곡선이에요. | string | |
| padding | 요소의 패딩을 제어해요. | number | |
| paddingXL | 요소의 특대 패딩을 제어해요. | number | |
| paddingXS | 요소의 아주 작은 패딩을 제어해요. | number |