안녕하세요! 오늘 살펴볼 문서는 모바일 웹 개발이나 PWA(Progressive Web App)를 개발할 때, 꼭 알아두어야 할 필수 개념인 환경 변수(Environment Variables)와 env() 함수에 대한 가이드입니다.
특히 아이폰(iPhone)의 노치(Notch) 디자인이나 다이내믹 아일랜드 때문에 웹 페이지 위쪽이나 아래쪽이 가려지는 문제를 겪어보신 적 있으신가요? 프론트엔드 개발자 면접에서도 "모바일 기기의 노치 영역에 콘텐츠가 가려지는 현상을 CSS로 어떻게 해결하나요?"라는 질문이 가끔 등장하는데, 이 문서가 바로 그 질문에 대한 완벽한 정답이 되어줄 것입니다.
그럼 MDN 문서를 함께 읽어볼까요?
CSS 환경 변수 모듈(CSS environment variables module)은 CSS에 환경 변수라는 개념을 도입하고, 이 환경 변수를 사용할 수 있게 해주는 env() 함수를 정의하고 있습니다. 이 가이드에서는 환경 변수가 무엇인지, 브라우저가 정의한 환경 변수들은 무엇이 있는지, 그리고 env() 함수를 통해 환경 변수를 어떻게 사용하는지 예제를 통해 알아보겠습니다.
env() 함수 (The env() function)env()를 사용한 환경 변수 사용 예제 (Using environment variables with env() example)CSS 환경 변수는 글로벌(전역) 변수입니다. 전체 문서에 걸쳐 전역적으로 스코프(범위)가 지정되어 있죠. 이 변수들은 사용자 에이전트(브라우저)에 의해 정의됩니다. 환경 변수는 여러분의 스타일이 사용자의 기기(디바이스)나 현재 상황(context)에 맞게 적응(adapt)하도록 도와주기 위해 브라우저나 운영체제(OS)가 제공하는 특별한 값들입니다. 이 값들에는 env() 함수를 사용하여 접근할 수 있습니다.
환경 변수는 커스텀 속성(custom properties, --*)이나 var() 함수와 비슷하게 작동하지만, 전역적으로 정의되고 스코프가 지정된다는 점이 다릅니다. 즉, 특정 요소(element)에만 스코프가 한정되는 커스텀 속성과 달리, 환경 변수는 언제나 전체 문서에 적용됩니다. 게다가 커스텀 속성은 우리가 마음대로 값을 바꿀 수 있는(mutable) 반면, 환경 변수는 오직 읽기 전용(read-only)입니다.
커스텀 속성과 마찬가지로 환경 변수 역시 대소문자를 엄격히 구분(case-sensitive)합니다. 속성 선언부 외부에서는 사용할 수 없는 커스텀 속성과는 달리, env() 함수는 속성값의 어느 부분이든, 심지어는 미디어 쿼리(Media query rules)와 같은 설명자(descriptor)의 일부로도 자유롭게 사용할 수 있습니다.
애플(Apple)은 개발자들이 둥근 모서리나 노치(notch) 같은 불규칙한 디바이스 디스플레이에 맞춰 레이아웃을 최적화할 수 있도록 iOS Safari 브라우저에 환경 변수를 가장 먼저 도입했습니다. 최초의 safe-area-inset-* 환경 변수들은 사용자가 어떤 기기나 브라우저를 쓰든 간에, 개발자가 뷰포트 내의 '안전한 영역(safe area)'에 콘텐츠를 배치할 수 있도록 해줍니다.
환경 변수를 사용해서 해결할 수 있는 일반적인 문제들은 다음과 같습니다:
CSS 환경 변수 명세서는 대소문자를 구분하는 몇 가지 변수들을 정의하고 있습니다. 주요 변수들은 다음과 같습니다:
preferred-text-scale
preferred-text-scale 환경 변수는 사용자가 선호하는 텍스트 배율(scale factor)을 나타냅니다. 이것은 운영체제나 사용자 에이전트의 "기본" 폰트 크기에 적용된 조정값입니다. [`text-size-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-size-adjust) 속성이 작동하는 기기나 브라우저에서는, text-size-adjust: auto에 의해 적용되는 배율이 바로 이 값입니다. 예를 들어, text-size-adjust: auto로 인해 텍스트 크기가 2배로 커진다면, env(preferred-text-scale) 값은 2로 해석됩니다.
safe-area-inset-*
안전 영역 여백(safe area inset)을 나타내는 네 개의 환경 변수 - safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left - 는 뷰포트의 가장자리로부터 위, 오른쪽, 아래, 왼쪽으로 쑥 들어간 직사각형 형태의 '안전 영역'을 정의합니다. 콘텐츠를 이 영역 안에 배치하면 직사각형이 아닌 디스플레이(노치, 둥근 모서리 등) 모양 때문에 콘텐츠가 잘려나가는 일 없이 안전하게 보여줄 수 있습니다.
일반적인 데스크톱이나 노트북 모니터처럼 잘려나가는 부분이 없는 완전한 직사각형 뷰포트에서는 이 네 개의 값이 모두 0이 됩니다. 반면, 전체 화면을 덮는 둥근 모서리를 가진 기기나 둥근 화면의 스마트 워치 등 비직사각형 디스플레이에서는, 사용자 에이전트(브라우저)가 지정한 이 네 개의 값이 하나의 직사각형을 형성하여, 그 직사각형 안의 모든 콘텐츠가 가려짐 없이 온전히 보이도록 보장해 줍니다.
safe-area-max-inset-*
최대 안전 영역 여백(safe area maximum inset)을 나타내는 네 개의 환경 변수 - safe-area-max-inset-top, safe-area-max-inset-right, safe-area-max-inset-bottom, safe-area-max-inset-left - 는 위에서 설명한 동적인 safe-area-inset-* 변수들이 가질 수 있는 정적인 최댓값을 나타냅니다. 즉, 동적으로 변하는 모든 유저 인터페이스(예: 브라우저 주소창 등)가 사라졌을 때 가지게 되는 safe-area-inset-*의 최댓값입니다.
예를 들어, 특정 플랫폼에서는 화면을 스크롤할 때 버튼 바가 나타났다 사라지며 safe-area-inset-* 값이 동적으로 바뀔 수 있습니다. 이렇게 현재 보이는 콘텐츠 영역이 바뀜에 따라 safe-area-inset-* 값은 수시로 변하지만, safe-area-max-inset-* 값은 항상 고정된 채로 유지됩니다.
viewport-segment-*
이 변수들은 화면이 접히는 폴더블(foldable) 폰처럼 여러 개의 세그먼트(화면 분할 영역)를 가진 기기에서만 의미가 있습니다. viewport-segment-bottom, viewport-segment-left, viewport-segment-right, viewport-segment-top 변수들은 viewport-segment-height, viewport-segment-width와 함께 논리적으로 분리된 뷰포트 영역의 위치와 크기를 정의합니다. 이 변수들은 뷰포트가 최소 두 개 이상의 세그먼트로 나뉘어 있을 때만 정의됩니다. 다중 세그먼트 기기에서 UI의 여러 부분들을 각 세그먼트 영역에 딱 맞게 배치하여, 화면이 접히는 경첩(fold) 부분에 콘텐츠가 잘리거나 걸치지 않게 피하기 위해 사용됩니다.
이 외의 다른 명세서들에서도 추가적인 환경 변수들을 정의하고 있습니다.
창 컨트롤 오버레이 API (Window Controls Overlay API)는 WindowControlsOverlay 인터페이스를 정의하는데, 이는 데스크톱 기기에 설치된 프로그레시브 웹 애플리케이션 (PWA)의 타이틀 바(title bar) 영역의 기하학적 정보를 노출합니다. PWA 매니페스트에서 window-controls-overlay display_override 값을 사용할 때, 다음과 같은 환경 변수들이 정의됩니다:
titlebar-area-*
titlebar-area-x, titlebar-area-y, titlebar-area-width, titlebar-area-height 변수들은 데스크톱 환경에서 실행되는 설치된 웹 애플리케이션에서 일반적으로 타이틀 바가 차지하는 영역을 정의합니다. 콘텐츠가 창 제어 버튼(최소화, 최대화, 닫기 버튼)과 겹치지 않도록 하려면 이 titlebar-area-* 변수들을 사용하세요.
keyboard-inset-*
keyboard-inset-top, keyboard-inset-right, keyboard-inset-bottom, keyboard-inset-left, keyboard-inset-width, keyboard-inset-height 변수들은 화면에 나타나는 가상 키보드(virtual keyboard)의 위치와 크기에 대한 정보를 제공합니다. 구체적으로는 뷰포트 가장자리로부터 위, 오른쪽, 아래, 왼쪽으로 얼마나 들어와 있는지(inset)를 나타냅니다 (너비와 높이 inset은 다른 inset 값들로부터 계산됩니다). 더 자세한 내용은 가상 키보드 API (VirtualKeyboard API) 문서를 참고하세요.
눈치채셨겠지만, 지금까지 살펴본 모든 변수 이름에는 left, right, top, bottom, height, width와 같은 물리적(physical) 용어들이 포함되어 있습니다. 이 변수 이름들이 화면에 표시되는 웹사이트의 논리적 방향이 아니라 기기 하드웨어 자체의 물리적 특성을 가리키기 때문에 논리적 대응 속성(logical equivalents, 예: start, end 등)이 굳이 필요하지 않은 것입니다.
env() 함수 (The env() function)env() 함수는 환경 변수의 값을 CSS 구문 내에 삽입할 때 사용됩니다. env() 함수는 어떤 요소의 어떤 속성값이든, 혹은 어떤 @규칙의 어떤 설명자(descriptor) 값이든 그 일부를 대체하는 데 사용할 수 있으며, 심지어 커스텀 속성(CSS 변수) 값 안에서도 사용할 수 있습니다. 기본적으로 CSS 값이 들어갈 수 있는 곳이라면 어디든 사용이 가능합니다.
기본 문법은 다음과 같습니다:
env( <environment-variable-name> )
env( <environment-variable-name>, <fallback-value> )
이 함수는 대소문자를 구분하는 환경 변수 이름을 첫 번째 인자로 받습니다. 그리고 선택 사항이지만 가급적 제공하는 것이 강력히 권장되는 대체 값(fallback value)을 두 번째 인자로 받을 수 있습니다.
line-height: env(preferred-text-scale, 2);
margin: env(safe-area-inset-top, 0) env(safe-area-inset-right, auto)
env(safe-area-inset-bottom, 3em) env(safe-area-inset-left, auto);
첫 번째 인자는 삽입할 환경 변수의 이름입니다. 쉼표(,) 뒤에 오는 인자는 (제공된 경우) 대체 값(fallback value)이며, 첫 번째 인자로 참조한 환경 변수가 해당 환경에 존재하지 않을 때 대신 사용됩니다.
위의 예제에서, 만약 브라우저에 preferred-text-scale 환경 변수가 존재하지 않는다면 line-height는 2로 설정됩니다. 그리고 브라우저가 safe-area-inset-* 값들을 지원하지 않는다면, margin은 margin: 0 auto 3em auto로 설정될 것입니다.
대체 값(fallback)의 문법은 커스텀 속성(var())의 문법과 비슷해서, 내부에 여러 개의 쉼표를 허용합니다. 첫 번째 쉼표부터 함수의 끝 부분 사이에 있는 모든 내용이 통째로 대체 값으로 간주됩니다. 단, 쉼표를 지원하지 않는 속성이나 설명자에 사용할 경우 그 값은 유효하지 않은 것으로 처리됩니다.
속성이나 설명자에 문법적으로 유효한 env() 함수가 포함되어 있다면, 파싱(parse) 시점에는 유효한 것으로 간주됩니다. 실제로 문법 검사가 이루어지는 것은 계산 시점(computed-time)으로, env() 함수가 브라우저가 제공하는 실제 값으로 모두 대체된 이후에 진행됩니다. 만약 첫 번째 매개변수로 전달된 환경 변수가 인식할 수 없는 이름이라면, 그 자리는 대체 값으로 교체됩니다. 이 대체 값 안에는 또 다른 환경 변수가 들어갈 수도 있고, 그 환경 변수 역시 자신만의 대체 값을 가질 수 있습니다. 만약 인식할 수 없는 환경 변수를 썼는데 대체 값조차 제공하지 않았다면, env() 함수를 포함한 그 속성이나 설명자는 계산 시점에 무효화(invalid)됩니다.
env()를 사용한 환경 변수 사용 예제 (Using environment variables with env() example)우리는 환경 변수를 활용하여, 기기 하단에 알림(또는 홈 인디케이터 바)이 떴을 때 화면 하단에 고정된 앱 툴바가 가려지지 않도록 안전장치를 마련할 수 있습니다.
화면 하단에 알림을 표시하는 기기에서는, 브라우저가 safe-area-inset-bottom 환경 변수값을 뷰포트를 가리고 있는 것의 맨 윗부분부터 뷰포트의 맨 아랫부분까지의 거리로 설정해 줍니다. 이 예제의 경우, 떠 있는 알림의 높이가 곧 그 값이 될 것입니다. 반면 직사각형의 일반 데스크톱 모니터에서는 safe-area-inset-bottom 값이 기본적으로 0입니다. 우리는 이 환경 변수값을 활용해서 뷰포트 하단에 공간을 여유 있게 확보함으로써, 알림이 뜨더라도 우리 앱의 콘텐츠가 가려지지 않게 만들 것입니다.
HTML 구조를 보면, <body>는 두 개의 자식을 가지고 있습니다. <main>은 툴바 역할을 하는 <footer>를 제외한 애플리케이션의 전체 내용을 담고 있습니다.
<body>
<main>Application</main>
<footer>Toolbar</footer>
</body>
<body>는 뷰포트 높이(100vh)를 가득 채우는 플렉스 컨테이너로 정의되어 있습니다. <main> 애플리케이션 영역은 형제 요소인 <footer> 툴바가 차지하고 남은 나머지 공간을 꽉 채우도록(grow) 설정되었습니다.
body {
display: flex;
flex-flow: column nowrap;
height: 100vh;
}
main {
flex: 1; /* 남은 공간을 모두 차지함 */
padding: 1em;
overflow-y: auto; /* 내용이 넘치면 자체 스크롤 발생 */
}
<footer>는 뷰포트 하단에 딱 달라붙어 있도록(stuck) 위치가 지정됩니다. position: sticky 선언은 bottom: 0을 기준으로, 요소의 스크롤 조상이자 컨테이닝 블록인 <body>에 상대적으로 푸터를 오프셋 시킵니다. 우리는 <footer>의 네 면 모두에 1em의 padding 값을 주었습니다. 그리고 나서 가장 중요한 부분인 padding-bottom에 1em과 safe-area-inset-bottom 환경 변수값을 더해줍니다. (만약 브라우저가 이 환경 변수를 지원하지 않을 경우를 대비해 1em을 대체 값으로 함께 적어줍니다.)
footer {
position: sticky;
bottom: 0;
padding: 1em;
/* 핵심 코드! 기본 패딩 1em에 기기의 안전 영역 마진을 더해줍니다. */
padding-bottom: calc(1em + env(safe-area-inset-bottom, 1em));
}
(간결함을 위해 추가적인 CSS는 생략했습니다.)
💡 강사의 모바일 웹 개발 팁!
아이폰 X 이후로 화면 하단에 둥근 모서리와 홈 인디케이터(Home Indicator) 바가 생기면서, 뷰포트 하단에 딱 붙어있는 네비게이션 바(GNB)의 텍스트나 버튼이 이 홈 바와 겹쳐서 눌리지 않는 문제가 빈번하게 발생합니다. 위의padding-bottom: calc(1em + env(safe-area-inset-bottom, 0px));(대체값은 0px 추천) 코드는 이 문제를 완벽하게 해결해 주는 실무 필수 코드입니다! 면접에서 이 지식을 어필한다면 현장 경험이 풍부한 개발자로 인정받으실 수 있습니다.
이 코드가 적용되면, safe-area-inset-bottom 환경 변수값이 0보다 큰 기기(예: 최신 아이폰 등)에서는 푸터의 하단 패딩이 기본 1em을 넘어서 더 길게 늘어납니다. 이 CSS는 화면 하단의 알림, 디스플레이의 노치 디자인, 또는 기기의 둥근 모서리 등 어떤 이유로든 콘텐츠가 가려지는 것을 막기 위해 푸터 하단에 필요한 만큼의 추가 패딩을 유연하게 제공합니다.
미래에는 개발자가 직접 정의할 수 있는 커스텀 환경 변수가 지원될 수도 있지만, 아직 이에 대한 표준이 정의되거나 구현되지는 않았습니다.
var()@media shape 설명자 (descriptor)이 페이지가 도움이 되셨나요?
기여하는 방법 알아보기 (Learn how to contribute)
이 페이지는 MDN 기여자들에 의해 2025년 11월 7일에 마지막으로 수정되었습니다 (MDN contributors).