개발 slecs

그리드·여백·테두리 중복 정리로 UI 시각 품질 개선

목차

UI 작업은 쉽다고 생각하기 쉽다. 특히 "스타일 정리"라는 말이 들어가면 더욱. 근데 막상 파고들면 그리드 하나가 틀어진 게 레이아웃 전체를 흔들고, 테두리 하나가 겹쳐 있는 게 특정 해상도에서만 이상하게 보이는 식으로 얽혀 있어서 단순하지 않다.

이번 작업은 화면 여러 곳에서 누적된 시각적 부채를 한 번에 정리한 세션이었음. 뷰/스타일 파일 4개, 스크립트 1개를 건드렸고, 크게는 그리드 공백 정규화, 테두리 중복 제거, bar 요소 겹침 수정, 색상 톤 통일 네 가지였다.

왜 이 작업이 필요했나

시각적 부채는 기능 부채보다 훨씬 빠르게 쌓인다. 기능은 "없으면 없는 거"라서 눈에 바로 띄는데, 스타일 문제는 "뭔가 어색한데 뭔지 모르겠는" 상태로 오래 방치된다. 화면이 10개, 20개 넘어가면 각 화면마다 조금씩 다른 여백, 조금씩 다른 테두리 굵기, 조금씩 다른 색상이 누적되고, 어느 시점이 되면 "디자인 시스템"이라는 게 사실상 유명무실해진다.

이번에 특히 문제였던 건 그리드 공백이 화면 단위로 제각각이었던 것. 어떤 화면은 gap: 12px, 어떤 화면은 gap: 16px, 또 어떤 화면은 margin으로 간격을 줬는데 이게 반응형 레이아웃에서 의도치 않게 공백이 더해지는 원인이 됐다. 테두리 중복은 더 고전적인 문제인데, 부모 컨테이너에 border-bottom이 있는데 자식 요소에도 border-top이 있어서 사이 간격이 눈에 띄게 두껍게 보이는 케이스가 있었다.

bar 요소 겹침도 마찬가지. 상태 표시용 bar를 absolute positioning으로 쌓았는데, 다른 요소의 relative 기준점이 맞지 않아서 특정 해상도에서 살짝 잘리거나 겹쳤음. 이걸 뒤늦게 발견한 게 모바일 기기에서 직접 확인했을 때였다.

가독성 원칙은 생각보다 단순하다. 숫자 데이터나 핵심 정보는 크고 굵게, 보조 레이블은 작고 흐리게. WCAG AA 기준 텍스트-배경 대비 4.5:1을 의식하기 시작하면 "배경이 회색인데 텍스트도 회색"인 조합이 얼마나 많은지 보이기 시작함. 색상 톤 통일 작업이 이번에 시간을 꽤 잡아먹었는데, 이걸 제대로 하지 않으면 다음에 또 같은 작업을 반복하게 된다.

CSS 방어 전략 - specificity와 변수 활용

스타일 정리 과정에서 가장 먼저 한 게 !important 제거였다. 레거시 코드에서 !important는 대개 "어디선가 덮어쓰이는데 이유를 모르겠어서 그냥 막았다"는 흔적인 경우가 많다. 이렇게 되면 나중에 의도적으로 스타일을 바꾸고 싶을 때 또 !important를 써야 하는 악순환이 생긴다.

대신 specificity를 높이는 방향으로 선택자를 정리했음.

/* 기존 - !important 의존 */
.bar {
  border: none !important;
  margin: 0 !important;
}

/* 수정 - 컨텍스트 선택자로 specificity 확보 */
.grid-container .bar {
  border: none;
  margin: 0;
}

색상은 CSS 변수로 일원화했다. 같은 색상값이 파일 세 곳에 흩어져 있던 걸 --color-border-default, --color-bg-surface 같은 변수로 묶었음. 이렇게 해두면 나중에 다크 모드 대응이나 테마 변경 때 변수 한 줄만 바꾸면 된다.

/* 변경 전 */
.card    { background: #f4f5f7; border: 1px solid #dfe1e6; }
.row     { background: #f4f5f7; }
.sidebar { border-right: 1px solid #dfe1e6; }

/* 변경 후 */
:root {
  --color-bg-surface: #f4f5f7;
  --color-border-default: #dfe1e6;
}
.card    { background: var(--color-bg-surface); border: 1px solid var(--color-border-default); }
.row     { background: var(--color-bg-surface); }
.sidebar { border-right: 1px solid var(--color-border-default); }

반응형 부분은 SCSS mixin으로 묶었음. 브레이크포인트가 파일마다 @media (max-width: 768px) 하드코딩으로 반복되던 걸 정리했다.

@mixin mobile  { @media (max-width: 767px)                      { @content; } }
@mixin tablet  { @media (min-width: 768px) and (max-width: 1023px) { @content; } }

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-md);

  @include mobile {
    grid-template-columns: 1fr;
    gap: var(--spacing-sm);
  }
}

미리 이렇게 되어 있으면 새 화면 추가할 때 브레이크포인트 값을 복붙하다가 숫자 하나 다르게 쓰는 실수가 없고, 나중에 값을 바꿔야 할 때 mixin 한 곳만 건드리면 된다. 반응형 브레이크포인트 mixin은 쓰면 쓸수록 왜 진작 안 만들었나 싶은 것 중 하나다.

작업 후기 - 커밋 단위와 확인 루틴

이번 작업에서 다시 확인한 건 스타일 정리도 커밋을 잘게 쪼개는 게 중요하다는 거다. "UI 전체 스타일 정리"로 한 커밋에 다 담으면 나중에 특정 변경이 문제가 됐을 때 뭘 되돌려야 할지 알 수 없다. 이번엔 아래 순서로 쪼갰음.

  • 색상 변수 추출 (기능 변경 없이 리팩토링만)
  • 테두리 중복 제거
  • 그리드 gap 통일
  • bar positioning 수정 + 스크립트 연동

각 커밋이 독립적으로 revert 가능한 단위여야 한다는 원칙을 지키려 했고, 덕분에 bar 수정 후 스크린샷 확인할 때 이전 커밋과 diff가 깔끔하게 비교됐다. 커밋 메시지도 "무엇을"보다 "왜"를 담으려고 노력했는데, 예를 들면 fix: bar border 제거 대신 fix: 부모 border-bottom 중복으로 인한 bar 간격 이상 수정 정도로 남겼음.

확인 루틴도 마찬가지. 변경 전에 현재 동작을 스크린샷으로 찍어두고, 수정 후 같은 케이스에서 다시 확인하는 게 기본이 되어야 한다. 관련 화면이 있으면 cross-check도 필수. "UI 작업은 빠르다"는 인식이 팀 안에 있으면 스타일 부채는 계속 쌓인다. 그리드 공백 한 줄 수정처럼 보이는 게 실제론 관련 화면 전체를 모바일/태블릿/데스크톱 세 해상도에서 다 확인해야 하는 작업이고, 이걸 제대로 하려면 시간이 필요하다.

사내 서비스를 만들다 보면 SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 모두 엮여 있다는 걸 계속 체감한다. 스타일도 마찬가지로, 어느 하나만 빠뜨려도 특정 해상도에서 이상한 화면이 나타난다. "대충 맞는 것 같다"로 넘어가면 나중에 반드시 다시 돌아온다는 걸, 이번 정리 작업 하면서 또 확인했음.

댓글 0

첫 댓글 달아줘.