IconButton
글리프 하나만 담는 원형 버튼입니다. 툴바나 목록 행처럼 라벨을 둘 공간이 없는 자리에 씁니다.
tsx
import { IconButton } from 'neba';
<IconButton icon={<PlusIcon />} label="항목 추가" />;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| icon * | ReactNode | — | 글리프. 그냥 넘기면 버튼에 대해 em으로 잡히고, 따로 크기가 필요하면 Icon으로 감싸세요 |
| label * | string | — | 버튼이 하는 일을 설명하는 접근성 이름. 이 컴포넌트의 유일한 필수 prop이며, 없으면 글리프뿐인 버튼에 이름이 생기지 않습니다 |
| variant공통 | 'solid' | 'outline' | 'text' | 'solid' | 표면의 무게. 채움 / 하이라인 / 없음 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Button과 같은 높이 사다리. 원반 하나가 버튼 줄에 끼어도 기준선이 흐트러지지 않습니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 전달은 되지만 눈에 보이지 않습니다. 아이콘 전용 컨트롤은 정사각형이라 가로 여백이 0입니다 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| loading | boolean | false | 글리프 자리에 스피너를 놓고 동작을 막습니다. 포커스는 그대로 |
| readOnly | boolean | false | 흐려지지 않은 채 반응만 멈춤, 액션은 존재하지만 여기서는 쓸 수 없습니다 |
| disabled | boolean | false | 사용 불가. 색 계열을 버리고 중립 회색이 됩니다 |
<button>의 native 속성은 그대로 전달됩니다. variant · elevation · loading · readOnly를 포함해 Button의 축을 그대로 씁니다.
정사각형 아이콘 컨트롤이 필요하다면 children 없는 Button이 이미 그 모양입니다.
예시
size
Button과 같은 컨트롤 높이 단계를 씁니다. 버튼이 늘어선 줄에 섞어 놓아도 기준선이 맞습니다.
tsx
import { Button, IconButton, Typography } from 'neba';
function SearchIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<circle cx="7" cy="7" r="4.25" stroke="currentColor" strokeWidth="1.5" />
<path d="m10.25 10.25 3 3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
const SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
/**
* The same control heights everything else uses, so a disc drops into a row of
* buttons without the row losing its baseline.
*/
export default function IconButtonSizes() {
return (
<div className="flex flex-col gap-5">
<div className="flex flex-wrap items-end gap-4">
{SIZES.map((size) => (
<div key={size} className="flex flex-col items-center gap-2">
<IconButton icon={<SearchIcon />} label={`Search, ${size}`} size={size} />
<Typography level="caption">{size}</Typography>
</div>
))}
</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline">Filter</Button>
<IconButton icon={<SearchIcon />} label="Search" variant="outline" />
<Typography level="caption">Same height, different shape.</Typography>
</div>
</div>
);
}loading · readOnly · disabled
동작은 Button의 같은 prop과 동일합니다. loading은 글리프 자리에 spinner를 띄웁니다.
tsx
import { useState } from 'react';
import { IconButton, Typography } from 'neba';
function RefreshIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<path
d="M13 8a5 5 0 1 1-1.6-3.7"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M13 2v3h-3"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
/** Every Button state is here unchanged, including the spinner. */
export default function IconButtonStates() {
const [loading, setLoading] = useState(false);
return (
<div className="flex flex-wrap items-center gap-4">
<div className="flex flex-col items-center gap-2">
<IconButton
icon={<RefreshIcon />}
label="Refresh"
loading={loading}
onClick={() => {
setLoading(true);
setTimeout(() => setLoading(false), 1600);
}}
/>
<Typography level="caption">press me</Typography>
</div>
<div className="flex flex-col items-center gap-2">
<IconButton icon={<RefreshIcon />} label="Refresh" disabled />
<Typography level="caption">disabled</Typography>
</div>
<div className="flex flex-col items-center gap-2">
<IconButton icon={<RefreshIcon />} label="Refresh" readOnly />
<Typography level="caption">readOnly</Typography>
</div>
<div className="flex flex-col items-center gap-2">
<IconButton icon={<RefreshIcon />} label="Refresh" elevation={2} />
<Typography level="caption">elevation 2</Typography>
</div>
</div>
);
}label
label은 필수 prop입니다. 글리프뿐인 버튼은 accessible name을 가질 방법이 없으므로 타입 차원에서 요구합니다.
tsx
// 타입 에러가 발생합니다.
<IconButton icon={<TrashIcon />} />
// 이렇게 쓰세요.
<IconButton icon={<TrashIcon />} label="파일 삭제" />label은 화면에 보이지 않습니다. 눈으로도 확인할 수 있게 하려면 Tooltip으로 감싸세요.
접근성
label이aria-label로 전달됩니다.- focus ring은
:focus-visible에서만 나타납니다.