Claude Code로 SVG 조작을 안전하게 구현하는 실전 가이드
Claude Code로 SVG를 다룰 때 필요한 viewBox, 접근 가능한 아이콘, currentColor 테마, 애니메이션, SVGO 최적화를 정리합니다.
SVG 작업 전에 정해야 할 것
SVG는 아이콘, 로고, 다이어그램, 작은 차트에 적합한 벡터 형식입니다. 크기를 키워도 선명하고, CSS로 색을 바꿀 수 있으며, React 컴포넌트로 만들기도 쉽습니다. Claude Code는 코드베이스를 읽고 파일을 수정하며 명령을 실행할 수 있으므로, SVG를 실제 UI 부품으로 정리하는 작업과 잘 맞습니다.
하지만 “SVG 아이콘을 만들어 줘”라고만 요청하면 중요한 조건이 빠지기 쉽습니다. viewBox가 사라지거나, 색상이 하드코딩되거나, 스크린 리더가 장식 아이콘을 불필요하게 읽거나, 업로드된 SVG를 그대로 inline 처리하는 문제가 생길 수 있습니다. Masa도 ClaudeCodeLab의 작은 UI 검증에서 같은 문제를 겪었습니다. 처음 만든 아이콘은 예뻤지만 텍스트 버튼, 아이콘 전용 버튼, 다크 모드에서 재사용하려면 매번 손으로 고쳐야 했습니다.
이 글에서는 Claude Code에 맡겨도 무너지지 않는 SVG 작업 흐름을 정리합니다. inline SVG, viewBox, 접근성, currentColor와 CSS 변수, 간단한 애니메이션, SVGO 최적화, 레이아웃과 보안 실수를 모두 다룹니다. 공식 문서는 MDN <svg>, MDN viewBox, MDN ARIA img role, MDN aria-hidden, SVGO 문서, Claude Code 개요를 기준으로 확인하면 됩니다.
전체 흐름
SVG 작업은 그림 하나를 만드는 일이 아니라 UI 품질을 관리하는 일입니다. 좌표계, 색상 전략, 접근성, 최적화, 리뷰가 모두 결과에 영향을 줍니다.
flowchart LR
A["목적 정하기"] --> B["viewBox 고정"]
B --> C["currentColor 사용"]
C --> D["aria-label 또는 aria-hidden 선택"]
D --> E["HTML 또는 React에 삽입"]
E --> F["SVGO로 최적화"]
F --> G["레이아웃과 보안 검토"]
Claude Code에는 다음처럼 구체적으로 요청하는 편이 좋습니다. “viewBox="0 0 24 24"를 유지하고, 선 색은currentColor를 사용하며, 장식 아이콘은aria-hidden, 의미 있는 단독 아이콘은role="img"와title을 사용하고, viewBox를 삭제하지 않는 SVGO 설정까지 추가해 주세요.”
inline SVG와 viewBox 기본
inline SVG는 이미지 파일을img로 불러오는 대신 HTML이나 JSX 안에<svg>를 직접 쓰는 방식입니다. hover 색상, 테마, 상태 변화, 작은 애니메이션이 필요한 UI 아이콘에 적합합니다.
viewBox는 SVG 내부 좌표계입니다. MDN은 이를min-x min-y width height 네 숫자로 설명합니다. 쉽게 말하면 viewBox="0 0 24 24"는 “이 아이콘은 24 곱하기 24 격자 위에 그린다”는 뜻입니다. 표시 크기는 16px, 24px, 48px로 바꿀 수 있지만 내부 좌표는 유지됩니다.
<button class="icon-button" type="button" aria-label="검색">
<svg
class="icon"
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
focusable="false"
>
<circle cx="11" cy="11" r="7" />
<path d="M20 20l-4.5-4.5" />
</svg>
</button>
이 예시에서는 버튼 자체가aria-label="검색"을 가지고 있으므로 SVG는 장식입니다. 아이콘 전용 버튼이라면 버튼에 이름을 주고, SVG 자체가 독립적인 그림이라면 SVG에 접근 가능한 이름을 줘야 합니다.
currentColor와 CSS 변수로 테마 대응
SVG 내부에fill="#111827"나stroke="#0ea5e9" 같은 색을 직접 넣으면 다크 모드나 hover 상태에서 수정해야 할 곳이 늘어납니다. 일반 UI 아이콘은 가능한 한currentColor를 사용하고, 색상은 CSS 변수로 관리하는 편이 안전합니다.
:root {
--color-text: #172033;
--color-muted: #667085;
--color-accent: #0f766e;
--color-danger: #b42318;
}
[data-theme="dark"] {
--color-text: #eef2f7;
--color-muted: #a9b4c3;
--color-accent: #2dd4bf;
--color-danger: #f97066;
}
.icon {
color: var(--icon-color, var(--color-text));
display: inline-block;
inline-size: 1.25rem;
block-size: 1.25rem;
flex: 0 0 auto;
}
.icon-button {
color: var(--color-muted);
}
.icon-button:hover {
color: var(--color-accent);
}
.icon-button[data-variant="danger"] {
--icon-color: var(--color-danger);
}
<button class="icon-button" type="button" aria-label="삭제" data-variant="danger">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" aria-hidden="true">
<path d="M4 7h16" />
<path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M6 7l1 14h10l1-14" />
<path d="M9 7V4h6v3" />
</svg>
</button>
리팩터링 후에는 Claude Code에rg "fill=\"#|stroke=\"#"를 실행하게 해서 고정 색상이 남아 있는지 확인합니다. 로고처럼 브랜드 색이 필요한 경우는 예외로 남길 수 있습니다.
React 아이콘 컴포넌트
의미 있는 SVG에는 이름이 필요하고, 장식 SVG는 접근성 트리에서 숨기는 것이 좋습니다. MDN은 삽입된 SVG를 하나의 이미지로 다룰 때role="img"와 라벨을 주는 방식을 설명합니다. 반대로 텍스트 버튼 옆의 장식 아이콘은aria-hidden="true"로 숨길 수 있습니다.
import { useId } from "react";
type IconName = "search" | "check" | "close";
const paths: Record<IconName, string> = {
search: "M10.5 18a7.5 7.5 0 1 1 5.3-12.8 7.5 7.5 0 0 1-5.3 12.8Zm5.3-2.2L21 21",
check: "M5 12.5l4.5 4.5L19 7",
close: "M6 6l12 12M18 6L6 18",
};
type SvgIconProps = {
name: IconName;
title?: string;
decorative?: boolean;
size?: number;
className?: string;
};
export function SvgIcon({
name,
title,
decorative = false,
size = 24,
className,
}: SvgIconProps) {
const titleId = useId();
const isMeaningful = !decorative && Boolean(title);
return (
<svg
className={className}
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
role={isMeaningful ? "img" : undefined}
aria-labelledby={isMeaningful ? titleId : undefined}
aria-hidden={decorative ? true : undefined}
focusable="false"
>
{isMeaningful ? <title id={titleId}>{title}</title> : null}
<path d={paths[name]} />
</svg>
);
}
<button type="button">
<SvgIcon name="check" decorative />
저장
</button>
<SvgIcon name="search" title="검색" />
첫 번째는 버튼 텍스트가 이미 동작을 설명하므로 아이콘은 장식입니다. 두 번째는 아이콘 자체가 의미를 가지므로title을 전달합니다. 포커스 가능한 요소에aria-hidden="true"를 붙이면 안 됩니다.
작은 애니메이션
SVG 애니메이션은 로딩, 성공 표시, 차트 강조처럼 작은 신호에 적합합니다. 레이아웃을 흔들지 않고, prefers-reduced-motion을 존중해야 합니다.
<svg class="spinner" viewBox="0 0 48 48" width="48" height="48" role="img" aria-label="로딩 중">
<circle class="spinner-track" cx="24" cy="24" r="20" />
<circle class="spinner-head" cx="24" cy="24" r="20" />
</svg>
.spinner {
color: #0f766e;
animation: spin 900ms linear infinite;
}
.spinner-track,
.spinner-head {
fill: none;
stroke-width: 4;
}
.spinner-track {
stroke: #d0d5dd;
}
.spinner-head {
stroke: currentColor;
stroke-linecap: round;
stroke-dasharray: 80 45;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
}
}
동작 설계를 더 깊게 보고 싶다면 Claude Code CSS 애니메이션 고급 기법을 함께 보면 좋습니다. SVG는 모양, CSS는 상태와 움직임을 담당하게 나누면 유지보수가 쉬워집니다.
작은 SVG 차트 생성
간단한 막대 그래프는 SVG 문자열로 만들 수 있습니다. 다만 외부 데이터가<text>에 들어간다면 XML 이스케이프가 필요합니다.
type BarDatum = {
label: string;
value: number;
};
function escapeXml(value: string): string {
return value.replace(/[<>&"']/g, (char) => {
const entities: Record<string, string> = {
"<": "<",
">": ">",
"&": "&",
'"': """,
"'": "'",
};
return entities[char];
});
}
export function createMiniBarChart(data: BarDatum[]): string {
const width = 420;
const height = 180;
const padding = 32;
const gap = 12;
const maxValue = Math.max(...data.map((item) => item.value), 1);
const barWidth = (width - padding * 2 - gap * (data.length - 1)) / data.length;
const bars = data
.map((item, index) => {
const barHeight = (item.value / maxValue) * 100;
const x = padding + index * (barWidth + gap);
const y = height - padding - barHeight;
return `
<rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" rx="6" fill="currentColor" />
<text x="${x + barWidth / 2}" y="${height - 10}" text-anchor="middle" font-size="12">
${escapeXml(item.label)}
</text>`;
})
.join("");
return `<svg viewBox="0 0 ${width} ${height}" role="img" aria-label="월별 문의 수" xmlns="http://www.w3.org/2000/svg">
<g color="#0f766e">${bars}</g>
</svg>`;
}
이 방식은 글 안의 작은 비교, 관리자 화면의 요약, 랜딩 페이지의 간단한 실적 표시에 적합합니다. 축, 범례, 툴팁, 줌, 대량 데이터가 필요하면 전용 차트 라이브러리를 선택해야 합니다.
SVGO로 최적화
디자인 도구에서 내보낸 SVG에는 메타데이터, 불필요한 속성, 너무 긴 숫자가 남아 있을 수 있습니다. SVGO는 이를 정리하는 도구입니다. 처음에는 보수적인 설정으로 시작하고 diff를 눈으로 확인하세요.
// svgo.config.mjs
export default {
multipass: true,
plugins: [
{
name: "preset-default",
params: {
overrides: {
cleanupIds: false
}
}
},
"removeDimensions",
{
name: "removeAttrs",
params: {
attrs: ["data-name"]
}
}
]
};
{
"scripts": {
"svg:optimize": "svgo --config svgo.config.mjs --folder src/assets/icons"
},
"devDependencies": {
"svgo": "^4.0.0"
}
}
SVGO 문서에 따르면removeDimensions는 최상위svg의width와height를 제거하고 필요하면viewBox로 대체합니다. 반면removeViewBox는 스케일링을 막을 수 있으므로 반응형 아이콘에는 기본 선택으로 두지 않는 편이 좋습니다.
사용 사례와 실수
사용 사례는 최소 네 가지가 있습니다. 첫째, 검색, 닫기, 저장, 경고, 외부 링크 같은 제품 UI 아이콘 시스템입니다. 둘째, 기술 글 안의 개념도입니다. 셋째, 가격표나 구매 버튼 주변에 놓는 랜딩 페이지 도식입니다. 넷째, 읽기 완료율, CTA 클릭률, 문의 수를 보여 주는 작은 대시보드 차트입니다.
| 실수 | 결과 | 대응 |
|---|---|---|
viewBox 삭제 | 아이콘이 잘리거나 확대축소가 깨짐 | SVGO 설정과 diff에서 보존 |
고정 fill 색상 | 다크 모드와 hover가 깨짐 | currentColor 사용 |
| 의미 있는 아이콘 숨김 | 스크린 리더가 동작을 모름 | 버튼이나 SVG에 이름 부여 |
| 장식 아이콘을 읽힘 | 이름이 중복되어 시끄러움 | aria-hidden="true" 사용 |
| 업로드 SVG를 inline 삽입 | 스크립트와 이벤트 속성 위험 | 신뢰한 SVG만 inline 처리 |
| reduced motion 무시 | 일부 사용자에게 부담 | prefers-reduced-motion 사용 |
MDN은 SVG의<script>요소를 문서화하고 있습니다. 따라서 사용자 업로드 SVG를 단순 이미지 텍스트처럼 취급하지 마세요. Claude Code가 diff를 도와줄 수는 있지만, 대상 폴더 제한과 파일 변경 검토는 반드시 필요합니다. 대량 변경 권한은 Claude Code 보안 문서와 함께 검토하는 편이 좋습니다.
바로 쓸 수 있는 프롬프트
이 저장소에 SVG 아이콘 시스템을 추가해 주세요.
조건:
- viewBox="0 0 24 24"를 유지
- fill 또는 stroke는 currentColor 사용
- 장식 아이콘은 aria-hidden=true
- 단독으로 의미 있는 아이콘은 role=img와 title 사용
- prefers-reduced-motion을 존중하는 로딩 SVG 추가
- viewBox를 삭제하지 않는 SVGO 설정 추가
- 마지막에 위험, 변경 파일, 검증 명령을 보고
성능까지 이어서 점검하려면 Claude Code 성능 최적화를 확인하세요. SVG는 작아 보여도 아이콘 수, 애니메이션, 도식이 누적되면 페이지 무게와 렌더링에 영향을 줍니다.
CTA와 검증 결과
아이콘 규칙, CLAUDE.md, 리뷰 체크리스트, SVGO 설정을 한 번에 정리하고 싶다면 ClaudeCodeLab의교재와 템플릿을 활용할 수 있습니다. 팀 도입이라면Claude Code 교육과 상담에서 실제 저장소 기준으로 설계할 수 있습니다.
Masa의 테스트 UI에서는 고정 색상을currentColor로 옮기자 같은 아이콘을 라이트 모드, 다크 모드, 위험 버튼에서 재사용할 수 있었습니다. 실제로 드러난 버그는 접근성이었습니다. 모든 아이콘에 기계적으로aria-hidden을 붙이면 아이콘 전용 검색 버튼의 이름이 사라졌습니다. 최종적으로는 버튼에 이름을 주고, 장식 아이콘만 숨기는 규칙으로 정리했습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code 권한 세이프티 래더: 통제력을 잃지 않고 allow 넓히기
read-only에서 제한 편집, 검증 명령, deploy 확인까지 권한을 단계적으로 넓히는 방법.
Claude Code Small PR Proof Pack: 작은 PR을 리뷰 가능한 상태로 만드는 증거 세트
Claude Code의 작은 PR에 diff, 검증, 공개 URL, CTA 경로, rollback을 붙이는 실무 체크리스트.
Claude Code 커밋 전 리뷰 게이트: diff, 테스트, 공개 URL, CTA 확인
Claude Code 작업을 커밋하기 전에 diff 범위, build, 공개 URL, Gumroad 링크, 상담 CTA, 테스트 누락과 무관한 파일을 확인하는 방법입니다.