할 일 앱 실습 - 디자인

#CSS #React

(이미지 좌측은 내가 짠 코드, 우측은 예제 코드)

자습서의 챕터로 따로 분리되있었던 할 일 앱 실습을 진행했다.

우선 예제 코드를 보지 않고 직접 코드를 작성했는데, 사실 디자인 보다는 로직을 작성하는 데에서 헤맸다.

그런데 코드 작성을 마치고 예제 코드와 비교해보려고하니 디자인 단(html, css) 부터 다른 점이 많아, 내 코드와 예제 코드가 어떤 부분에서 차이가 나는지 디자인 부터 분석을 시작했다.

나는 Tailwind CSS로 작업했고, 예제 코드는 global CSS로 작성이 되어있어서 서로 비교하기 쉽게 나도 global CSS로 다시 코드를 작성해서 비교해보았다.


1. 네이밍 *

비교하자마자 바로 차이가 보였던건 클래스 네이밍이였다.

나는 클래스 네이밍을 이런식으로 작성했다.

todo_title, todo_subtitle
todo_add_area, todo_add_input, todo_add_button...

예제 코드에서의 네이밍은 이랬다.

todo__title, todo__subtitle
todo__editor, todo__input, todo__button...

가장 처음에 든 생각은 ‘왜 언더바를 두 개씩 쓰지?’ 였다. 그 생각을 토대로 찾아보니, 언더바를 두 개 사용해서 요소(Element)를 구분하는 것은 BEM(Block Element Modifier) 방법론을 사용한 것이였다.

BEM 방식을 간단하게 말하자면, 해당 위치(Block)에 어떤 요소(Element)가 어떤 상태(Modifier)인지 나타내는 네이밍 규칙이다. 한 단어의 공백을 나타낼 때는 바(-)를 사용하고, 요소를 나타낼 때는 요소 앞에 언더 바 두 개(__), 상태를 나타낼 때는 바를 두 개(—) 사용한다.

이 때 중요한건 위치, 요소, 상태는 각각 하나씩만 사용하는 것이다. 예를 들어 요소 안에 다른 요소를 나타내서는 안된다. (ex. todo__editor__input) 왜냐하면 나중에 계층 관계를 수정할 일이 있을 때마다 클래스 이름을 바꿔야하는 불편함이 생기기 때문이다. 그래서 CSS 네이밍은 될 수 있으면 단순하게 하는 것이 좋다고 한다.

2. div vs ul *

할 일 리스트를 작성하는 방식에서도 차이가 있었다. 나는 리스트를 div로 각각 만들었고, 예제에서는 ul과 li를 활용하여 만들었다.

결론부터 말하자면 ul과 li를 활용해 만드는 방식이 시멘틱 코드 측면에서 더 권장되었다. 시각이 불편한 분들이 화면을 읽어 주는 기능을 쓸 때, 브라우저가 해당 블럭이 목록인걸 알고 더 알맞은 안내를 해 준다고 한다. 리스트가 불렛이 없고, 작은 카드 형식으로 되어있어서 간과했는데, 어떤 형태이든 목록을 만들 때는 ul, ol과 li를 사용해야겠다.

참고로 리스트 불렛을 없애는 속성은 list-style: none; 이다. display 속성을 사용하면 list-style: none; 속성이 없어도 불렛이 사라질 수 있지만, 안정된 코드를 위해 속성을 적어주는 것이 좋다고 한다.

3. 레이아웃 그룹핑

예제 코드에서는 체크박스와 라벨을 묶어 todo__checkbox-group으로, 수정 버튼과 삭제 버튼을 묶어 todo__button-group으로 만들었다. 나는 따로 그룹핑을 하지 않고 gap과 margin을 이용하여 사이의 거리를 조정했다. 어떤 방식이 더 좋을지는 상황마다 다르겠지만, 이 앱을 만들 때는 버튼을 재사용하는 부분이 있어 그룹으로 묶는 방식이 더 좋은 것 같다. 그리고 거리를 조정할 때는 margin 보다는 gap을 쓰는 것이 더 편하고 눈에 잘 들어온다.

