한 줄 요약 — 미디어 쿼리 @media (min-width: 768px) { … }는 "화면이 이만큼 넓어지면 이 스타일을 더 입혀라"는 조건문이다. 작은 화면을 기본으로 깔고 넓어질 때만 스타일을 얹는 모바일 퍼스트로 쓰면, 같은 페이지가 폰·태블릿·데스크톱에 한 벌처럼 맞는다. breakpoint는 기기 픽셀이 아니라 레이아웃이 깨지는 지점에서 정한다.

학습 목표#

  • @media 미디어 쿼리의 기본 문법을 읽고 쓸 수 있다.
  • min-widthmax-width의 차이를 설명하고 골라 쓸 수 있다.
  • 작은 화면을 기본으로 두는 모바일 퍼스트로 스타일을 쌓을 수 있다.
  • breakpoint를 기기가 아니라 콘텐츠 기준으로 정할 수 있다.

오늘의 비유 — 한 벌의 옷을 키와 체형에 맞춰 늘리고 줄이는 일#

옷 한 벌을 같은 사람이 입어도, 그 사람이 마르거나 살이 붙으면 솔기를 조금 늘리거나 줄여야 맞는다. 반응형 디자인도 똑같다. 페이지의 내용(입는 사람)은 하나인데, 화면이라는 몸은 폰처럼 좁기도 하고 데스크톱처럼 넓기도 하다. 우리는 옷을 세 벌 따로 짓는 게 아니라, 한 벌을 화면 폭마다 늘리고 줄여 어디서나 몸에 맞게 만든다.

이때 순서가 중요하다. 작은 몸에 맞는 기본 옷을 먼저 짓고, 몸이 커질 때마다 천을 덧대는 방식이 모바일 퍼스트다. 반대로 큰 정장을 먼저 지어 놓고 작은 몸이 올 때마다 줄이는 방식도 있지만, 줄이는 시침이 자꾸 늘어 옷이 너덜거리기 쉽다. 그래서 보통은 작은 옷부터 짓고 늘려 가는 쪽이 손이 덜 간다.

핵심 개념#

미디어 쿼리 문법 — 화면 폭에 다는 조건문#

@media로 시작하고, 괄호 안에 "언제 적용할지" 조건을 적는다. 가장 많이 쓰는 조건이 화면 폭이다.

/* 화면 폭이 768px 이상일 때만 이 안의 규칙이 켜진다 */
@media (min-width: 768px) {
  .container {
    display: flex;
  }
}

괄호 밖에 적은 스타일은 폭과 상관없이 항상 적용되는 기본값이고, 조건이 참인 동안만 중괄호 안 규칙이 추가로 켜진다. 조건이 거짓이 되면 안쪽 규칙만 꺼지고 기본값으로 돌아간다.

그 전에 — viewport 메타 태그#

모바일 브라우저는 기본적으로 자기 폭을 980px쯤으로 속여 데스크톱 페이지를 욱여넣어 보여 준다. 그러면 좁은 폭 조건이 영영 안 켜진다. <head> 안에 다음 한 줄을 넣어 "화면 실제 폭을 그대로 쓰라"고 알려 줘야 한다.

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

이 줄이 없으면 아무리 미디어 쿼리를 잘 짜도 폰에서 옷이 헐렁한 데스크톱 치수로 나온다.

min-width vs max-width — 늘릴지 줄일지#

min-width는 "이 폭 이상에서 켜라", max-width는 "이 폭 이하에서 켜라"다. 모바일 퍼스트에서는 기본을 작은 화면에 맞춰 두고 min-width로 넓어질 때 천을 덧댄다.

.card { width: 100%; }          /* 기본: 폰 — 한 장이 가로를 꽉 채운다 */

@media (min-width: 600px) {     /* 태블릿부터 두 칸 */
  .card { width: 50%; }
}
@media (min-width: 900px) {     /* 데스크톱부터 세 칸 */
  .card { width: 33.33%; }
}

조건이 작은 값부터 큰 값 순으로 쌓여, 폭이 넓어질수록 뒤 규칙이 앞 규칙을 덮는다. 반대로 max-width로 큰 화면을 기본 삼아 작은 화면을 깎아 내려가는 것도 되지만, 줄이는 예외가 늘면 코드가 시침투성이가 된다.

breakpoint는 기기가 아니라 콘텐츠로 정한다#

"아이폰은 390px, 갤럭시는 412px…" 하고 기기 치수를 외워 거기 맞추면 새 기기가 나올 때마다 어긋난다. breakpoint는 레이아웃이 어색해지는 폭에서 잡는다. 글줄이 너무 길어지거나 카드가 찌그러지기 시작하는 지점, 즉 옷이 당기거나 남기 시작하는 곳에서 한 단계 늘린다. 흔히 600px·900px·1200px 언저리를 출발점으로 삼되, 숫자 자체보다 "여기서 디자인이 깨지나"를 기준으로 본다.

함께 따라하기 — 한 페이지를 3단계로#

같은 카드 목록을 폰에서 1열, 태블릿에서 2열, 데스크톱에서 3열로 만든다. 모바일을 기본으로 깔고 min-width로 늘려 간다.

<section class="cards">
  <article class="card">카드 1</article>
  <article class="card">카드 2</article>
  <article class="card">카드 3</article>
  <article class="card">카드 4</article>
</section>
.cards {
  display: grid;
  grid-template-columns: 1fr;   /* 기본: 폰 — 1열 */
  gap: 16px;
}

@media (min-width: 600px) {
  .cards { grid-template-columns: 1fr 1fr; }       /* 태블릿 — 2열 */
}
@media (min-width: 900px) {
  .cards { grid-template-columns: 1fr 1fr 1fr; }   /* 데스크톱 — 3열 */
}

