SegmentedButton
두세 개의 선택지를 하나로 붙여 놓고 그중 하나를 고르는 컨트롤입니다. 선택지가 짧고 개수가 적을 때, 모든 선택지를 한눈에 보여 주면서 자리를 아낍니다.
tsx
import { Segment, SegmentedButton } from 'neba';
<SegmentedButton aria-label="기간" defaultValue="week">
<Segment value="day">일</Segment>
<Segment value="week">주</Segment>
<Segment value="month">월</Segment>
</SegmentedButton>;Props
SegmentedButton
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 세그먼트가 놓이는 홈통의 무게. solid는 서리 낀 홈통에 채워진 타일, outline은 같은 홈통에 얇은 선과 밝아진 타일, text는 홈통 없이 선택된 것에만 표면 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 세그먼트의 높이와 타입 스케일. Button과 같은 사다리입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 홈통의 그림자 깊이. 0이 기본입니다 |
| value | string | number | null | — | 선택된 세그먼트. controlled |
| defaultValue | string | number | null | null | 처음 선택된 세그먼트 |
| onValueChange | (value: string | number | null) => void | — | 선택이 바뀔 때 |
| disabled | boolean | false | 전체를 한 번에 사용 불가로 |
| readOnly | boolean | false | 무엇이 선택되었는지는 보이되 바꿀 수는 없습니다 |
| name | string | — | 폼 전송 시의 필드 이름 |
| fullWidth | boolean | false | 세그먼트들이 폭을 똑같이 나눠 갖습니다 |
| children | ReactNode | — | Segment들 |
value와 onValueChange로 controlled, defaultValue로 uncontrolled 컴포넌트가 됩니다. 세트에는 이름이 필요하므로 aria-label이나 aria-labelledby를 주세요.
Segment
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value * | string | number | — | 세그먼트의 식별자. onValueChange가 보고하는 값입니다 |
| startIcon | ReactNode | — | 라벨 앞의 내용 |
| endIcon | ReactNode | — | 라벨 뒤의 내용, 개수, 상태 점 |
| disabled | boolean | false | 사용 불가. 집합에는 남습니다 |
| children | ReactNode | — | 세그먼트의 라벨 |
예시
variant
solid는 홈통 안에서 채워진 타일이 움직이고, outline은 같은 홈통에 테두리를 두르고 선택된 sheet를 밝힙니다. text는 홈통 없이 선택된 항목에만 표면이 생깁니다.
tsx
import { Segment, SegmentedButton } from 'neba';
const variants = ['solid', 'outline', 'text'] as const;
export default function SegmentedButtonVariants() {
return (
<div className="flex flex-col items-center gap-5">
{variants.map((variant) => (
<SegmentedButton key={variant} aria-label={variant} variant={variant} defaultValue="week">
<Segment value="day">Day</Segment>
<Segment value="week">Week</Segment>
<Segment value="month">Month</Segment>
</SegmentedButton>
))}
</div>
);
}size
Button과 같은 컨트롤 높이 단계를 씁니다. md Segment와 md Button이 모두 32px이므로 툴바에 나란히 놓아도 기준선이 맞습니다.
tsx
import { Segment, SegmentedButton } from 'neba';
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
export default function SegmentedButtonSizes() {
return (
<div className="flex flex-col items-center gap-4">
{sizes.map((size) => (
<SegmentedButton key={size} aria-label={size} size={size} defaultValue="on">
<Segment value="on">On</Segment>
<Segment value="auto">Auto</Segment>
<Segment value="off">Off</Segment>
</SegmentedButton>
))}
</div>
);
}startIcon · disabled · readOnly
readOnly는 선택 상태를 보여 주되 바꿀 수 없게 하고 채도만 낮춥니다. disabled는 색 계열을 중립 회색으로 바꿉니다. Segment 단위로도 disabled를 줄 수 있습니다.
tsx
import { Segment, SegmentedButton } from 'neba';
function GridIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<rect
x="2.5"
y="2.5"
width="4.5"
height="4.5"
rx="1"
stroke="currentColor"
strokeWidth="1.5"
/>
<rect x="9" y="2.5" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
<rect x="2.5" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
}
function ListIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<path
d="M5.5 4h8M5.5 8h8M5.5 12h8M2.5 4h.01M2.5 8h.01M2.5 12h.01"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
}
export default function SegmentedButtonStates() {
return (
<div className="flex w-full max-w-md flex-col items-center gap-5">
{/* Icons ride on the label, sized in `em`. */}
<SegmentedButton aria-label="Layout" defaultValue="grid">
<Segment value="list" startIcon={<ListIcon />}>
List
</Segment>
<Segment value="grid" startIcon={<GridIcon />}>
Grid
</Segment>
</SegmentedButton>
{/* One segment out of the set, without disabling the rest. */}
<SegmentedButton aria-label="Plan" defaultValue="team">
<Segment value="starter">Starter</Segment>
<Segment value="team">Team</Segment>
<Segment value="enterprise" disabled>
Enterprise
</Segment>
</SegmentedButton>
{/* Read-only keeps the choice and drains the saturation; disabled drops
the colour family entirely. */}
<SegmentedButton aria-label="Mode" defaultValue="auto" readOnly>
<Segment value="light">Light</Segment>
<Segment value="auto">Auto</Segment>
<Segment value="dark">Dark</Segment>
</SegmentedButton>
{/* The full width, divided evenly. */}
<SegmentedButton aria-label="Period" defaultValue="30d" fullWidth>
<Segment value="7d">7 days</Segment>
<Segment value="30d">30 days</Segment>
<Segment value="90d">90 days</Segment>
</SegmentedButton>
</div>
);
}fullWidth
세트를 컨테이너 너비만큼 늘리고 각 Segment가 공간을 균등하게 나눠 갖습니다.
접근성
role="radiogroup"으로 렌더링됩니다. 세트 전체가 tab 정지 하나이고, 그 안에서는 방향키로 이동하며, 선택된 항목에aria-checked가 붙습니다.- 선택 타일은
left·top·width·height로 이동하므로 라벨이 다시 그려지지 않습니다. 첫 렌더와 창 크기 변경 시에는 애니메이션하지 않습니다.
이럴 때는 다른 컴포넌트를
- 선택이 아니라 액션의 줄이라면 ButtonGroup을 쓰세요.
- 선택지가 다섯 개를 넘거나 라벨이 길다면 Select를 쓰세요.
- 아래에 패널이 딸린다면 Tabs를 쓰세요.
- 눈에 보이는 라벨이 필요하다면 RadioGroup이 적합합니다.