4. flex 레이아웃 *

CSS를 작성하면서 계속 막혔던 부분이 있었는데, 요소를 너비에 꽉 차게 하는 부분이였다.
여기에서는 리스트에 있는 label과 input을 다른 요소들 가로 너비를 제외한 나머지 너비에 가득 채워야했다.

이런 경우에는 **width: 100%;**를 주거나, **flex: 1;**을 주면 너비가 꽉 차게 채워진다. 이 중에서도 flex: 1;을 사용하는 것이 좀 더 유연한 방법인데, width: 100%;을 이용하면 다른 요소의 너비는 신경쓰지 않아 다른 요소들이 레이아웃 밖으로 튀어나갈 수도 있기 때문이다.

flex: 1;을 사용하면 다른 요소의 너비나 높이가 min-width, min-height까지 줄어드는 상황이 생길 수 있다. 이 때는 flex-shrink 속성을 사용한다. flex-shrink는 flex 레이아웃 안에서 크기 조절이 일어날 때, 크기가 줄어드는 비율을 설정하는 속성이다. flex-shrink의 값을 0으로 두면 크기가 줄어들지 않고, 1로 두면 기본 값으로 줄어들며, 2로 두면 2배 더 많이 줄어든다고 한다.

예제 코드에서는 flex를 사용하지 않고 width에 고정값을 두었는데, 이 부분은 유연하게 작성하는 것이 좋을 것 같아 나는 flex 값을 주어 너비를 조정했다.

5. 선택자 *

예제 코드에는 클래스 선택자 외에 다른 선택자를 활용하여 스타일을 꾸민 경우가 많았다.

* (전체 선택자)

화면의 전체 스타일을 꾸며주는 선택자이다. 여기서 box-sizing, margin, padding, font-family를 설정했다.

margin과 padding은 모두 0으로 둬서 브라우저의 기본 margin과 padding을 없앴고, font-family는 1순위를 Inter, 2순위를 serif로 둬서 Inter 웹 폰트 다운을 시도한 후 실패하면 serif 체로 스타일이 적용되게 했다.

box-sizing은 처음 보는 속성이였는데, 값으로 content-box나 border-box를 줄 수 있었다. 브라우저의 기본 값은 content-box로, width, height에 margin, padding이 따로 작용하는 형태이다. 예를들어 width를 100px으로 두고 좌우 margin을 각각 10px으로 두면, 총 너비는 120px이 된다. border-boxwidth, height에 margin, padding이 속해져서 작용한다. 예를들어 width를 100px으로 두고 좌우 margin을 각각 10px으로 두어도, 총 너비는 100px로 유지된다.

보통 CSS를 작성할 때는 계산이 편한 border-box를 선호해서 전체 선택자로 설정해 두는 경우가 많다고 한다.

body (타입 선택자)

html의 body를 꾸며주는 선택자이다. Tailwind CSS에서는 가장 바깥의 div에 레이아웃 스타일을 줬는데, global CSS에서는 body에 레이아웃 스타일을 주는 것 같다. 여기서 하나 아차 싶었던건 color 였다. 기본이 되는 font 색상을 body에 전체적으로 먹이는 것이 더 편한데, 나는 글자 색을 일일이 다르게 기입하고 있어서 비효율적이였다. 다음부터는 화면을 보고 기준이 되는 글자색이 있으면 미리 전체에 적용시켜 두어야겠다.

자손 선택자

예제에 클래스 선택자 옆에 label이 붙어있는 코드가 있었다.

.todo__item--complete label { 
  text-decoration: line-through;
}

이 label은 자손 선택자로, 같이 쓰여진 클래스 안에 있는 모든 label에 해당되는 스타일을 적용할 때 사용한다. 기호를 사용하여 상위 하나만 적용하는 등 범위를 조절할 수도 있다고 한다.

의사 클래스와 의사 요소

