Half sheet
For keeping a map or photo in view while the list below is dragged up only as far as it is needed.
Live preview
Source
<div class="dc-hs-stage">
<div class="dc-hs-map" aria-hidden="true">
<span class="dc-hs-map__pin"></span>
</div>
<section class="dc-hs" role="dialog" aria-labelledby="dc-hs-t">
<button class="dc-hs__handle" type="button" aria-expanded="false" aria-controls="dc-hs-body">
<span class="dc-hs__grip" aria-hidden="true"></span>
<span class="dc-sr">목록 끝까지 펼치기</span>
</button>
<div class="dc-hs__body" id="dc-hs-body">
<h2 class="dc-hs__title" id="dc-hs-t">가까운 수거함 6곳</h2>
<p class="dc-hs__sub">성수동 기준 · 걸어서 12분 안</p>
<ul class="dc-hs__list">
<li class="dc-hs__row">
<span class="dc-hs__name">성수동 주민센터 앞</span>
<span class="dc-hs__meta">180m · 24시간</span>
</li>
<li class="dc-hs__row">
<span class="dc-hs__name">뚝섬역 2번 출구</span>
<span class="dc-hs__meta">420m · 오전 6시~자정</span>
</li>
<li class="dc-hs__row">
<span class="dc-hs__name">서울숲 주차장 옆</span>
<span class="dc-hs__meta">760m · 24시간</span>
</li>
</ul>
<p class="dc-hs__hint">위로 끌거나 손잡이를 눌러 6곳을 모두 봅니다.</p>
</div>
</section>
</div>
.dc-hs-stage {
position: relative; overflow: hidden;
height: 520px;
border-radius: var(--dc-r-md);
font-family: var(--dc-font);
}
.dc-hs-map {
position: absolute; inset: 0;
background:
repeating-linear-gradient(90deg, var(--dc-line) 0 1px, transparent 1px 46px),
repeating-linear-gradient(0deg, var(--dc-line) 0 1px, transparent 1px 46px),
var(--dc-sunken);
}
.dc-hs-map__pin {
position: absolute; left: 48%; top: 22%;
width: 16px; height: 16px;
border: 3px solid var(--dc-surface); border-radius: var(--dc-r-full);
background: var(--dc-brand);
box-shadow: var(--dc-sh-2);
}
.dc-hs {
position: absolute; left: 0; right: 0; bottom: 0;
display: flex; flex-direction: column;
height: 56%;
background: var(--dc-surface);
border-top: 1px solid var(--dc-line);
border-radius: var(--dc-r-lg) var(--dc-r-lg) 0 0;
box-shadow: var(--dc-sh-3);
transition: height var(--dc-slow);
}
.dc-hs--full { height: 100%; border-radius: 0; padding-top: var(--dc-safe-top); }
.dc-hs--peek { height: 146px; }
.dc-hs__handle {
flex: 0 0 auto;
display: grid; place-items: center;
width: 100%; height: 44px;
border: 0; border-radius: var(--dc-r-lg) var(--dc-r-lg) 0 0;
background: transparent;
touch-action: none;
}
.dc-hs__grip {
display: block; width: 44px; height: 5px;
border-radius: var(--dc-r-full);
background: var(--dc-line-bold);
transition: background var(--dc-fast);
}
.dc-hs__handle:active .dc-hs__grip { background: var(--dc-brand); }
.dc-hs__close {
position: absolute; right: var(--dc-2); top: calc(var(--dc-safe-top) + var(--dc-1));
display: grid; place-items: center;
width: 44px; height: 44px;
border: 0; border-radius: var(--dc-r-full);
background: transparent; color: var(--dc-text-sub);
}
.dc-hs__close svg { width: 22px; height: 22px; }
.dc-hs__close:active { background: var(--dc-sunken); }
.dc-hs__body {
flex: 1 1 auto; overflow-y: auto;
padding: 0 var(--dc-5) calc(var(--dc-4) + var(--dc-safe-bottom));
}
.dc-hs__title { margin: 0; font-size: 17px; font-weight: 700; line-height: 1.4; color: var(--dc-text); }
.dc-hs__sub { margin: var(--dc-1) 0 var(--dc-4); font-size: 12px; color: var(--dc-text-sub); }
.dc-hs__list { margin: 0; padding: 0; list-style: none; }
.dc-hs__row {
display: flex; align-items: center; justify-content: space-between; gap: var(--dc-3);
min-height: 56px; padding: var(--dc-2) 0;
border-top: 1px solid var(--dc-line);
}
.dc-hs__name { font-size: 14px; font-weight: 600; color: var(--dc-text); }
.dc-hs__meta { flex: 0 0 auto; font-size: 12px; color: var(--dc-text-mute); }
.dc-hs__hint {
margin: var(--dc-4) 0 0;
padding: var(--dc-2) var(--dc-3);
border-radius: var(--dc-r-sm);
background: var(--dc-info-soft);
font-size: 12px; line-height: 1.6; color: var(--dc-info);
text-align: center;
}
How to use it
- Define three stops — peek, half, full — and never let it rest between them
- At half height keep the title and two or three rows visible, so dragging looks worth it
- Make the handle a real button: tapping it must also move to the next stop
- At full height nothing shows behind it, so give it its own way to close
Watch out for
- Drag-only locks out people with limited dexterity — the handle tap and keyboard must do the same job
- Update aria-expanded every time the height changes
- Leave --dc-safe-top at full height and --dc-safe-bottom at the end of the list