BorderBeam
BorderBeam (테두리 빔)
BorderBeam는 컨테이너에 장식용으로 흐르는 빔(beam) 효과를 더해 시각적 강조를 주고 싶을 때 사용하는 컴포넌트예요.
출처: 문서
본문
언제 사용하나요
- 비즈니스 상태 의미를 도입하지 않으면서 컨테이너에 더 강한 시각적 강조가 필요할 때.
- 로그인 패널, 추천 카드, AI 모듈, 핵심 CTA 블록에 적합해요.
- 장식 효과로, 포커스 링, 검증 테두리, 상태 피드백을 대체해서는 안 돼요.
예제 (Examples)
기본 (Basic)
기본 사용법이에요. BorderBeam으로 어떤 컨테이너든 감싸면 그 테두리를 따라 연속적인 장식 빔 효과가 더해져요.
import React from 'react';
import { BorderBeam, Card } from 'antd';
const App: React.FC = () => (
<div style={{ width: 360 }}>
<BorderBeam>
<Card title="Workspace overview">
Review task status, deployment health, and recent automation activity in one panel.
</Card>
</BorderBeam>
</div>
);
export default App;
호버 시 표시 (Show on hover)
기본적으로 테두리 빔을 숨기고, 컨테이너에 호버할 때 나타나게 해요.
import React from 'react';
import { BorderBeam, Card } from 'antd';
import { createStyles } from 'antd-style';
const useStyles = createStyles((props) => {
const { css, prefixCls, cssVar } = props;
return {
card: css`
width: 360px;
.${prefixCls}-border-beam {
opacity: 0;
transition: opacity ${cssVar.motionDurationMid};
&::before {
animation-play-state: paused;
}
}
&:hover {
.${prefixCls}-border-beam {
opacity: 1;
&::before {
animation-play-state: running;
}
}
}
`,
};
});
const Demo: React.FC = () => {
const { styles } = useStyles();
return (
<BorderBeam>
<Card className={styles.card} title="Hover over the card">
The border beam appears when the pointer moves over this card.
</Card>
</BorderBeam>
);
};
export default Demo;
여러 빔 (Multiple beams)
count로 빔의 개수를 설정해요. 여러 빔은 컨테이너 테두리 주변에 고르게 분포돼요. 양의 정수여야 하며 기본값은 1이에요.
import React from 'react';
import { BorderBeam, Card, Flex } from 'antd';
const App: React.FC = () => (
<Flex vertical gap="medium">
<BorderBeam count={3}>
<Card title="Multiple beams">
Set count to distribute multiple beams evenly around the container border.
</Card>
</BorderBeam>
<BorderBeam count={2}>
<Card title="Multiple beams">
Set count to distribute multiple beams evenly around the container border.
</Card>
</BorderBeam>
</Flex>
);
export default App;
커스텀 컨테이너 (Custom container)
커스텀 컨테이너도 BorderBeam을 호스팅할 수 있어요. 빔 레이어는 자식 노드에 삽입되고 position: absolute로 컨테이너 가장자리를 따라 배치되므로, 호스트 요소가 포지셔닝 컨텍스트를 제공해야 해요. 대부분 position: relative를 설정하면 돼요.
import React from 'react';
import { BorderBeam } from 'antd';
const panelStyle: React.CSSProperties = {
position: 'relative',
width: 420,
background: '#fff',
border: '1px solid #f0f0f0',
borderRadius: 8,
};
const contentStyle: React.CSSProperties = {
minHeight: 160,
padding: 24,
color: 'rgba(0, 0, 0, 0.88)',
lineHeight: 1.5715,
};
const App: React.FC = () => (
<BorderBeam>
<div style={panelStyle}>
<div style={contentStyle}>
Review task status, deployment health, and recent automation activity in one custom
container.
</div>
</div>
</BorderBeam>
);
export default App;
그라데이션 (Gradients)
여섯 가지 그라데이션 빔 팔레트를 보여주고 그 사이를 전환해요.
import React from 'react';
import { BorderBeam, Card, Flex, Segmented, Tag, Typography } from 'antd';
import type { BorderBeamGradient } from 'antd';
const presets: Array<{
key: string;
name: string;
usage: string;
description: string;
color: BorderBeamGradient;
}> = [
{
key: 'ocean',
name: 'Ocean',
usage: 'Dashboard',
description: 'A calm blue-green accent that works well for data views and cloud tooling.',
color: [
{ color: '#1677ff', percent: 0 },
{ color: '#36cfc9', percent: 52 },
{ color: '#95de64', percent: 100 },
],
},
{
key: 'sunset',
name: 'Sunset',
usage: 'Upgrade',
description: 'A warm highlight for upgrade prompts, featured cards, and marketing blocks.',
color: [
{ color: '#ff7a45', percent: 0 },
{ color: '#ff4d4f', percent: 49 },
{ color: '#ff85c0', percent: 100 },
],
},
{
key: 'aurora',
name: 'Aurora',
usage: 'AI',
description:
'A vivid cool-toned beam suited for AI assistants, copilots, and automation panels.',
color: [
{ color: '#7c3aed', percent: 0 },
{ color: '#06b6d4', percent: 57 },
{ color: '#67e8f9', percent: 100 },
],
},
{
key: 'forest',
name: 'Forest',
usage: 'Recommendation',
description:
'A bright natural palette that feels good on recommendation and growth-oriented cards.',
color: [
{ color: '#22c55e', percent: 0 },
{ color: '#a3e635', percent: 54 },
{ color: '#facc15', percent: 100 },
],
},
{
key: 'ember',
name: 'Ember',
usage: 'Alert',
description: 'A high-energy warm gradient for important alerts, launch cards, and hot paths.',
color: [
{ color: '#fa541c', percent: 0 },
{ color: '#ff7875', percent: 46 },
{ color: '#ffd666', percent: 100 },
],
},
{
key: 'nebula',
name: 'Nebula',
usage: 'Labs',
description: 'A cool purple-pink mix that fits experimental modules and product lab surfaces.',
color: [
{ color: '#2f54eb', percent: 0 },
{ color: '#722ed1', percent: 44 },
{ color: '#ff85c0', percent: 100 },
],
},
];
const defaultPresetKey = presets[0].key;
const App: React.FC = () => {
const [currentPresetKey, setCurrentPresetKey] = React.useState(defaultPresetKey);
const currentPreset = presets.find((preset) => preset.key === currentPresetKey) ?? presets[0];
return (
<Flex vertical gap={16} style={{ maxWidth: 480 }}>
<Segmented
block
options={presets.map((preset) => ({
label: preset.name,
value: preset.key,
}))}
value={currentPresetKey}
onChange={(value) => setCurrentPresetKey(value as string)}
/>
<BorderBeam color={currentPreset.color}>
<Card
title={currentPreset.name}
extra={<Tag variant="filled">{currentPreset.usage}</Tag>}
styles={{
body: {
display: 'flex',
flexDirection: 'column',
gap: 16,
},
}}
>
<Typography.Text type="secondary">{currentPreset.description}</Typography.Text>
<Flex gap={8} wrap>
{currentPreset.color.map((item) => (
<Tag key={`${item.color}-${item.percent}`} color={item.color} variant="filled">
{item.color} · {item.percent}%
</Tag>
))}
</Flex>
<Typography.Text type="secondary">
Stop positions use the public 0-100 input range.
</Typography.Text>
</Card>
</BorderBeam>
</Flex>
);
};
export default App;
지속시간 (Duration)
duration으로 빔이 한 바퀴를 도는 데 걸리는 시간(초)을 제어해요. 기본값은 6초예요.
import React from 'react';
import { BorderBeam, Card, Flex, Tag, Typography } from 'antd';
const durations = [
{
name: 'Fast',
seconds: 3,
description: 'A quick loop for temporary highlights and active modules.',
},
{
name: 'Default',
seconds: 6,
description: 'The original pacing for most emphasized containers.',
},
{
name: 'Slow',
seconds: 12,
description: 'A calmer loop for persistent panels and ambient surfaces.',
},
];
const App: React.FC = () => (
<Flex gap={16} wrap>
{durations.map(({ name, seconds, description }) => (
<div key={name} style={{ width: 220 }}>
<BorderBeam duration={seconds}>
<Card title={name} extra={<Tag variant="filled">{seconds}s</Tag>}>
<Typography.Text type="secondary">{description}</Typography.Text>
</Card>
</BorderBeam>
</div>
))}
</Flex>
);
export default App;
크기 (Size)
size로 보이는 빔 세그먼트의 크기를 제어해요. 기본값은 100px이고, 숫자는 픽셀로 처리돼요.
import React from 'react';
import { BorderBeam, Card, Tag, Typography } from 'antd';
const sizes: Array<{
name: string;
size?: number | string;
bodyMinHeight: number;
description: string;
spanFull?: boolean;
}> = [
{
name: 'Default',
bodyMinHeight: 112,
description: 'Uses the default 100px visible beam segment.',
},
{
name: 'Compact',
size: 56,
bodyMinHeight: 112,
description: 'Keeps the highlight shorter for dense card groups.',
},
{
name: 'Extended',
size: 160,
bodyMinHeight: 192,
description: 'Creates a longer highlight for wider feature panels.',
spanFull: true,
},
];
const App: React.FC = () => (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
gap: 32,
maxWidth: 960,
}}
>
{sizes.map(({ name, size, bodyMinHeight, description, spanFull }) => (
<div key={name} style={{ gridColumn: spanFull ? '1 / -1' : undefined }}>
<BorderBeam size={size}>
<Card
title={name}
extra={<Tag variant="filled">{size ?? 100}px</Tag>}
styles={{ body: { minHeight: bodyMinHeight, display: 'flex', alignItems: 'center' } }}
>
<Typography.Text type="secondary">{description}</Typography.Text>
</Card>
</BorderBeam>
</div>
))}
</div>
);
export default App;
선 너비 (Line width)
lineWidth로 개별 BorderBeam의 빔 너비를 조절해요. 기본값은 1px이고, 숫자는 픽셀로 처리돼요.
import React from 'react';
import { BorderBeam, Card } from 'antd';
const App: React.FC = () => (
<div style={{ width: 360 }}>
<BorderBeam lineWidth={2}>
<Card title="Custom line width" style={{ borderWidth: 2 }}>
Set lineWidth to match the border width of this container.
</Card>
</BorderBeam>
</div>
);
export default App;
API
공통 props 참고: Common props
BorderBeam
| 속성 | 설명 | 타입 | 기본값 | 버전 | 전역 설정 |
|---|---|---|---|---|---|
| children | 장식할 콘텐츠 | ReactNode |
- | 6.4.0 | × |
| color | 빔 색 설정. 단일 색 문자열 또는 그라데이션 스톱을 지원. percent는 0 ~ 100 입력 범위를 사용하고 BorderBeam은 투명 페이드용으로 꼬리 공간을 남겨 둠 |
string | { color: string; percent: number }[] |
- | 6.4.0 | × |
| count | 빔 개수 | number | 1 | 6.6.0 | × |
| duration | 빔이 한 바퀴 도는 시간(초) | number | 6 | 6.5.0 | × |
| lineWidth | 빔 선의 너비. 숫자는 픽셀로 처리 | number | string |
1px |
6.5.0 | × |
| outset | 빔 레이어의 컨테이너 가장자리로부터 바깥 거리. 잘린(clipped) 컨테이너에는 0으로 설정 |
number | string |
- | 6.4.0 | × |
| size | 보이는 빔 세그먼트의 크기. 숫자는 픽셀로 처리 | number | string |
100 | 6.5.0 | × |
디자인 토큰 (Design Token)
전역 토큰 (Global Token)
| 토큰 이름 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| colorPrimary | 브랜드 색. 제품의 특성과 커뮤니케이션을 반영하는 가장 직접적인 시각 요소. 선택하면 완전한 색 팔레트가 자동 생성 | string | |
| colorPrimaryHover | 기본 색 그라데이션 아래의 호버 상태. | string | |
| lineWidth | 기본 컴포넌트의 테두리 너비 | number |
FAQ
축소 모션(reduced motion)이 켜지면 BorderBeam은 어떻게 동작하나요? {#faq-reduced-motion}
BorderBeam은 빔을 장식 효과로 취급해요. prefers-reduced-motion: reduce가 활성화되면 빔 효과가 숨겨져요.
color의 percent는 무엇을 의미하나요? {#faq-color-percent}
percent는 작성자가 지정한 스톱 위치를 나타내며 0부터 100까지의 값을 받아요. BorderBeam은 그 스톱들을 보이는 빔 세그먼트에 매핑하고, 움직이는 꼬리가 계속 보이도록 투명 페이드아웃용으로 뒤쪽 공간을 남겨 둬요.
size 제한 {#faq-size-limit}
BorderBeam은 한 변의 길이가 size인 정사각형 그라데이션 레이어로 빔을 만들어요. 레이어는 컨테이너 테두리를 따라 이동하고, 마스크가 테두리와 겹치는 영역을 노출해요. size는 테두리 경로 길이와 무관하게 한 변의 길이를 설정해요.
가로 가장자리를 따라 그라데이션 레이어는 가장자리의 양쪽으로 약 size / 2만큼 확장돼요. size가 마스크 오버레이 높이의 두 배에 가까워지거나 넘어가면 정사각형이 위·아래 가장자리를 모두 덮을 수 있어요. 빔이 세로 가장자리를 따라 이동할 때도 너비에 같은 기하가 적용돼요.
size를 마스크 오버레이의 짧은 쪽의 두 배보다 훨씬 작게 유지하세요: size < 2 × min(width, height). 마스크 오버레이는 보통 장식된 컨테이너와 크기가 비슷하고, outset이 그 크기를 바꿔요. 테두리 반지름, lineWidth, 그라데이션의 투명 영역도 겹침이 보이기 시작하는 지점에 영향을 줘요.
BorderBeam이 동작하지 않는 이유는? {#faq-not-working}
BorderBeam은 children에서 실제 DOM 노드를 찾아 그 노드에 빔 레이어를 삽입해야 해요. 감싼 콘텐츠가 네이티브 DOM 요소이거나, ref를 DOM 요소로 올바르게 전달하는 React 컴포넌트인지 확인하세요. 그렇지 않으면 BorderBeam이 실제 컨테이너를 찾을 수 없어 빔을 렌더링할 수 없어요.
빔 레이어는 position: absolute로 배치되므로, 찾은 DOM 노드도 포지셔닝 컨텍스트를 제공해야 해요. 대부분 감싼 요소에 position: relative를 설정하면 돼요. BorderBeam은 자식의 포지셔닝 스타일을 검사하거나 고쳐 주지 않아요.
성능상의 이유로 children이 빔을 호스팅할 수 있는지와 그 포지셔닝 정보는 초기화 중에 해석되며, 나중에 자식 구조나 포지셔닝 스타일이 바뀌어도 계속 갱신되지 않아요.
빔 반지름을 내 컨테이너와 어떻게 맞추나요? {#faq-radius}
BorderBeam은 빔 레이어를 실제 컨테이너의 자식으로 렌더링하고 border-radius: inherit로 그 반지름을 직접 상속해요. Card 같은 단일 컨테이너 자식의 경우 빔은 자동으로 컨테이너 반지름을 따라요. 더 복잡한 자식 트리에서는 실제 컨테이너 루트에 반지름이 설정되어 있는지 확인하세요.
반지름은 CSS 상속을 통해 동기화되며, 초기화 중에 읽거나 측정되지 않아요. className, 반응형 스타일, CSS 변수로 나중에 변경해도 빔 레이어에 자동으로 반영돼요.
더 알아보기 (Learn more)
- Card 컴포넌트 — 컨테이너 카드
- Segmented 컴포넌트 — 그라데이션 전환
- Ant Design 시작하기 — 프로젝트 설정