클래스 선택자 옆에 콜론이 하나(:)인 경우가 있고 두 개(::)인 경우가 있어 궁금한 마음에 찾아보았다. 콜론이 하나인 것은 의사 클래스인데, 클래스의 특정 상태를 나타낸다. (ex. .todo__input:checked) 콜론이 두 개인 것은 의사 요소인데, 클래스의 특정 부분을 나타낸다. (ex. .todo__input::placeholder) 원래는 두 요소 모두 똑같이 콜론 하나를 썼으나, css가 버전이 업데이트 되면서 둘을 구분 짓기 위해 콜론 두 개 문법이 생겼다고 한다. 콜론 하나로도 동작할 수는 있지만 안정성을 위해 클래스의 동작인 경우 콜론 두 개를 사용하자.

6. 체크박스 *

예제 코드에서는 체크박스에 커스텀 이미지를 넣었다. 커스텀 이미지를 넣기 위해서는 우선 브라우저에서 제공하는 기본 체크 박스 스타일을 없애야하는데, appearance: none;을 적용하면 기본 스타일이 사라진다. 그리고 체크된 상태에서 배경 이미지를 넣고, 배경 이미지가 반복되지 않고 중앙에 정렬되도록 코드를 넣어준다.

.todo__checkbox:checked{
  background-image: url(./assets/check.svg);
  background-repeat: no-repeat;
  background-position: center;
}

svg가 파일이 아닌 코드로 주어지는 경우에 체크박스 이미지로 적용하려면 반드시 svg 파일로 저장해서 사용해야하는지 궁금해서 찾아보았는데, Data URI 형식을 사용하면 CSS 안에 직접 넣을 수 있다고 한다.

.todo__checkbox:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' ... %3E%3C/svg%3E");
}

하지만 보통 가독성 때문에 파일로 관리하거나, 컴포넌트로 분리하여 사용하는 경우가 많다.

7. 카드 *

할 일 리스트를 담는 카드에서 내가 생각하지 못한게 두 가지 있었다.

하나는 카드의 배경색을 주는 것이다. 배경색이 하얀색이라 인지를 못하고 있었는데, 만약 배경색이 다른 페이지에서 클래스를 재사용할 경우, 투명하게 비쳐보일 여지가 있다. 따라서 하얀색 배경이라도 배경을 잘 적용하자.

두 번째는 카드의 세로 스크롤을 만드는 것이다. 내가 짠 코드는 리스트가 늘어날 수록 페이지가 계속 길어지는 형태인데, 그것 보다는 max-height와 overflow를 설정하여 카드에 세로 스크롤을 만드는 편이 더 사용성이 좋다. overflow는 auto로 값을 넣어주면 된다.

8. 기타

svg 코드 사용

html 태그 안에 svg 코드를 붙여넣으면 된다. 자동으로 이미지가 가운데 정렬 되지는 않으므로, 가운데로 값을 정리하고 싶으면 flex, justify-contents, align-items 삼총사를 사용한다.

htmlFor는 id와 연결

계속 헷갈리는데 htmlFor는 name이 아닌 id와 연결된다. name과 연결되는 것은 Form을 제출할 때이니 참고하자.

border 한 줄 설정

border 크기, 색상, 스타일은 한 줄에 한 번에 설정할 수 있다.

  border-style: solid;
  border-width: 1px;
  border-color: #4f4f4f;

  border: 1px solid #4f4f4f;

버튼 좌우 패딩

버튼은 좌우 패딩을 주지 않고 자체 width를 설정해주면 알아서 텍스트가 가운데 정렬이 된다.

<p> vs <span>

글자를 넣을 때는 태그로 한 번 감싸는 것이 좋다. 글자를 감싸는 태그 중 <p>는 한 줄을 꽉 채우는 블록 타입이고, <span>은 해당 글자 너비만 차지하는 inline 타입이다. <p>안에는 <span>을 사용할 수 있지만, <span>안에는 <p>를 사용할 수 없다.