05One-line mini bar
A single-line strip that barely covers the page rises from the bottom, and only the cookie icon on the left tips side to side within six degrees.
Best forBlogs and docs sites with short notices, and banners that must eat less screen on mobile
높이 44px 고정rotate ±6degradius 12
<main class="stage">
<div class="mb">
<div class="mb__top" aria-hidden="true">
<i class="mb__dot"></i><i class="mb__dot"></i><i class="mb__dot"></i>
</div>
<div class="mb__lines" aria-hidden="true">
<i class="mb__line mb__line--title"></i>
<i class="mb__line"></i>
<i class="mb__line"></i>
<i class="mb__line mb__line--short"></i>
</div>
<div class="mb__bar is-demo" role="region" aria-label="Cookie notice">
<span class="mb__cookie" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a48 48 0 0 0 48 48a48 48 0 0 0 48 48" opacity=".2"/><path d="M164.49 163.51a12 12 0 1 1-17 0a12 12 0 0 1 17 0m-81-8a12 12 0 1 0 17 0a12 12 0 0 0-16.98 0Zm9-39a12 12 0 1 0-17 0a12 12 0 0 0 17-.02Zm48-1a12 12 0 1 0 0 17a12 12 0 0 0 0-17M232 128A104 104 0 1 1 128 24a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8m-16.31 7.39A56.13 56.13 0 0 1 168.5 87.5a56.13 56.13 0 0 1-47.89-47.19a88 88 0 1 0 95.08 95.08"/></g></svg></span>
<p class="mb__text">We use cookies. Continuing means you accept them.</p>
<button class="mb__ok" type="button">OK</button>
<button class="mb__x" type="button" aria-label="Close"><span class="mb__xic" aria-hidden="true"><svg viewBox="0 0 256 256" width="14" height="14"><g fill="currentColor"><path d="M216 56v144a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h144a16 16 0 0 1 16 16" opacity=".2"/><path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></g></svg></span></button>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop;
$easing: $ease-spring;
$color: $subject-cream; // 어두운 무대 — 강조는 파랑 대신 밝은 쪽 대비
@include stage($stage-ink);
// ── 어두운 판 위의 가짜 페이지 목업 (움직이지 않는다) ──
.mb {
position: relative;
box-sizing: border-box;
width: clamp(272px, 90vw, 420px);
height: clamp(170px, 62vh, 232px);
overflow: hidden; // 띠가 내려간 만큼은 판 안에서 자른다
border: 1px solid rgba(255, 247, 230, .12);
border-radius: $r-card;
background: rgba(255, 247, 230, .04);
color: $color;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.mb__top {
display: flex;
align-items: center;
gap: $sp-1;
height: 24px;
padding: 0 $sp-3;
background: rgba(255, 247, 230, .06);
}
.mb__dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 247, 230, .20); }
.mb__lines { display: grid; gap: $sp-3; padding: $sp-4; }
.mb__line { height: 8px; border-radius: $r-xs; background: rgba(255, 247, 230, .06); }
.mb__line--title { height: 12px; width: 56%; background: rgba(255, 247, 230, .12); }
.mb__line--short { width: 62%; }
// ── 맨 아래 44px 고정 띠 ──
.mb__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
height: 44px;
display: flex;
align-items: center;
gap: $sp-2;
padding: 0 $sp-3;
border-top: 1px solid rgba(255, 247, 230, .22);
background: rgba(255, 247, 230, .14);
transition: transform $dur-base $easing;
}
.mb__bar.is-gone { transform: translateY(100%); }
.mb__cookie { display: inline-flex; flex: 0 0 auto; color: $color; }
.mb__text {
margin: 0;
min-width: 0;
flex: 1 1 auto;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: clamp(9px, 2.6vw, 12px);
line-height: 1.3;
color: rgba(255, 247, 230, .88);
}
.mb__ok {
flex: 0 0 auto;
border: 0;
border-radius: $r-pill;
padding: $sp-1 $sp-3;
background: $color;
color: $subject-ink;
font-family: inherit;
font-size: clamp(9px, 2.6vw, 11px);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition: opacity $dur-quick $ease-out, transform $dur-instant $ease-out;
}
.mb__ok:hover, .mb__ok:focus-visible { opacity: .86; }
.mb__ok:active { transform: scale(.97); }
.mb__x {
flex: 0 0 auto;
display: inline-flex;
border: 0;
border-radius: $r-sm;
padding: $sp-1;
background: none;
color: rgba(255, 247, 230, .64);
cursor: pointer;
transition: color $dur-quick $ease-out, transform $dur-instant $ease-out;
}
.mb__x:hover, .mb__x:focus-visible { color: $color; }
.mb__x:active { transform: scale(.97); }
.mb__xic { display: inline-flex; }
// ── 자동 루프: 0~20% 에 확실히 올라오고 머물다 다시 내려간다 (0% = 100%) ──
.mb__bar.is-demo { animation: mb-up $duration $easing infinite; }
@keyframes mb-up {
0%, 100% { transform: translateY(100%); }
20%, 82% { transform: translateY(0); }
}
// 띠 안 cookie 아이콘만 좌우로 천천히 기운다 (-6 → 6 → -6)
.mb__bar.is-demo .mb__cookie { animation: mb-tilt $duration $ease-out infinite; }
@keyframes mb-tilt {
0% { transform: rotate(-6deg); }
50% { transform: rotate(6deg); }
100% { transform: rotate(-6deg); }
}
@media (max-width: 340px) {
.mb__lines { gap: $sp-2; padding: $sp-3; }
.mb__bar { gap: $sp-1; padding: 0 $sp-2; }
.mb__ok { padding: $sp-1 $sp-2; }
}
@media (prefers-reduced-motion: reduce) {
.mb__bar.is-demo, .mb__bar.is-demo .mb__cookie { animation: none !important; }
.mb__bar.is-demo { transform: translateY(0); } // 올라와 있는 결과 상태로 둔다
.mb__bar, .mb__ok, .mb__x { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.mb {
position: relative;
box-sizing: border-box;
width: clamp(272px, 90vw, 420px);
height: clamp(170px, 62vh, 232px);
overflow: hidden;
border: 1px solid rgba(255, 247, 230, 0.12);
border-radius: 16px;
background: rgba(255, 247, 230, 0.04);
color: #fff7e6;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.mb__top {
display: flex;
align-items: center;
gap: 4px;
height: 24px;
padding: 0 12px;
background: rgba(255, 247, 230, 0.06);
}
.mb__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(255, 247, 230, 0.2);
}
.mb__lines {
display: grid;
gap: 12px;
padding: 16px;
}
.mb__line {
height: 8px;
border-radius: 4px;
background: rgba(255, 247, 230, 0.06);
}
.mb__line--title {
height: 12px;
width: 56%;
background: rgba(255, 247, 230, 0.12);
}
.mb__line--short {
width: 62%;
}
.mb__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
height: 44px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
border-top: 1px solid rgba(255, 247, 230, 0.22);
background: rgba(255, 247, 230, 0.14);
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.mb__bar.is-gone {
transform: translateY(100%);
}
.mb__cookie {
display: inline-flex;
flex: 0 0 auto;
color: #fff7e6;
}
.mb__text {
margin: 0;
min-width: 0;
flex: 1 1 auto;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: clamp(9px, 2.6vw, 12px);
line-height: 1.3;
color: rgba(255, 247, 230, 0.88);
}
.mb__ok {
flex: 0 0 auto;
border: 0;
border-radius: 999px;
padding: 4px 12px;
background: #fff7e6;
color: #17141a;
font-family: inherit;
font-size: clamp(9px, 2.6vw, 11px);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition: opacity 220ms ease-out, transform 140ms ease-out;
}
.mb__ok:hover, .mb__ok:focus-visible {
opacity: 0.86;
}
.mb__ok:active {
transform: scale(0.97);
}
.mb__x {
flex: 0 0 auto;
display: inline-flex;
border: 0;
border-radius: 8px;
padding: 4px;
background: none;
color: rgba(255, 247, 230, 0.64);
cursor: pointer;
transition: color 220ms ease-out, transform 140ms ease-out;
}
.mb__x:hover, .mb__x:focus-visible {
color: #fff7e6;
}
.mb__x:active {
transform: scale(0.97);
}
.mb__xic {
display: inline-flex;
}
.mb__bar.is-demo {
animation: mb-up 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes mb-up {
0%, 100% {
transform: translateY(100%);
}
20%, 82% {
transform: translateY(0);
}
}
.mb__bar.is-demo .mb__cookie {
animation: mb-tilt 2s ease-out infinite;
}
@keyframes mb-tilt {
0% {
transform: rotate(-6deg);
}
50% {
transform: rotate(6deg);
}
100% {
transform: rotate(-6deg);
}
}
@media (max-width: 340px) {
.mb__lines {
gap: 8px;
padding: 12px;
}
.mb__bar {
gap: 4px;
padding: 0 8px;
}
.mb__ok {
padding: 4px 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.mb__bar.is-demo, .mb__bar.is-demo .mb__cookie {
animation: none !important;
}
.mb__bar.is-demo {
transform: translateY(0);
}
.mb__bar, .mb__ok, .mb__x {
transition: none;
}
}// 실제 동작: OK·Close 어느 쪽을 눌러도 띠가 아래로 내려가고 선택을 저장한다
var bar = document.querySelector('.mb__bar');
function dismiss(v) {
bar.classList.remove('is-demo');
bar.classList.add('is-gone');
try { localStorage.setItem('gd-consent-05', v); } catch (e) {}
}
document.querySelector('.mb__ok').addEventListener('click', function () { dismiss('accept'); });
document.querySelector('.mb__x').addEventListener('click', function () { dismiss('close'); });import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./MiniLineBar.module.scss";
export type MiniLineBarProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
function CookieIcon() {
return (
<svg viewBox="0 0 256 256" width="20" height="20" aria-hidden="true">
<g fill="currentColor">
<path d="M224 128a96 96 0 1 1-96-96a48 48 0 0 0 48 48a48 48 0 0 0 48 48" opacity=".2" />
<path d="M164.49 163.51a12 12 0 1 1-17 0a12 12 0 0 1 17 0m-81-8a12 12 0 1 0 17 0a12 12 0 0 0-16.98 0Zm9-39a12 12 0 1 0-17 0a12 12 0 0 0 17-.02Zm48-1a12 12 0 1 0 0 17a12 12 0 0 0 0-17M232 128A104 104 0 1 1 128 24a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8a40 40 0 0 0 40 40a8 8 0 0 1 8 8m-16.31 7.39A56.13 56.13 0 0 1 168.5 87.5a56.13 56.13 0 0 1-47.89-47.19a88 88 0 1 0 95.08 95.08" />
</g>
</svg>
);
}
function XIcon() {
return (
<svg viewBox="0 0 256 256" width="14" height="14" aria-hidden="true">
<g fill="currentColor">
<path d="M216 56v144a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h144a16 16 0 0 1 16 16" opacity=".2" />
<path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z" />
</g>
</svg>
);
}
export default function MiniLineBar({
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#fff7e6",
}: MiniLineBarProps) {
const [live, setLive] = useState(true);
const [gone, setGone] = useState(false);
const vars: Vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
};
const dismiss = (value: string): void => {
setLive(false);
setGone(true);
try {
localStorage.setItem("gd-consent-05", value);
} catch (e) {
/* 샌드박스에서 던질 수 있다 */
}
};
return (
<div className={styles.mb} style={vars}>
<div className={styles.top} aria-hidden="true">
<i className={styles.dot} />
<i className={styles.dot} />
<i className={styles.dot} />
</div>
<div className={styles.lines} aria-hidden="true">
<i className={`${styles.line} ${styles.lineTitle}`} />
<i className={styles.line} />
<i className={styles.line} />
<i className={`${styles.line} ${styles.lineShort}`} />
</div>
<div
className={`${styles.bar} ${live ? styles.demo : ""} ${gone ? styles.gone : ""}`}
role="region"
aria-label="Cookie notice"
>
<span className={styles.cookie}>
<CookieIcon />
</span>
<p className={styles.text}>We use cookies. Continuing means you accept them.</p>
<button className={styles.ok} type="button" onClick={() => dismiss("accept")}>
OK
</button>
<button
className={styles.x}
type="button"
aria-label="Close"
onClick={() => dismiss("close")}
>
<span className={styles.xic}>
<XIcon />
</span>
</button>
</div>
</div>
);
}05 One-line mini bar — Measured area 12.71% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · Cookie Consent Banner UI — 9 Patterns to Paste In
The post this demo lives in
Cookie Consent Banner UI — 9 Patterns to Paste InA cookie consent banner ui is the strip or card that tells visitors a site stores cookies and records their choice. Thes See all demos in this post


