Safe Area 기기의 safe area inset을 화면에 반영하고, 배경과 스크롤 영역을 화면 끝까지 채우는 방법을 알아봅니다.
요소를 safe area 안에 둘지, 화면 끝까지 채울지 정하는 기준은 Safe Area 패턴 을 참고하세요. 이 문서는 그 기준을 React로 구현하는 방법을 설명합니다.
safe area inset을 반영하려면 viewport-fit=cover가 포함된 viewport 메타 태그를 사용하세요.
< meta
name = "viewport"
content = "width=device-width, initial-scale=1, viewport-fit=cover"
/>
이 설정이 없으면 iOS WebKit은 env(safe-area-inset-*)를 0으로 계산합니다. 따라서 아래에서 설명하는 컴포넌트와 속성도 inset을 반영하지 않습니다.
다음 컴포넌트는 별도로 설정하지 않아도 inset을 반영합니다. 자세한 동작은 각 컴포넌트 문서를 참고하세요.
AppScreenContent처럼 inset만큼 안쪽으로 들어간 컨테이너 안에서 요소를 화면 끝까지 넓히려면 Box의 속성에 "safeArea" 값을 지정합니다. 이 값을 받는 속성은 Box의 Safe Area 를 참고하세요.
이미지처럼 요소 전체가 화면 끝까지 이어져야 하면 bleedX="safeArea"를 지정합니다.
배경은 화면 끝까지 채우고 내용은 safe area 안에 두려면 bleedX와 함께 pl, pr에도 "safeArea"를 지정합니다. 화면 여백(spacingX.globalGutter)은 안쪽 요소에 지정합니다.
화면 하단에 고정하는 바는 pb="safeArea"도 지정해서 하단 inset만큼 내용을 올립니다.
import type { StaticActivityComponentType } from "@stackflow/react/future" ;
import { Box, HStack, Text, VStack } from "@seed-design/react" ;
import type { ReactNode } from "react" ;
import { ActionButton } from "seed-design/ui/action-button" ;
import { AppBar, AppBarBackButton, AppBarLeft, AppBarMain } from "seed-design/ui/app-bar" ;
import { AppScreen, AppScreenContent } from "seed-design/ui/app-screen" ;
import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "seed-design/ui/chip-tabs" ;
import img from "../assets/peng.jpeg" ;
declare module "@stackflow/config" {
interface Register {
ActivitySafeAreaBleed : {};
}
}
const CATEGORIES = [
"전체" ,
"디지털기기" ,
"생활가전" ,
"가구/인테리어" ,
"생활/주방" ,
"유아동" ,
"여성의류" ,
"남성패션/잡화" ,
"뷰티/미용" ,
];
const Section = ({
title,
description,
children,
} : {
title : string ;
description : string ;
children : ReactNode ;
}) => (
< VStack gap = "x3" >
< VStack gap = "x1" px = "spacingX.globalGutter" >
< Text textStyle = "t5Bold" color = "fg.neutral" >
{title}
</ Text >
< Text textStyle = "t3Regular" color = "fg.neutralMuted" >
{description}
</ Text >
</ VStack >
{children}
</ VStack >
);
const ActivitySafeAreaBleed : StaticActivityComponentType < "ActivitySafeAreaBleed" > = () => (
< AppScreen >
< AppBar >
< AppBarLeft >
< AppBarBackButton />
</ AppBarLeft >
< AppBarMain title = "좌우 safe area와 bleed" />
</ AppBar >
< AppScreenContent >
< VStack gap = "x8" pt = "x4" >
< Section title = "이미지" description = "bleedX에 safeArea를 주면 좌우 inset까지 채워요." >
< Box bleedX = "safeArea" >
< img src = {img} alt = "penguin" style = {{ display: "block" , width: "100%" }} />
</ Box >
</ Section >
< Section
title = "가로 스크롤"
description = "스크롤 영역은 bleedX로 화면 끝까지 넓히고, pl과 pr의 safeArea로 내용을 safe area 경계에서 시작해요. gutter는 안쪽 요소가 가져요."
>
{ /* Before iOS 26.4, WebKit leaves a block scroll container's end padding out of the scroll
range when a block-level child overflows; a flex container keeps it, so the last item
stops at the safe area edge. */ }
< Box display = "flex" bleedX = "safeArea" pl = "safeArea" pr = "safeArea" overflowX = "auto" >
< HStack gap = "x2" px = "spacingX.globalGutter" width = "max-content" >
{Array. from ({ length: 10 }, ( _ , index ) => (
< VStack
key = {index}
width = "120px"
height = "80px"
borderRadius = "r2"
bg = "bg.neutralWeak"
justify = "center"
align = "center"
>
< Text textStyle = "t4Bold" color = "fg.neutral" >
{index + 1 }
</ Text >
</ VStack >
))}
</ HStack >
</ Box >
</ Section >
< Section
title = "Chip Tabs"
description = "스크롤 영역이 gutter를 padding으로 직접 가지면, gutter와 inset을 더한 값을 padding으로 줘요."
>
< Box bleedX = "safeArea" >
< ChipTabsRoot defaultValue = { CATEGORIES [ 0 ]} variant = "neutralSolid" >
< ChipTabsList
style = {{
paddingLeft:
"calc(var(--seed-dimension-spacing-x-global-gutter) + var(--seed-safe-area-left))" ,
paddingRight:
"calc(var(--seed-dimension-spacing-x-global-gutter) + var(--seed-safe-area-right))" ,
}}
>
{ CATEGORIES . map (( category ) => (
< ChipTabsTrigger key = {category} value = {category}>
{category}
</ ChipTabsTrigger >
))}
</ ChipTabsList >
</ ChipTabsRoot >
</ Box >
</ Section >
< Section
title = "배경 띠"
description = "배경은 bleedX로 화면 끝까지 채우고, 내용은 pl과 pr의 safeArea와 gutter 안쪽에 둬요."
>
< Box bleedX = "safeArea" pl = "safeArea" pr = "safeArea" bg = "bg.brandWeak" >
< Box px = "spacingX.globalGutter" py = "x4" >
< Text textStyle = "t4Medium" color = "fg.brand" >
배경은 화면 끝까지, 글자는 safe area 안쪽에 있어요.
</ Text >
</ Box >
</ Box >
</ Section >
< VStack gap = "x3" px = "spacingX.globalGutter" >
{Array. from ({ length: 6 }, ( _ , index ) => (
< Text key = {index} textStyle = "t4Regular" color = "fg.neutral" >
아래 하단 바가 콘텐츠 위에 고정되는지 확인하기 위한 문단이에요. 하단 바는 배경을 화면
끝까지 채우고, 버튼은 safe area 안쪽에 둬요.
</ Text >
))}
</ VStack >
< Box
position = "sticky"
bottom = { 0 }
bleedX = "safeArea"
pl = "safeArea"
pr = "safeArea"
pb = "safeArea"
bg = "bg.layerDefault"
>
< HStack px = "spacingX.globalGutter" py = "x3" >
< ActionButton variant = "brandSolid" flexGrow >
확인
</ ActionButton >
</ HStack >
</ Box >
</ VStack >
</ AppScreenContent >
</ AppScreen >
);
export default ActivitySafeAreaBleed;
가로 스크롤 영역도 bleedX와 pl, pr에 "safeArea"를 지정해 화면 끝까지 넓힙니다. 스크롤하기 전에는 첫 항목이 safe area 경계에서 화면 여백만큼 떨어져 시작하고, 끝까지 스크롤하면 마지막 항목 뒤에도 inset과 화면 여백이 남습니다.
가로 스크롤 컨테이너는 flex 컨테이너(display="flex")로 두세요. iOS 26.4 미만의 WebKit은 block 컨테이너 안에서 block 레벨 요소가 가로로 넘치면, 컨테이너의 끝쪽 padding을 스크롤 범위에 넣지 않습니다. 이때는 pr="safeArea"로 지정한 inset이 스크롤 범위에서 빠져서, 끝까지 스크롤해도 마지막 항목이 오른쪽 inset 영역에 걸립니다.
ChipTabsList처럼 스크롤 영역이 화면 여백을 padding으로 직접 가지는 컴포넌트에는, 화면 여백과 inset을 더한 값을 padding으로 지정합니다.