Velog
상태들의 참담한 상태
Figma 컴포넌트의 state 속성은 하나의 enum 안에 서로 다른 차원의 상태가 뒤섞여 있다. 이를 코드처럼 불리언 속성 + 의존 관계 로 분해해야 디자인 시스템이 실제 구현과 맞물린다.
핵심 요약
텍스트 입력의 "상태" 는 사실 다음 여러 차원의 곱이다.
| 차원 | 종류 | 동시 가능? |
|---|---|---|
| 상호작용 | rest / hover / focus / active | 상호배타 (enum) |
| 가용성 | enabled / disabled / readonly | 상호배타 (enum) |
| 검증 | valid / invalid | 불리언 |
| 채움 | empty / filled | 불리언 |
| 시각 옵션 | with-icon, with-helper-text 등 | 각자 불리언 |
Figma 가 state: hover/disabled/error 처럼 모두 한 enum 에 욱여넣으면 다음 두 문제가 생긴다.
- 불가능한 조합 허용:
state=hover안에서 "내가 disabled 일 수도 있나?" 답이 없음 - 가능한 조합 누락:
disabled + error시안이 존재하지 않음 → 개발자는 추측으로 그림
해결의 방향은 상호배타 차원은 enum, 직교 차원은 불리언 prop 으로 분리하는 것. 이는 React/Web Components API 설계와도 1:1 로 맞물린다.
디자인 시스템의 state 는 하나의 축이 아니라 여러 직교 축의 곱이라는 관점이 핵심이다. 코드는 이미 :hover, :focus, :disabled, :invalid 를 독립 셀렉터로 다루는데, 디자인 도구는 "hover/focus/disabled/error" 를 한 enum 으로 묶어버려 불가능한 조합(예: disabled + error)을 만들거나, 반대로 가능한 조합을 누락한다. 좋은 디자인 시스템은 상태를 불리언 분해 + 상호배타 enum 분리 로 모델링한다.
프론트엔드 개발자가 디자인 시스템과 협업할 때 가장 자주 마주치는 통증이 "피그마엔 disabled + error 시안이 없다", "hover 와 focus 를 같이 봐야 하는데 따로 본다" 같은 문제다. 이 글은 그 통증의 구조적 원인을 짚어준다. 즉 면접·실무에서 다음 능력을 보여주려면 핵심:
- 디자이너에게 "이 상태는 enum 이 아니라 불리언이어야 한다" 를 언어화해서 설명
- 컴포넌트 API 를 설계할 때 prop 을 enum 하나로 두지 않고
disabled,loading,invalid처럼 분리 - Storybook/테스트에서 조합 매트릭스를 자동 생성하는 발상
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
상태는 enum 이 아니라 "여러 차원의 곱"
코드의 CSS 의사 클래스는 이미 직교한다.
input:hover { ... }
input:focus { ... }
input:disabled { ... }
input:invalid { ... }
위 네 가지는 동시에 일어날 수 있다(focus + invalid). 그런데 디자인 도구의 state=hover|focus|disabled|error 는 하나의 값만 고를 수 있게 만든다. 결과적으로 현실의 조합을 다 표현하지 못한다.
"상태가 너무 많아지니까 enum 으로 묶자" 라는 단순화. 시안 수는 줄어들지만 불가능 조합 허용 + 가능 조합 누락 으로 실제 구현 단계에서 비용이 폭증한다.
코드 API 와 디자인 속성을 1:1 로 맞추기
React 컴포넌트 props 가 제일 좋은 참고서다.
type InputProps = {
// 상호배타: 가용성 enum
availability?: 'enabled' | 'disabled' | 'readonly';
// 불리언: 직교 차원
invalid?: boolean;
required?: boolean;
hasIcon?: boolean;
// 상호작용은 CSS 가 처리 → prop 아님
};
디자인 도구 속성도 상호배타는 enum, 직교는 불리언 으로 같은 모양이어야 한다. 그러면 "디자인 시안 = 코드 prop 조합" 이 자명해지고, 매트릭스 자동 생성이 가능해진다.
"디자이너 친화적" 이라는 이유로 enum 을 유지하는 것. 단기엔 쉬워 보이지만 PR 단계에서 "이 조합이 시안에 없는데?" 가 반복된다.
상호작용 상태는 디자인 prop 이 아니라 "인터랙션 모드"
hover, focus, active 는 사용자 행동의 결과이지 컴포넌트의 형용사 가 아니다. 즉 prop 이 아니라 변형(variant) 분기로 시안에서만 표시하면 충분하다. 코드 측에서는 CSS 의사 클래스가 자동으로 처리한다.
반면 disabled, readonly, invalid 는 외부에서 강제되는 상태이므로 prop 이며 CSS 도 :disabled[invalid] 같은 결합 셀렉터로 받아야 한다.
디자인 시안의 state=hover 를 그대로 prop 으로 받아 <Input state="hover"> 같은 API 를 만드는 것. 실제 hover 는 마우스가 결정해야 하는데 prop 이 가로챈다.
읽는 순서
- 1이론
CSS 의사 클래스와 React 컴포넌트 props 가 어떻게 "직교 vs 상호배타" 차원으로 분리되는지 정리한다.
state라는 단어가 가리킬 수 있는 차원 4가지 이상을 적어본다. - 2구현
Input컴포넌트를 두 가지 API 로 만들어본다. (a) 단일stateenum 버전 (b)availabilityenum +invalid/requiredboolean 버전. 같은 시나리오 5개를 두 API 로 모두 표현해보고 어느 쪽이 누락되는지 본다. - 3실무
프로젝트의 디자인 시스템에서
Button,Input,Select의 props 를 점검해 enum 으로 묶인 직교 차원을 찾아낸다. 1개라도 발견되면 작은 PR 로 분해한다. - 4설명
디자이너 동료에게 "왜 hover 와 disabled 가 같은 enum 에 들어가면 안 되는지" 를 5분 안에 코드 예시 없이 말로 설명한다.
면접 연결 질문
[감점 답변] "속성이 많아진다". [좋은 답변] 두 종류 비용을 분리한다. (1) 불가능한 조합 허용: state 가 enum 이라 disabled + error 같은 동시 상태를 표현할 수 없거나, 강제로 우선순위를 정해야 함. (2) 가능한 조합 누락: 시안에 없는 조합이 PR 단계에서 발견 — 개발자가 추측으로 그림. 결국 직교 차원은 boolean, 상호배타는 enum 으로 분해해야 한다.
[감점 답변] "커스터마이즈 가능하니 노출하자". [좋은 답변] 노출하지 않는다. 이 상태들은 사용자 입력의 결과라서 CSS 의사 클래스가 처리하는 게 자연스럽다. prop 으로 받으면 진짜 hover/focus 를 가로채서 테스트·접근성에서 문제가 생긴다. 단 시안에는 표시돼야 하므로 Storybook 의 pseudo 플러그인 등으로 별도 노출.
[감점 답변] "임의로 그린다". [좋은 답변] (1) 누락이라고 단정하지 않고 "이 조합이 product 상 가능한가" 부터 묻는다. 가능한데 시안만 빠진 거라면 (2) 상태 매트릭스(2D 표) 를 공유해 가능 조합/불가능 조합을 디자이너와 같이 정한다. 이후 (3) 시안 누락분을 채우거나, 불가능한 조합 으로 합의되면 코드에서 assertNever 같은 가드로 막는다.
자기 점검
모든 상태를 한 차원으로 보는 것. 실제로는 상호작용·가용성·검증·채움 등 여러 직교 차원이 동시에 존재한다.
"덜 표현되는 것뿐 큰 문제는 없다" 라는 인식. 실제로는 런타임 분기 폭증 과 디자인-구현 간 리뷰 비용 으로 누적된다.