저장하고 브라우저 창을 좁은 폭에서 넓게 끌어 보면, 카드가 한 줄에 한 장 → 두 장 → 세 장으로 단을 늘린다. 같은 마크업, 같은 옷 한 벌이 창 너비에 따라 몸에 맞게 늘어나는 셈이다.

흔한 실수 3가지#

1. 데스크톱 먼저 만들고 모바일에 max-width를 잔뜩 박는다#

큰 화면용 디자인을 다 끝낸 뒤 폰에서 깨지는 곳마다 max-width 예외를 덧대는 방식이다. 처음엔 빨라 보여도 줄여야 할 곳이 계속 나와 시침이 쌓인다.

/* (큰 정장을 먼저 짓고 작은 화면마다 줄이는 예외가 끝없이 붙습니다) */
.card { width: 33.33%; }

@media (max-width: 900px) { .card { width: 50%; } }
@media (max-width: 600px) { .card { width: 100%; } }

작은 화면을 기본으로 두면 예외가 "넓어지면 늘린다" 한 방향으로만 쌓여 읽기 쉽다.

.card { width: 100%; }

@media (min-width: 600px) { .card { width: 50%; } }
@media (min-width: 900px) { .card { width: 33.33%; } }

2. 픽셀 폭에 집착해 디자인이 아닌 기기에 맞춘다#

특정 폰 모델의 정확한 폭에 breakpoint를 맞추는 경우다. 치수표의 한 사람 몸에만 맞춘 옷처럼, 다른 체형이 오면 또 어긋난다.

/* (특정 기기 폭에만 맞춰, 그 사이 폭에서는 어정쩡해집니다) */
@media (min-width: 390px) { /* … 아이폰 폭 */ }
@media (min-width: 412px) { /* … 갤럭시 폭 */ }

기기 이름 대신 레이아웃이 당기기 시작하는 폭에서 끊는다. 넉넉한 두세 단계면 대부분의 화면을 덮는다.

@media (min-width: 600px) { /* … 카드가 두 장 들어갈 만큼 넓어지는 지점 */ }
@media (min-width: 900px) { /* … 세 장이 들어갈 지점 */ }

3. 미디어 쿼리 안에 더 굵은 셀렉터를 적어 명시도 충돌이 난다#

15회차에서 본 명시도 문제가 미디어 쿼리에서 자주 터진다. 쿼리 안 규칙이 분명히 켜졌는데도 화면이 안 바뀌면, 쿼리 밖 규칙의 명시도가 더 높아 못 이기는 경우가 대부분이다.

/* (밖 규칙이 더 구체적이라, 안쪽 규칙이 켜져도 못 덮습니다) */
.cards .card { width: 100%; }       /* 명시도 0,2,0 */

@media (min-width: 600px) {
  .card { width: 33.33%; }          /* 명시도 0,1,0 — 진다 */
}

고치려는 솔기와 같은 자리를, 같은 굵기 실로 손봐야 한다. 미디어 쿼리 안팎의 셀렉터를 같은 명시도로 맞추면 폭 조건대로 깔끔히 덮인다.

.card { width: 100%; }

@media (min-width: 600px) {
  .card { width: 33.33%; }
}

오늘 배운 것 체크리스트#

  • @media (min-width: …)는 화면이 그 폭 이상일 때만 켜지는 조건문이다.
  • min-width는 넓어질 때 더하고, max-width는 좁아질 때 더한다.
  • 모바일 퍼스트는 작은 화면을 기본으로 깔고 min-width로 쌓는다.
  • <meta name="viewport"> 한 줄이 없으면 폰에서 미디어 쿼리가 안 먹는다.
  • breakpoint는 기기 픽셀이 아니라 레이아웃이 깨지는 지점에서 정한다.

자주 묻는 질문#

Q. 미디어 쿼리는 어떻게 작성하나요?

A. @media 뒤 괄호에 조건을 적고, 중괄호 안에 그 조건일 때만 켤 규칙을 넣습니다. 가장 흔한 조건은 화면 폭으로, @media (min-width: 768px) { … }처럼 씁니다. 괄호 밖 스타일은 항상 적용되는 기본값이고, 조건이 참일 때만 안쪽 규칙이 추가로 덮습니다.

Q. max-width와 min-width 미디어 쿼리는 뭐가 다른가요?

A. min-width: 600px는 폭이 600px 이상일 때, max-width: 600px는 600px 이하일 때 켜집니다. 작은 화면을 기본으로 두고 넓어질 때 더하는 모바일 퍼스트에서는 min-width를, 큰 화면을 기본으로 두고 좁아질 때 깎는 방식에서는 max-width를 씁니다. 한 프로젝트에서는 한쪽 방향으로 통일하는 편이 코드가 덜 헷갈립니다.

Q. 반응형 breakpoint는 몇 px로 잡는 게 좋나요?

A. 특정 기기 폭을 외워 맞추기보다 레이아웃이 어색해지는 지점에서 끊는 게 좋습니다. 600px(태블릿)·900px(데스크톱) 같은 넉넉한 두세 단계를 출발점으로 잡고, 카드가 찌그러지거나 글줄이 너무 길어지는 폭을 직접 보며 조정하세요. 숫자 자체보다 "여기서 디자인이 깨지나"가 기준입니다.

다음 시간 예고#

내일은 반응형 이미지 — srcset, picture, object-fit를 다룬다. 오늘은 레이아웃을 화면 폭에 맞췄다면, 다음 시간엔 이미지가 화면 크기와 화질에 맞춰 알아서 골라지고, 칸에 맞게 잘려 담기는 법을 배운다.

더 알아보기#