2024년 7월에 디자인 시스템 개발자로 업무를 시작했습니다. 당시 팀 내에서는 기존 디자인 시스템의 리뉴얼 작업이 한창이었습니다. 운이 좋게도 팀이 지향하던 목표와 제가 겪어온 경험들이 얼추 맞아 떨어져서, 많이 기여할 수 있었던 것 같습니다.
1년 7개월 정도 시간이 흐른 지금, 저는 운이 좋게도 디자인 시스템 팀을 이끄는 리더 역할을 맡게 되었습니다. 그리고 리더로서 디자인 시스템이 초기에 목표했던대로 잘 나아가고 있는지, 실제 사용자들은 어떻게 느끼고 있는지를 객관적으로 확인해보고 싶었습니다. 그래서 전사적으로 디자인 시스템 만족도에 대한 설문조사를 진행했고, 사내 구성원들을 대상으로 디자인 시스템의 성장에 대해 발표를 하며 회고하는 시간을 갖기도 했습니다.
오늘은 그 과정에서 느꼈던 점들을 바탕으로, 제가 개발하고 있는 디자인 시스템의 과거와 현재, 그리고 앞으로의 방향성에 대해서 이야기해보려 합니다.
디자인 시스템 리뉴얼
디자인 시스템은 사용자들에게 최적의 경험을 일관되게 제공하기 위해 일관성에 높은 가치를 둡니다. 하지만 언제나 예외는 존재하고, 예외 상황에 쉽고 빠르게 대응할 수 있도록 적당한 수준의 자유도를 제공하는 것도 굉장히 중요합니다. 어느 것에 더 높은 가치를 둘 것인지는 조직의 상황에 따라 유동적으로 결정되어야 합니다.
제가 팀에 합류했을 당시는 비즈니스의 성장에 따라 제품이 늘어나는 시기였고, 그런 상황에서 일관성에 치중되어 있던 기존 디자인 시스템은 업무 흐름에 병목이 되는 경우가 잦았습니다. 제품 개발자들은 새로운 기능이 필요할 때마다 디자인 시스템 팀에 기능을 요청한 뒤 대응을 기다려야 헀습니다. 디자인 시스템 팀 역시 적절한 커스텀 수단이 없으니 기능 추가 요청을 수용할 수밖에 없었고, 결국 디자인 시스템의 체계가 점점 무너지기도 했습니다.
우리는 문제를 해결하기 위해 다양한 방법을 고민했습니다. 그 결과, 훨씬 더 높은 자유도를 보장하기 위해, 사용자가 필요한 기능을 직접 추가하거나 변경할 수 있는 합성 컴포넌트 패턴을 도입하기로 결정했습니다.
합성 컴포넌트 패턴
막상 결정을 하고 나니, 고민할 것들이 산더미였습니다. 그 중, 가장 어려웠던 것은 피그마와의 일치입니다. 디자인 시스템은 개발자와 디자이너 간의 공용 언어라는 점에서, 하나의 기능이나 형태를 칭하는 합의된 용어가 필요합니다. 사실 용어를 정하는 것 자체는 어렵지 않지만, 피그마와 리액트라는 서로 다른 도구로 인해 발생하는 불일치가 치명적이었습니다.
1) 슬롯
단적인 예로, 리액트에서는 children을 통해 자식 요소를 자유롭게 주입할 수 있지만, 피그마에서는 미리 정의해둔 시안을 상황에 따라 선택하는 방식이기 때문에 자유롭게 컴포넌트를 조합하는 게 어려웠습니다.
그래서 컴포넌트 내에 원하는 컴포넌트를 추가할 수 있는 프레임을 두는 방식을 이용하여 직접 슬롯을 구현하여 사용했습니다. 현재는 피그마에서 공식적으로 슬롯 기능을 지원하지만, 제 기준에 이 방법은 나름 획기적인 시도였다고 생각합니다.
2) 서브 컴포넌트
합성 컴포넌트 패턴을 도입하면서 여러가지 서브 컴포넌트가 생겨났습니다. 개발자들에게는 크게 낯설지 않은 방법이었지만, 디자이너들에게 이런 방식은 다소 생소한 방식이었습니다. 그래서 무턱대고 도입을 한다면 디자이너들의 업무 효율성이 크게 저하했을 것입니다. 그렇다고 각자의 방식을 채택하자니, 시안을 코드로 옮기는 개발자들은 모든 컴포넌트의 사용법을 완벽하게 알고 있어야 한다는 문제가 있었습니다.
이 문제를 해결하기 위해 피그마에서는 단일 컴포넌트로 사용하되, 컴포넌트를 구성하는 프레임들의 명칭을 서브 컴포넌트의 이름과 완전히 통일시켰습니다. 이렇게 했을 때, 디자이너들은 본인들의 방식으로 컴포넌트를 쉽게 가져다 사용할 수 있고, 개발자들은 피그마에서의 프레임 명칭만 보고서 서브 컴포넌트를 호출하여 사용할 수 있기 때문에, 학습 비용도 굉장히 저렴했습니다.
프리셋과 슬롯 패턴
이렇게 합성 컴포넌트 패턴으로 커스텀 효율성을 개선하고 의사소통 문제까지 해결했지만, 이제는 또 다른 문제가 보이기 시작했습니다. 매번 서브 컴포넌트를 조합해야 하는 것이 너무 번거롭다는 것입니다. Select 컴포넌트 하나를 사용하기 위해 Trigger, TriggerIcon, Portal, Positioner, Popup, Item, ItemIcon 등, 꽤 많은 수의 컴포넌트를 조합해야 했습니다. 이 방법은 번거롭다는 문제 뿐만 아니라, 서브 컴포넌트들의 조합 레시피를 모두 알고 있어야 하기 때문에 별도의 학습을 요구하기도 했어요.
이 문제는 자주 사용되는 조합을 미리 묶어둔 프리셋 컴포넌트를 제공하여 해결했습니다. 예를 들어, Select 컴포넌트의 팝업을 띄울 때 Portal, Positioner, Popup 컴포넌트가 항상 사용되어야 하는 구조라면, 이걸 한 번에 사용할 수 있는 EasyPopup 같은 요소를 제공하는 것입니다.
// EasyPopup
<Select.Portal>
<Select.Positioner>
<Select.Popup>{children}</Select.Popup>
</Select.Positioner>
</Select.Portal>
// 사용처
<Select.Root>
<Select.Trigger />
<Select.EasyPopup />
</Select.Root>여기에 추가로, 프리셋 컴포넌트를 이용하면서도 구현체에 내재된 컴포넌트의 속성을 손쉽게 커스텀할 수 있도록 슬롯 속성을 추가해두었습니다.
<Select.Root>
<Select.Trigger />
<Select.EasyPopup portal={} positioner={} />
</Select.Root>이로써 기존의 폐쇄적이고 병목을 유발하던 구조에서 벗어나, 사용자의 요구사항에 맞춰 손쉽게 커스텀할 수 있는 유연한 구조로 완전히 탈바꿈 할 수 있었습니다.
결과적으로 시스템 팀에 들어오는 기능 추가 문의는 현저히 줄어들었고, 실제로 내부 설문조사의 '기존 대비 커스텀 자유도 만족도' 항목에서 80% 이상의 긍정 답변을 얻기도 했습니다.
아쉬운 점
이로써 기존의 폐쇄적이고 병목을 유발하던 컴포넌트 인터페이스에서 벗어나, 요구사항에 맞춰 유연하게 커스텀할 수 있는 구조로 완전히 탈바꿈할 수 있었습니다. 하지만 개인적으로는 몇 가지 아쉬운 지점들도 남아있습니다.
1. 인터페이스의 파편화
가장 큰 아쉬움은 컴포넌트 활용 방식이 필요 이상으로 다양하다는 점입니다. 현재 디자인 시스템은 커스텀 시 서브 컴포넌트를 직접 재조립하는 방식과 슬롯 속성을 이용하는 방식을 모두 허용하고 있습니다. 사용 방법이 다양하면 자유도가 높아 보일 수 있지만, 실제 협업 과정에서는 다음과 같은 우려 사항이 존재합니다.
- 일관성 없는 코드 베이스: 개발자마다 선호하는 커스텀 방식이 다르기 때문에 프로젝트 전체의 코드 스타일을 하나로 통합하기 어려워집니다. 이는 현재 동료와의 협업뿐만 아니라, 향후 새로운 팀원이 합류했을 때 코드의 의도를 파악하는 데 진입장벽이 될 수 있습니다.
- 선택 과부하(Choice Overload)로 인한 피로도: 심리학의 선택 과부하 이론에 따르면, 선택지가 늘어날수록 사용자는 결정에 더 큰 어려움을 겪습니다. 비록 선택지가 두 가지뿐이라 할지라도, 불필요한 고민을 유발하는 구조는 지양해야 합니다.
결국 컴포넌트 조합 방식과 슬롯 방식은 각각의 장단점이 뚜렷한 패턴입니다. 따라서 우리 팀의 상황에 가장 적합한 표준 인터페이스 패턴을 하나로 정의하는 것이 중요합니다. 그래야만 컴포넌트 사용 시의 예측 가능성을 높이고, 개발자의 피로도는 낮추면서 작업 효율성을 극대화할 수 있기 때문입니다.
2. 공용 언어의 실종
초기 설계 당시에는 여러 제품에서 공용으로 사용할 수 있는 시스템을 제작하는 것이 최우선 과제였습니다. 그리고 그 과정에서 커스텀의 자율성을 확보하는 것이 중요한 요구사항 중 하나였습니다. 따라서 우리는 컴포넌트의 기본 기능을 최대한 보수적으로 설정하고, 제품별로 상이할 수 있는 기능들은 사용자가 직접 커스텀하도록 장려했습니다. 당시에는 이것이 여러 제품에 유연하게 대응할 수 있는 합리적인 방안이라 생각했지만, 운영 결과 예상치 못한 부작용들이 나타났습니다.
가장 큰 문제는 UX의 파편화와 커뮤니케이션 비용의 증가입니다.
예를 들어, 현재의 TextInput은 순수 input 요소의 핵심 기능만 포함하고 있습니다. 입력된 내용의 초기화 버튼이나 검색 돋보기 아이콘 같은 보편적인 요소들까지 사용자의 책임으로 넘기다 보니, 개발자는 매번 동일한 기능을 위해 별도 스타일을 정의해야 했습니다. 또한, 해당 기능의 세부 동작을 확인하기 위해 디자이너와 개발자가 불필요한 소통 과정을 거쳐야 하는 상황도 빈번해졌습니다. 이 과정에서 약속된 표준이자 공용 언어로서의 디자인 시스템의 역할이 퇴색된다고 느끼기도 했습니다.
앞으로의 방향성
이러한 문제들을 해결하기 위해, 디자인 시스템의 본질인 일관성을 강화하는 방향으로 나아가려 합니다.
우선 파편화된 컴포넌트의 인터페이스를 하나로 통합하는 것이 중요합니다. 이미 제공하던 인터페이스를 제거하는 것은 브레이킹 체인지를 유발하기 때문에 다소 조심스럽지만, 안정적인 패키지 관리 및 마이그레이션 전략에 유의하여 점진적으로 마이그레이션 하면 사용자들의 피로감을 최소화 할 수 있을 것입니다.
또 보편적으로 사용되는 기능을 서브 컴포넌트 형태로 추가 제공하여 UX 일관성을 강화할 필요가 있습니다. 예를 들어 TextInput.Reset과 같은 기능을 제공한다면, 사용자는 추가 구현 없이도 기능을 즉시 사용할 수 있고, 컴포넌트의 배치나 사용 여부에 대한 자유도는 여전히 보장받을 수 있습니다.
디자인 시스템은 전사적으로 공유하는 가이드라인을 가장 효율적으로 구현할 수 있는 도구여야 합니다. 따라서 모든 컴포넌트가 표준 가이드를 만족할 수 있도록 하위 컴포넌트를 보강하고 부족한 부분을 개선한다면, 사용자들은 더 효율적으로 예측 가능하고 일관된 제품 환경을 구축할 수 있을 것입니다.
맺음
글의 도입부에서 언급했듯이, 디자인 시스템에서 일관성과 유연성은 서로 상충하면서도 그 어느것도 놓칠 수 없는 중요한 가치입니다. 그렇기 때문에 어느 한쪽으로 치우치지 않도록 끊임없이 점검해야 합니다. 우리의 경우에도 유연성에 집중하다 보니 일관성이 저하되는 문제를 마주했고, 이제는 균형점을 되찾는 방향으로 목표를 잡고 있습니다.
이번 경험은 디자인 시스템의 본질이 무엇인지 다시 한 번 깨닫게 해주는 시간이었습니다. 그리고 이를 바탕으로 사용자들에게 더 나은 경험과 사용성을 제공하는 디자인 시스템을 만들기 위해 꾸준히 노력할 예정입니다.