사용자 대부분은 책상 앞이 아니라 길에서, 침대에서, 한 손으로 손바닥만 한 화면을 들여다봅니다. 넓은 화면에서 만든 걸 그냥 좁게 줄이기만 해선 좋은 경험이 나오지 않습니다. 모바일의 형편을 실제 CSS로 하나씩 반영해 보겠습니다.

28.1 뷰포트부터 잡기

이 한 줄이 없으면 모바일 브라우저는 넓은 화면인 척 축소해서, 글자가 깨알처럼 작아집니다. 모든 모바일 대응의 출발선입니다.


<meta name="viewport"
content="width=device-width, initial-scale=1">


화면 실제 너비에 맞추고 배율을 1로 두어야, 그다음의 미디어쿼리와 반응형 레이아웃이 비로소 제대로 동작합니다.

28.2 좁아지면 세로로 쌓기

넓은 화면에서 나란하던 것을 좁은 화면에 그대로 두면 삐져나와 가로 스크롤이 생깁니다. 좁아지면 한 줄로 세웁니다.


.layout {
display: grid;
grid-template-columns: 240px 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.layout {
grid-template-columns: 1fr;
gap: 16px;
}
}


넓을 땐 사이드바와 본문이 두 칸으로 나뉘고, 768px 아래로 좁아지면 한 칸으로 합쳐 위아래로 쌓입니다. 어느 화면에서도 옆으로 잘리지 않습니다.

28.3 손가락에 넉넉한 터치 타깃

마우스 끝은 뾰족하지만 손가락은 뭉툭합니다. 눌리는 판을 최소 44px로 잡아 옆의 것까지 건드리는 사고를 막습니다.


.tab-bar a {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
}
.tab-bar {
display: flex;
gap: 8px;
}


아이콘 자체는 작아도 눌리는 영역을 44px로 키우고 사이에 간격을 두면, 흔들리는 차 안에서 눌러도 어긋나지 않습니다.

28.4 hover에 기능을 숨기지 않기

손가락엔 마우스오버가 없습니다. hover에만 나타나는 메뉴는 터치 사용자에겐 없는 길입니다.


.menu {
opacity: 0;
}
.card:hover .menu {
opacity: 1;
}


이 방식은 터치 기기에서 메뉴가 영영 안 보입니다. hover가 진짜 되는 기기에서만 숨김을 적용하도록 감쌉니다.


@media (hover: hover) {
.menu { opacity: 0; }
.card:hover .menu { opacity: 1; }
}


이제 마우스가 있는 기기만 hover로 감췄다 보여 주고, 터치 기기에선 메뉴가 늘 보입니다. 손짓은 지름길이되 없는 길은 되지 않아야 합니다.

28.5 노치를 피하는 안전 영역

요즘 기기는 위아래 모서리가 둥글거나 노치가 파여 있습니다. 하단 고정 바가 그 턱에 가려지지 않도록 안전 영역을 존중합니다.


.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
}


env(safe-area-inset-bottom) 이 기기마다 다른 하단 여백을 알아서 더해 줍니다. 홈 바에 버튼이 겹쳐 안 눌리는 일이 사라집니다.

28.6 유동적인 글자 크기

작은 화면엔 살짝 작게, 큰 화면엔 살짝 크게. 화면 폭에 따라 글자가 매끄럽게 변하도록 clamp() 로 묶습니다.


.hero-title {
font-size: clamp(22px, 6vw, 40px);
line-height: 1.25;
}


가운데 6vw 가 화면 폭을 따라 커지되, 22px보다 작아지거나 40px보다 커지지 않게 아래위를 막았습니다. 미디어쿼리를 여러 개 쓰지 않고도 부드럽게 이어집니다.

28.7 엄지가 닿는 곳에 중요한 버튼

한 손으로 쥐면 화면 아래쪽은 엄지가 편히 닿고 위쪽 구석은 힘겹습니다. 자주 누르는 행동을 아래에 고정합니다.


.action-bar {
position: sticky;
bottom: 0;
display: flex;
gap: 8px;
padding: 12px 16px;
background: #fff;
border-top: 1px solid #e5e7eb;
}
.action-bar .btn-primary {
flex: 1;
}


주된 버튼을 화면 아래에 붙이고 폭을 꽉 채우면, 스크롤이 길어도 엄지 하나로 늘 닿습니다. 위험한 삭제 같은 건 여기서 일부러 비켜 둡니다.

28.8 터치 기기에 맞는 입력 자판

좁은 화면에서 자판을 두드리는 건 고됩니다. 칸의 성격에 맞는 자판이 바로 뜨게 해 수고를 덜어 줍니다.


<input type="email" inputmode="email"
autocomplete="email">
<input type="text" inputmode="numeric"
autocomplete="one-time-code">


inputmode 로 숫자 칸엔 숫자 자판이, 이메일 칸엔 골뱅이가 있는 자판이 뜹니다. autocomplete 는 이미 아는 값을 채워 두드릴 일 자체를 줄입니다.

28.9 터치 기기의 회색 하이라이트 다듬기

모바일에서 링크를 누르면 반투명 회색 상자가 번쩍 뜹니다. 그대로 둬도 되지만, 직접 만든 눌림 효과와 겹치면 지저분해 보입니다.


.btn {
-webkit-tap-highlight-color: transparent;
transition: transform .1s;
}
.btn:active {
transform: scale(0.97);
}


기본 회색 번쩍임을 끄고, 대신 누를 때 살짝 작아지는 반응을 직접 줬습니다. 손끝에 눌렸다는 느낌은 남기되 지저분함은 걷어 냅니다.

28.10 넘치는 것 없이 흐르게

좁은 화면에서 가로 스크롤이 생기는 흔한 범인은 폭이 고정된 이미지와 긴 단어입니다. 미디어와 텍스트가 화면 안에서 흐르도록 잡습니다.


img, video {
max-width: 100%;
height: auto;
}
.post-body {
overflow-wrap: break-word;
word-break: break-word;
}


이미지는 부모보다 커지지 않고 비율을 지키며 줄고, 긴 주소나 단어는 칸을 넘기 전에 줄바꿈됩니다. 카드 목록도 화면 폭에 맞춰 칸 수가 저절로 접히게 합니다.


.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}


auto-fillminmax 덕에 넓은 화면엔 여러 칸, 좁은 화면엔 한두 칸으로 알아서 줄어듭니다. 미디어쿼리 없이도 어느 폭에서든 삐져나오지 않습니다.

28.11 정리

모바일은 넓은 화면을 좁힌 게 아니라 다른 자리에서 다른 형편의 사용자를 만나는 통로입니다. 뷰포트를 잡고, 좁아지면 세로로 쌓고, 손가락에 넉넉한 44px를 주고, hover에 기능을 숨기지 않고, 엄지가 닿는 아래쪽에 중요한 버튼을 두는 것, 이 코드들이 손바닥 위 경험을 편안하게 만듭니다. 가장 빠듯한 화면에서 먼저 출발하면 대개 양쪽 다 나아집니다.