지난 10년간 여러 CSS 방법론들이 있었지만 2024년인 지금 가장 대중적인 방법은 아마 tailwind CSS 일 것이다. 하지만 나에겐 테일윈드마저 약간 오버스펙으로 다가와서 직접 필요한 스타일만 추가해서 유틸리티 CSS를 만들게 되었다. 결과적으로 핀크 내 서비스를 개발할 때 아주 편리하고 유용하게 사용하고 있어서 공유해본다. 사실 특별할 건 정말 하나도 없다.
Tailwind CSS란?
Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.
Tailwind CSS는 유틸리티 퍼스트(Utility-First) 접근 방식을 사용하는 CSS 프레임워크로, 사전 정의된 클래스를 활용해 빠르게 스타일을 적용할 수 있습니다. Tailwind CSS는 전통적인 CSS 작성 방식을 대체하며, HTML 코드 내에서 직접 클래스를 사용해 스타일을 정의합니다.
Tailwind CSS vs 전통적 CSS
특징 | Tailwind CSS | 전통적 CSS / 프레임워크 |
스타일 작성 방식 | HTML에 유틸리티 클래스를 직접 작성 | 별도 파일에 CSS 작성 |
유지보수성 | 클래스 네이밍 고민이 적고, 디자인 일관성 우수 | 스타일과 마크업 분리로 가독성 높음 |
학습 곡선 | 초기 학습이 필요 | CSS 자체에 익숙하면 추가 학습 불필요 |
파일 크기 | 사용된 클래스만 포함, 매우 작음 | CSS 전부 포함, 상대적으로 클 수 있음 |
사용성 | 빠른 스타일 적용 가능 | 직접 작성해야 하므로 상대적으로 느림 |
복잡한 디자인 요구 | 추가 설정 또는 커스텀 클래스 필요 | 자유도 높음 |
나는 꽤 오랜시간동안 클래스명을 보고 파악할 수 없는 테일윈드 스타일을 선호하지 않았다. 한 두곳도 아니고 개발 페이지 전체에 유틸리티 CSS명이 뺴곡하게 들어가 있는 것에 대한 심리적 거부감이 컸던 것 같다. 예를 들면 아래와 같은 경우다. 코드만 보아도 왼쪽은 '핀크 잭팟 영역'이구나 라는 걸 바로 알 수 있지만 오른쪽은 쉽게 알기 어렵다.
<div class="finnq-jackpot-area"> ... </div> |
<div class="mt40 pt24 spacing-x border-t flex-center p16 fc-b1"> .... </div> |
그래서 내가 선호하는 방식은 전통적 CSS 방식과 유틸리티 CSS를 적절하게 섞어서 사용하는 방식이다.
요즘 내가 선호하는 스타일
코어 컴포넌트는 클래스명 기반의 스타일 작성을 하고, 그 외에 나머지는 유틸리티 CSS를 사용한다. 특히나 핀크에서는 모바일 웹뷰라는 특수 환경만 케어하면 되기 때문에 피시 웹 사이즈나 피시에서 동작하는 hover 스타일 등은 필요하지 않기 때문에 더욱 유틸리티 CSS 사용이 편리하다. 처음에는 tailwind를 사용해볼까 고민했지만 생각보다 내가 사용하는 CSS의 양이 크지 않아서 직접 만들어서 사용하기로 했다.
_preset.scss
미리 정의하였다는 의미로 preset 이라는 파일명을 지어줬다. !important 사용을 무분별하게 하면 안된다고 하지만 preset.scss 에서는 부득이하게 자주 사용하게 되었다. 특히나 간격(여백)이나 정렬을 다루는 요소는 다른 CSS를 덮어 쓰는 경우가 더러 있어서 !important를 붙여줬다.
/*
display
visibility
padding
margin
width
border
border-radius
background
font-xxx
*/
// display
.none { display: none; }
.block { display: block; }
.inline-block { display: inline-block; }
.flex { display: flex; align-items: center; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.flex-start { display: flex; align-items: flex-start; }
.inline-flex { display: inline-flex; align-items: center; }
.flex-column { display: flex; flex-direction: column; }
.flex-between { display: flex; justify-content: space-between; }
.flex1 { flex: 1; }
.align-top { align-items: flex-start !important; }
.align-center { align-items: center !important; }
.cursor { cursor: pointer; }
.fit-content { width: fit-content; }
.img-center { display: block; margin: 0 auto; }
// gap
.gap0 { gap: 0 !important; }
.gap2 { gap: 2px !important; }
.gap4 { gap: 4px !important; }
.gap6 { gap: 6px !important; }
.gap8 { gap: 8px !important; }
.gap10 { gap: 10px !important; }
.gap16 { gap: 16px !important; }
.gap24 { gap: 24px !important; }
.gap32 { gap: 32px !important; }
.gap48 { gap: 48px !important; }
.gap56 { gap: 56px !important; }
// visibility
.hidden { visibility: hidden; }
.visible { visibility: visible; }
// padding
.no-spacing-x { padding-left: 0 !important; padding-right: 0 !important; }
.spacing-x { padding-left: var(--spacing-x); padding-right: var(--spacing-x); }
.spacing-x-reverse { margin-left: calc(-1 * var(--spacing-x)) !important; margin-right: calc(-1 * var(--spacing-x)) !important; }
.spacing-x-margin { margin-left: var(--spacing-x); margin-right: var(--spacing-x); }
.p-0 { padding: 0 !important; }
.p-8 { padding: 8px !important; }
.p-16 { padding: 16px !important; }
.p-32 { padding: 32px !important; }
.p12-24 { padding: 12px 24px !important; }
.p16-24 { padding: 16px 24px !important; }
.p24-16 { padding: 24px 16px !important; }
.pt0 { padding-top: 0 !important; }
.pt2 { padding-top: 2px !important; }
.pt4 { padding-top: 4px !important; }
.pt6 { padding-top: 6px !important; }
.pt8 { padding-top: 8px !important; }
.pt10 { padding-top: 10px !important; }
.pt12 { padding-top: 12px !important; }
.pt16 { padding-top: 16px !important; }
.pt20 { padding-top: 20px !important; }
.pt24 { padding-top: 24px !important; }
.pt32 { padding-top: 32px !important; }
.pt40 { padding-top: 40px !important; }
.pt48 { padding-top: 48px !important; }
.pt56 { padding-top: 56px !important; }
.pt64 { padding-top: 64px !important; }
.pb4 { padding-bottom: 4px !important; }
.pb6 { padding-bottom: 6px !important; }
.pb8 { padding-bottom: 8px !important; }
.pb12 { padding-bottom: 12px !important; }
.pb16 { padding-bottom: 16px !important; }
.pb24 { padding-bottom: 24px !important; }
.pb32 { padding-bottom: 32px !important; }
.pb40 { padding-bottom: 40px !important; }
.pb48 { padding-bottom: 48px !important; }
.pb56 { padding-bottom: 56px !important; }
.pb64 { padding-bottom: 64px !important; }
.pb80 { padding-bottom: 80px !important; }
.pl4 { padding-left: 4px !important; }
.pl8 { padding-left: 8px !important; }
.pl12 { padding-left: 12px !important; }
.pl16 { padding-left: 16px !important; }
.pl20 { padding-left: 20px !important; }
.pl24 { padding-left: 24px !important; }
.pr4 { padding-right: 4px !important; }
.pr8 { padding-right: 8px !important; }
.pr12 { padding-right: 12px !important; }
.pr16 { padding-right: 16px !important; }
.pr20 { padding-right: 20px !important; }
.pr24 { padding-right: 24px !important; }
// margin
.m0 { margin: 0 !important; }
.m0-auto { margin: 0 auto !important; }
.mt2 { margin-top: 2px !important; }
.mt4 { margin-top: 4px !important; }
.mt6 { margin-top: 6px !important; }
.mt8 { margin-top: 8px !important; }
.mt10 { margin-top: 10px !important; }
.mt12 { margin-top: 12px !important; }
.mt16 { margin-top: 16px !important; }
.mt20 { margin-top: 20px !important; }
.mt24 { margin-top: 24px !important; }
.mt32 { margin-top: 32px !important; }
.mt40 { margin-top: 40px !important; }
.mt48 { margin-top: 48px !important; }
.mt56 { margin-top: 56px !important; }
.mt80 { margin-top: 80px !important; }
.mr4 { margin-right: 4px !important; }
.mr8 { margin-right: 8px !important; }
.mr12 { margin-right: 12px !important; }
.mr16 { margin-right: 16px !important; }
.ml-auto { margin-left: auto !important; }
.ml2 { margin-left: 2px !important; }
.ml4 { margin-left: 4px !important; }
.ml8 { margin-left: 8px !important; }
.ml10 { margin-left: 10px !important; }
.ml12 { margin-left: 12px !important; }
.ml16 { margin-left: 16px !important; }
.mb8 { margin-bottom: 8px !important; }
.mb12 { margin-bottom: 12px !important; }
.mb16 { margin-bottom: 16px !important; }
.mb24 { margin-bottom: 24px !important; }
.mb32 { margin-bottom: 32px !important; }
.mb40 { margin-bottom: 40px !important; }
.mb48 { margin-bottom: 48px !important; }
.mb56 { margin-bottom: 56px !important; }
.mb80 { margin-bottom: 80px !important; }
// width
.w-100 { width: 100% !important; }
// border
.border-none { border: none !important; }
.border-t { border-top: 1px solid var(--border100); }
.border-b { border-bottom: 1px solid var(--border100); }
.border-100 { border: 1px solid var(--border100); }
.border-200 { border: 1px solid var(--border200); }
// border-radius
.br-4 { border-radius: 4px; }
.br-6 { border-radius: 6px; }
.br-8 { border-radius: 8px; }
.br-12 { border-radius: 12px; }
.br-16 { border-radius: 16px; }
// background
.bg-texti { background-color: var(--texti) !important; }
.bg-100 { background-color: var(--bg100) !important; }
.bg-200 { background-color: var(--bg200) !important; }
.bg-300 { background-color: var(--bg300) !important; }
.bg-500 { background-color: var(--bg500) !important; }
.bg-white { background-color: white !important; }
.bg-secondary08 { background-color: var(--secondary08) !important; }
.bg-success08 { background-color: var(--success08) !important; }
.bg-error08 { background-color: var(--error08) !important; }
// text-align
.text-left { text-align: left !important; }
.text-center { text-align: center !important; }
.text-right { text-align: right !important; }
// text-decoration
.text-underline { text-decoration: underline !important; }
.text-through { text-decoration: line-through !important; }
// vertical-align
.vertical-top { vertical-align: top; }
.vertical-middle { vertical-align: middle; }
.vertical-bottom { vertical-align: bottom; }
// font size
.fz-h1 { @include font-heading(1); }
.fz-h2 { @include font-heading(2); }
.fz-h3 { @include font-heading(3); }
.fz-h4 { @include font-heading(4); }
.fz-h5 { @include font-heading(5); }
.fz-h6 { @include font-heading(6); }
.fz-b1 { @include font-body(1); }
.fz-b2 { @include font-body(2); }
.fz-b3 { @include font-body(3); }
.fz-c1 { @include font-caption(1); }
.fz-c2 { @include font-caption(2); }
.fz-c3 { @include font-caption(3); }
// font weight
.fw-700 { font-weight: 700 !important; }
.fw-500 { font-weight: 500 !important; }
.fw-300 { font-weight: 300 !important; }
// font color
.fc-text2 { color: var(--text2) !important; }
.fc-text5 { color: var(--text5) !important; }
.fc-text8 { color: var(--text8) !important; }
.fc-text9 { color: var(--text9) !important; }
.fc-texti { color: var(--texti) !important; }
.fc-primary { color: var(--primary) !important; }
.fc-primary-sub { color: var(--primary-sub) !important; }
.fc-secondary { color: var(--secondary) !important; }
.fc-success { color: var(--success) !important; }
.fc-error { color: var(--error) !important; }
.fc-danger { color: var(--danger) !important; }
.flex-nowrap { flex-wrap: nowrap !important; }
.break-all { word-break: break-all !important; }
.keep-all { word-break: keep-all !important; }
.nowrap { white-space: nowrap !important; }
.pre-wrap { white-space: pre-wrap !important; }
.ellipsis1 { @include ellipsis(1); }
.ellipsis2 { @include ellipsis(2); }
.ellipsis3 { @include ellipsis(3); }
.no-select { user-select: none; }
_variable.scss
:root {
// colors
--primary: #4D00B6;
--primary-sub: #8370f8;
--primary-rgb: 77, 0, 182;
--primary08: #F1EBF9;
--text2: #222222;
--text5: #505866;
--text8: #818B8F;
--text9: #9eaeb6;
--texti: #B1BBC0;
--border100: #EAEFF4;
--border200: #DEE8EE;
--bg-divider: rgba(234, 239, 244, 0.5);
--bg100: #F7F7F7;
--bg200: #F1F3F4;
--bg300: #DCE2E5;
--bg500: rgba(166, 196, 212, 0.32);
--layer-dimmed: rgba(0,0,0,0.75);
--secondary: #278DF5;
--secondary08: #EEF6FE;
--success: #00B389;
--success08: #EBF9F6;
--danger: #F74B20;
--danger08: #FEF1ED;
--error: #F74B20;
--error08: #FEF1ED;
--warning: #FFDB15;
--yellow: #FFC736;
// semantic variables
--placeholder: var(--text9);
--placeholder-rgb: 158, 174, 182;
--placeholder-disabled: var(--texti);
// font
--font-family-sans: "SUIT", sans-serif;
// height
--header-height: 44px;
--tabs-list-height: 52px;
--input-height: 90px;
// spacing
--spacing-x-narrow: 16px;
--spacing-x: 24px;
--spacing-y: 16px;
--spacing-b: 80px;
// z-index
--z-index-modal: 9999;
--z-index-header: 9998;
--z-index-page-header: 9997;
--z-index-tabs-more: 9998;
--z-index-tabs: 9997;
--z-index-input: 9997;
--z-index-floating-btn: 9999;
}
_mixin_typo.scss
타이포그래피 스타일은 미리 정의된 디자인 시스템에 따라 개발하였다. h1~h6, body1~3, caption1~3 의 크기안에서만 디자인이 되니 문장이나 타이틀을 붙일 경우 간단하게 클래스명으로만으도 스타일이 가능해서 매우 편리하다.
<p class="fz-b1 fw-700 fc-text8">body1 사이즈, 굵은, text8 색상</p>
// Typography
@mixin font-style($size, $lineHeight: normal) {
font-size: $size !important;
line-height: $lineHeight !important;
}
@mixin font-heading($size, $weight: 700) {
@if $size == 1 {
@include font-style(36px);
}
@if $size == 2 {
@include font-style(30px);
}
@if $size == 3 {
@include font-style(28px);
}
@if $size == 4 {
@include font-style(24px, 31px);
}
@if $size == 5 {
@include font-style(20px, 28px);
}
@if $size == 6 {
@include font-style(18px, 24px);
}
font-weight: $weight;
}
@mixin font-body($size, $weight: 500) {
@if $size == 1 {
@include font-style(16px, 22px);
}
@if $size == 2 {
@include font-style(14px, 20px);
}
@if $size == 3 {
@include font-style(13px, 19px);
}
font-weight: $weight;
}
@mixin font-caption($size, $weight: 500) {
@if $size == 1 {
@include font-style(12px, 16px);
}
@if $size == 2 {
@include font-style(11px, 15px);
}
@if $size == 3 {
@include font-style(10px);
}
font-weight: $weight;
}
적용 예시
<button class="m0-auto mt32 mb48 fz-b2 fc-secondary flex-center gap2">
당첨 결과 한번에 확인
<IconMagicWand :size="20"/>
</button>
체감하는 장점
1. 클래스명 짓는 스트레스 해방
ㄴ 필요없는 곳에 쓰는 에너지만 줄어도 작업 속도가 굉장히 빨라짐을 느낀다.
2. 작은 수정이나 변경이 용이
ㄴ 핀크와 같이 여러곳에 분산되어 있는 개발 환경에서 CSS를 수정하기란 쉽지 않다. 더군다나 마크업만 확인할 수 있는 환경이 없는 경우, 간단한 수정을 위해서 환경 셋팅을 하기가 굉장히 버거운데 preset.scss와 함께라면 개발자에게 클래스명만 전달해도 보통 발생하는 오류들은 금방 해결할 수 있다.
'개발일지 > 2024' 카테고리의 다른 글
[Vue3] type: String as PropType와 validator 차이점 (0) | 2024.12.05 |
---|---|
Vuex 와 Pinia 비교 (0) | 2024.11.22 |
[Canvas] Vue로 복권 긁기 기능 구현 (LottoScratch) (0) | 2024.10.24 |
[인터랙션개발] CSSWinner에서 발견한 재밌는 효과들 (0) | 2024.06.28 |
[Fonts] 독특한 무료 한글 폰트, HS산토끼체 2.0 (0) | 2024.06.28 |