#01 Button System
사실 디자인 시스템에 대한 이야기는 귀에 딱지가 앉도록 많이 듣고 보셨을 것입니다. 하지만 사실 남들이 해야 한다고 이야기하는 것과 내가 하는 것 그리고 그것이 회사의 워크 프로세스로 정착하는 것까지는 아주 많은 갭이 존재합니다. 그래서 저는 이 글에서 작은 규모의 스타트업에서 어떻게 디자인과 프론트 코드를 동기화시켜서 디자인 시스템을 만들어가고 있는지에 관해 이야기 해보려 합니다. 난립하는 컴포넌트들 사이에서 어려움을 겪고 있는 스타트업의 디자이너와 개발자분들께 조금이나마 도움이되는 글이었으면 좋겠습니다.
저희 팀은 클라이언트 팀과 디자인팀의 업무 효율을 높이고 유저가 서비스에서 통일된 경험을 느낄 수 있게 하기 위해 디자인 시스템 구축을 시작했습니다. 그리고 그 시작에 해당하는 미션을 버튼 컴포넌트 통일화(Button System Focus)로 잡고 프로젝트를 진행했습니다.
들어가기 전
- 저희는 글로벌 학술 논문 검색 웹 서비스(Scinapse)를 만들고 있어서 언어는 영어이고, PC 사용자가 주를 이루고 있습니다.
- 팀에 Product Designer는 두 명, Client(Front End) Developer도 두 명이 있습니다.
- 클라이언트 개발은 React + Typescript를 이용합니다.
어떤 상황이었는데?
디자이너 그리고 개발자 모두 버튼 및 다른 컴포넌트들의 시스템화를 위해 노력하지 않았던 것은 아닙니다. 디자인 팀은 2년 전쯤부터 버튼 및 폼, 리스트 등 공통으로 많이 사용되는 컴포넌트들을 스케치 심볼로 만들어서 관리했습니다. 하지만 라이브러리 및 파일 동기화가 어렵게 느껴지면서 점점 각자 디자인을 하게 되었고, 완전히 동일하지는 않지만, 암묵적으로 비슷한 스타일의 버튼들을 각자 디자인하기 시작합니다.
클라이언트 팀에서는 디자인된 버튼을 구현하기 위해 공통 scss 파일을 이용해서 같은 스타일을 적용하는 시도와 리액트 컴포넌트로 글로벌로 사용할 버튼들을 만드는 시도를 했습니다. 하지만 각자의 디자이너가 다른 버튼들을 만들어 내고 있었기 때문에 클라이언트 팀은 버튼을 몇 가지 스타일로 정규화하기 힘들었고, 결국에는 디자인에 따라서 일일이 다시 만들게 되었습니다. 이로 인해 버튼들은 점점 다양한 형태로 발산되어가고 있었습니다.
어떻게 해결할까?
이번 개선 프로젝트에서는 프론트팀과 디자인팀의 버튼 컴포넌트에 대한 이해를 동기화시키고 추후 관리가 잘 될 수 있도록 하는 것이 필수라 생각했고 이를 위한 해결 방안을 팀원들과 논의해 보았습니다. 해결책은 디자인 팀이 주도해서 컴포넌트를 설계하고 유지 관리하는 방식이었습니다.
그 이유에 대해서는 저희 팀 프론트개발자 미르님의 한 말씀:
특히, 내 경험상 이러한 디자인 시스템, 컴포넌트 설계, 시각적 효과 정립과 같은 일은 클라이언트 팀이 주도가 되어서는 제대로 정착되기 어렵다. 디자인 팀이 주도가 되어 설계, 적용 할 수 있었을 때, 안정된 시스템으로 동작하기가 훨씬 쉬웠다. 사실상 컴포넌트의 주 유저는 디자이너들 본인들이기 때문에, 스스로 시스템을 설계, 유지, 보수해야 안정적이고 변화가 적은 ‘시스템’을 만들기가 수월하기 때문이라고 생각한다.(물론 개발 사이드에서의 도움은 필수적이다.)
결국 디자이너가 버튼의 스타일을 만들어 내기에 그 생각과 동일한 컴포넌트를 더 잘 만들어 낼 수 있고, 새로운 버튼을 만들어 낼 때도 제약 조건을 잘 알고 있을 것이므로 유지 보수가 훨씬 수월해진다는 이야기 같습니다.
저희 팀은 오랫동안 스타일 관련 QA를 디자이너가 직접 해왔기 때문에 개발 환경이 세팅되어있었고, 현 프로덕트에 대한 코드 구조 이해가 있는 상태였습니다. 따라서 컴포넌트의 스켈레톤 코드를 디자이너가 직접 작성하는 구조가 가능했고, 프론트 팀에 리뷰 받아 고쳐나가는 식으로 컴포넌트를 완성해 나가기로 했습니다.
구현 시작!
버튼 대통합에 실패하지 않는 컴포넌트를 만들기 위해 중심적으로 생각했던 요소들은 다음과 같습니다.
- 기존에 암묵적으로 만들어온 비슷한 스타일들의 버튼을 포용할 수 있어야 한다.
- 버튼의 스타일을 조작 할 수 있는 variation에 제한을 두어 같은 목소리를 낼 수 있어야 한다.
- 적용의 방식이 기존에 버튼을 만드는 방식과 크게 다르지 않게 구현하여 migration과 실제 사용이 쉬워야 한다.
버튼 구조를 잡기 위해서는 디자인 시스템계의 바이블, Google Material Design을 많이 참고하였고, 팀에서 원래 사용하던 꼭 필요한 요소와 상태 그리고 옵션만을 사용하여 코드의 복잡성을 최대한 줄이면서 녹여내기 위해 노력했습니다.
01. 버튼 스타일 구현하기
처음에 버튼을 설계하기 위해 현재 사용되고 있는 버튼들을 참고하여 상태와 스타일을 어떤 식으로 정규화/추상화시킬 것인지 고민해 보았습니다. 주로 사용하고 있던 버튼들의 모습을 잘 살펴보고 뭉쳐보니 버튼의 스타일은 크게 3가지 카테고리의 조합으로 표현할 수 있었습니다.
- Size
small, medium, large 세가지로 분류하였고 버튼 container의 height 값, 폰트 크기, 아이콘 크기, 요소간의 여백 크기에 영향을 줍니다.
- Variant
contained, outlined, text 세가지로 분류하였고 버튼의 스타일에 영향을 주는 요소입니다.
- Color
같은 사이즈와 스타일을 가져도 색에 따라 주요 버튼과 보조 버튼등으로 용도를 나누어 사용 할 수 있습니다. 저는 main color로 쓰이는 main blue 와 보조 컬러들로 사용되는 gray, black 세가지로 버튼의 variation을 제한하여 디자인 했습니다.
코드로 구현하기
구현을 하기위해서 각 카테고리에 해당하는 요소를 props 로 지정하여 ClassName에 넣고, CSS 클래스 선택자를 이용해 스타일을 조정했습니다.
사이즈에 따라 다른 스타일은 small button에 대한 코드를 먼저 작성하고 이를 scss의 extend을 이용해서 medium button과 large button에 상속하여 단순 반복 작업을 줄였습니다.
그리고 variant와 color에 따라 바뀌는 버튼 스타일은 클래스 선택자들의 조합으로 background-color와 color 값들을 지정해 주어 표현했습니다.
02. 아이콘 버튼 만들기
하지만 위 props들(size, variant, color)의 조합만으로는 아이콘의 위치와 유무 여부는 넘겨줄 수 없었습니다. 이 또한 props로 넘겨줄 수도 있지만 넘겨줄 props가 너무 많아지고 기존에 버튼을 만들던 모습과 많이 다른 형태로 컴포넌트를 사용해야 했습니다. 저는 이 이슈를 해결하기 위해 props를 새로 받지 않고 props.children 매서드를 사용하여 아이콘 버튼을 구현했습니다.
이렇게 되면 Button 태그 안에 있는 것들이 Button 컴포넌트의 children prop으로 전달되어 스타일을 조정할 수 있으므로 아이콘과 텍스트 요소들의 배치 순서나 아이콘 위치나 존재 여부에 따라서 변화되는 모양의 아이콘 버튼을 만들 수 있습니다.
다음은 그렇게 구현된 버튼의 모습입니다. 아이콘과 텍스트들의 위치만 바꾸어주면 leading 아이콘 버튼이 tailing 아이콘 버튼으로 변합니다. 텍스트나 아이콘 블록을 각각 없애도 지정된 패딩으로 인해 자연스러운 text only 버튼(텍스트만 있는 버튼), icon only 버튼(아이콘만 있는 버튼)이 됩니다.
03. 버튼 상태 표현하기
버튼에는 위와 같이 많은 상태가 존재합니다. 저희는 material design에 언급된 Focused를 제외한 4개의 상태에 Loading 상태를 더해 총 5개의 상태를 정의하고 이를 반영하였습니다.
- enabled : 활성화 되어있는 상태에서 사용자의 인터렉션이 없는 기본 상태
- hover : 마우스를 버튼위에 올려놓은 상태
- pressed : 버튼을 탭하여 누른 상태
- disabled : 클릭(동작)이 불가능한 상태
- loading : 통신시의 로딩 상태
구현을 위해 enabled
스타일을 기본스타일로 지정하고 스타일을 입혀주고, css의 :hover
, :active
와 같이 상태에 따라서 달라지는 pseudo-class selector를 활용하여 배경색, 폰트 컬러 와 같은 스타일 코드를 바꾸어hover
과 pressed
상태를 간단하게 구현했습니다.
disabled
상태는 props(isDisabled=true
)로 값을 받아 버튼 스타일을 만들어 주었던 것과 유사하게 클래스 선택자(class selector)를 이용하여 각각 스타일을 지정해 주었습니다.
이제 loading 상태만 해결하면 됩니다..!
loading
상태를 만들 때는 약간의 이슈가 있었습니다. 버튼이 text의 길이에 따라서 자동으로 변하게 하기위해서 버튼 container의 패딩값으로 버튼의 총 너비가 결정되었는데 loading
상태를 위해 버튼 내부의 요소들(아이콘과 Text)를 스피너로 갈아끼우면 버튼의 총 width가 스피너 크기에 맞춰 줄어들 수 밖에 없었습니다.
이 현상을 해결하기 위해 기존 버튼의 아이콘과 텍스트를 보이지 않게 처리하고 (visibility: 'hidden'
), Container 위에 스피너를 덮어씌워 width가 변하지 않는loading
상태를 만들 수 있었습니다.
04. 버튼 기능 통합하기
저는 <button/> 태그 만을 고려하여 버튼을 제작하였지만, 현재 사용되고 있는 버튼들은 용도에 따라 <a/> 와 <link/> 태그들도 사용하고 있었습니다. 해당 케이스들도 지금까지 만들었던 버튼에 통합하기 위한 작업이 필요했고, 해당 작업은 클라이언트 팀의 도움을 받아 진행했습니다.
이러한 과정을 거쳐 최종적으로 만들어진 버튼의 코드는 다음 github repository에서 자세히 보실 수 있습니다.
실제 제품에 반영 해보자
이제 실제 프로덕트에 적용해 나가면서, 놓친부분은 없는지 테스트 해보아야합니다. 저희는 적용의 첫걸음으로 프로덕트에서 글로벌하게 쓰이면서 고대의 유물처럼 남아있던 가입 모달의 버튼들을 교체해보았습니다. 실제로 이용되던 버튼을 교체해보니 동작이나 스타일 등에서 어색한 점들이 발견되어 사소한 수정과정들을 거쳤습니다. 아래는 교체된 버튼들의 before(좌)/after(우) 모습입니다.
앞으로 만들어갈 페이지의 버튼들을 이 버튼 컴포넌트로 필수 사용할 것이고, 현재 사용되고 있는 여러 버튼들도 차례대로 교체해 나갈 예정입니다.
데모 페이지
위 컴포넌트들을 쉽게 보고 원하는 형태의 버튼의 코드를 바로 복사할 수 있는 데모 페이지도 간단하게 만들어 보았습니다. 옵션을 선택하면 해당 옵션의 조합으로 나오는 버튼이 보여지고, 아래에는 그 버튼에 대해 작성해야하는 코드가 만들어집니다.
데모 페이지 : https://scinapse.io/ui-demo
데모페이지가 아직은 기존 프로덕트에 path만 설정해서 사용하고 있어서 header 네비게이션과 feedback 플로팅 버튼이 남아있지만… 서서히 도메인도 독립하고 발전시켜 나갈 예정입니다.😅
다음은 뭘 해볼까?
버튼 컴포넌트 하나를 정규화 하는데에도 아주 많은 고려요소가 있었던 것 같습니다. 하지만 이렇게 정교하게 설계해 두었으니 서비스를 구현하는데 걸리는 시간과 디자인-클라이언트간 QA 핑퐁 작업이 많은 부분 줄어들것입니다. 그리고 그 변화는 벌써 느껴지기 시작했습니다.
다음 도전 과제는 저희 프로덕트에 가장 많이 사용되고 있고 복잡도가 높은 Paper Item(List)와 Field를 차례로 정리 하는 것 입니다.
사실 그보다 더 우선시 되어야 할 것은 이 버튼들을 스케치로도 옮겨야 하는데, 딱 이시기에 58버전 smart layout 기능이 나와서 많은 도움을 받을 수 있을 것 같습니다.
공통 버튼 컴포넌트를 만들고 사용하기 위해 외부 라이브러리를 쓸 수도 있었는데, 라이브러리를 사용하다 보면 저희 서비스 경험에서 중요한 요소를 강조하는 형태로 변형된 컴포넌트를 만들어야 할 때 pure 컴포넌트를 쓰는 것보다 일이 더 복잡해지는 상황이 발생해왔습니다. 그래서 저희는 팀 규모는 작지만, 우리만의 것을 만들어서 사용해보기로 했고, 그것을 만들어 가는 과정을 기록으로 남겨 보았습니다.
최대한 자세히 이야기를 담아보려 했더니 글이 많이 길어졌네요😂😂😂. 디자인 시스템을 만들어서 사용한 결과물은 많이 봤지만 만들고 반영하는 과정들에 대해서 자세히 다룬 발표나 글은 못 봐서못봐서 아쉬웠는데, 이 글이 조금이나마 도움이 되는 글이었으면 좋겠습니다. 저희도 분명 이상적인 디자인시스템을 만들어나간다고볼 수는 없지만 피드백도 받고 개선해 나가고 싶습니다.
긴 글 읽어주셔서 감사합니다!