GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Cookie Consent Banner UI — 9 Patterns to Paste In

A cookie consent banner ui is the strip or card that tells visitors a site stores cookies and records their choice.

Auto-plays · click a tile to jump to its section · all nine in one zip

The order follows the sequence in which a visitor meets consent, not how often each shape shows up in the wild. It opens with the strip that rises from the bottom edge, moves to the cookie consent ui design that splits consent into separate switches and then to the button that reopens a choice already made, and shrinks the footprint into a corner card and a single-line bar. After that come the pair that gives accept and reject the same weight, the small motion that marks the moment of agreement, the gate that waits for the end of the text, and finally the confirmation that the setting was stored. Every one of them writes its result to localStorage, so the value is still there after a reload. Collecting consent through a short guide instead of a strip belongs to Onboarding UI Design, and the notifications that pile up afterward are inherited as a stack by Toast Notification UI.

Number Where it sits Animated property
01 · 05 Strip across the bottom edge translateY
02 Three rows inside a card grid-template-rows · translateX
03 Stage center, plus a bottom-left button scale · opacity
04 Bottom-right corner translateY · rotate · scale
06 Two buttons inside a card opacity · scale
07 A centered icon and three crumbs translate · scale
08 Progress bar above a scroll box scaleX
09 Toast at the top right translateX · scaleX

01Bottom bar slide-up

What you see in this cookie banner ui design

The strip waits below the mock page by its own height plus $sp-2, rides up into place, and leaves along the same line once a button is pressed.

Bottom bar slide-up
Open in a new tab

01 · Bottom bar slide-up — Measured area 10.36% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateY 100%→0$dur-enterradius 16

Where to look in the code

The label of whichever button you pressed then stays on the page as a small chip.

.cb.is-demo .cb__banner { animation: cb-slide $dur-loop $easing infinite; }

@keyframes cb-slide {
  0%        { transform: translateY(calc(100% + #{$sp-2})); opacity: 0; }
  22%, 72%  { transform: translateY(0); opacity: 1; }
  100%      { transform: translateY(calc(100% + #{$sp-2})); opacity: 0; }
}

02Category toggle panel

What happens on screen

Necessary, analytics, and marketing each own a switch, the necessary one locked on, and pressing a row title grows its description from 0fr to 1fr.

Category toggle panel
Open in a new tab

02 · Category toggle panel — Measured area 17.019% · moving frames 14/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid 0fr→1fr노브 translateXradius 16

How to read the code

A height of auto cannot be transitioned, so the grid row size carries the animation instead.

.ct__panel {
  grid-column: 1 / -1;
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows $duration $easing;
}
.ct__inner { overflow: hidden; }
.ct__row.is-open .ct__panel { grid-template-rows: 1fr; }

.ct__knob { transform: translateX(0); transition: transform $duration $easing; }
.ct__sw.is-on .ct__knob { transform: translateX(16px); }

03Manage-consent modal reopen

What moves

A round button in the bottom-left corner breathes once, and pressing it drops a backdrop over the page while the modal grows from .92 to 1.

Manage-consent modal reopen
Open in a new tab

03 · Manage-consent modal reopen — Measured area 67.808% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

scale .92→1백드롭 opacityradius 16

What to change

Focus moves to the first button inside on open and returns to the round button on close.

.mo__modal {
  width: min(100%, 264px);
  opacity: 0;
  transform: scale(.92);
  pointer-events: none;
  transition: transform $duration $easing, opacity $duration $ease-out;
}
.mo.is-open .mo__modal { opacity: 1; transform: scale(1); pointer-events: auto; }

.mo__backdrop { background: rgba(23, 20, 26, .44); opacity: 0; pointer-events: none; }
.mo.is-open .mo__backdrop { opacity: 1; pointer-events: auto; }

04First-visit corner card

What you see

The card arrives tilted four degrees and straightens itself while three purpose rows follow at 0.10, 0.16, and 0.22 seconds.

First-visit corner card
Open in a new tab

04 · First-visit corner card — Measured area 12.553% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

rotate 4deg→00.1s 시차radius 16

Where to look in the code

Reusing one keyframe and changing only the delay saves writing a separate animation per row.

@keyframes fv-card {
  0%, 100%  { opacity: 0; transform: translateY(16px) rotate(-4deg) scale(.96); }
  18%, 78%  { opacity: 1; transform: translateY(0) rotate(0deg) scale(1); }
}

.fv__card.is-demo { animation: fv-card $duration $easing infinite; }
.fv__card.is-demo .fv__row { animation: fv-row $duration $easing infinite; }
.fv__card.is-demo .fv__row--a { animation-delay: .10s; }
.fv__card.is-demo .fv__row--b { animation-delay: .16s; }
.fv__card.is-demo .fv__row--c { animation-delay: .22s; }

05One-line mini bar

What happens on screen

A 44px strip slides up and takes almost nothing away from the page, and inside it only the cookie icon rocks from side to side.

One-line mini bar
Open in a new tab

05 · One-line mini bar — Measured area 12.71% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

높이 44px 고정rotate ±6degradius 12

How to read the code

The tilt runs -6deg → 6deg → -6deg, so the end of a cycle already matches its start.

@keyframes mb-up {
  0%, 100%  { transform: translateY(100%); }
  20%, 82%  { transform: translateY(0); }
}
@keyframes mb-tilt {
  0%   { transform: rotate(-6deg); }
  50%  { transform: rotate(6deg); }
  100% { transform: rotate(-6deg); }
}

.mb__bar.is-demo { animation: mb-up $duration $easing infinite; }
.mb__bar.is-demo .mb__cookie { animation: mb-tilt $duration $ease-out infinite; }

06Equal-weight accept and reject

What moves

Both buttons carry the same 2px border and the same font weight, and only accept fills its background.

Equal-weight accept and reject
Open in a new tab

06 · Equal-weight accept and reject — Measured area 2.603% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

outline 2pxscale 0.97 누름radius 12

What to change

Each button carries its own focus ring in an ::after, and the two alternate by opacity and scale, so a difference in width can never park the ring on the wrong label.

.ep__pair { display: grid; grid-template-columns: 1fr 1fr; gap: $sp-2; }
.ep__btn {
  border: 2px solid $color;
  background: $stage-paper;
  color: $color;
  font-weight: 700;
}
.ep__btn--accept { background: $color; color: $stage-paper; }
.ep__btn::after {
  content: "";
  position: absolute;
  inset: -6px;
  border: 3px solid $color;
  border-radius: $r-control;
  opacity: 0;
}

07Crumbling cookie icon

What you see

The cookie dips and swells while three crumbs travel out at separate angles and come home to the same spot.

Crumbling cookie icon
Open in a new tab

07 · Crumbling cookie icon — Measured area 3.951% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

scale .88→1.08부스러기 3개 왕복radius 50%

Where to look in the code

The looping version and the one-shot version share the same keyframes, and only the iteration count is overridden afterward.

@keyframes ck-crumb-a {
  0%   { transform: translate(0, 0) scale(.4);        opacity: 0; }
  16%  { transform: translate(-12px, -10px) scale(1); opacity: 1; }
  46%  { transform: translate(-34px, -26px) scale(1); opacity: 1; }
  78%  { transform: translate(-10px, -8px) scale(.7); opacity: .5; }
  100% { transform: translate(0, 0) scale(.4);        opacity: 0; }
}

.ck.is-demo .ck__crumb--a,
.ck.is-burst .ck__crumb--a { animation: ck-crumb-a $duration $easing infinite; }
.ck.is-burst .ck__icon,
.ck.is-burst .ck__crumb { animation-iteration-count: 1; }

08Scroll-to-end consent gate

What happens on screen

The consent button stays disabled until the terms box reaches its end, and the bar above reads the real scrollTop ratio through --p and fills with scaleX.

Scroll-to-end consent gate
Open in a new tab

08 · Scroll-to-end consent gate — Measured area 26.667% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

scaleX 0→1트랙만 translateYradius 12

How to read the code

The idle loop never touches the scroll position: it drifts the inner column upward while the bar and the button follow on the same beat.

sgBox.addEventListener('scroll', function () {
  sg.classList.remove('is-demo');
  var max = sgBox.scrollHeight - sgBox.clientHeight;
  var p = max > 0 ? sgBox.scrollTop / max : 1;
  sg.style.setProperty('--p', String(p < 1 ? p : 1));
  if (sgBox.scrollTop + sgBox.clientHeight >= sgBox.scrollHeight - 4) { sgReady(); }
});

09Settings saved toast

What moves

The toast enters from the right, waits while a thin timer line shrinks toward its right edge, and exits the way it came.

Settings saved toast
Open in a new tab

09 · Settings saved toast — Measured area 4.706% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateX 120%→0scaleX 1→0 타이머radius 12

What to change

The count and the clock time land on the second line, so pressing save again visibly changes the text.

@keyframes ts-slide {
  0%   { transform: translateX(120%); }
  14%  { transform: translateX(0); }
  86%  { transform: translateX(0); }
  100% { transform: translateX(120%); }
}
@keyframes ts-timer {
  0%   { transform: scaleX(1); }
  14%  { transform: scaleX(1); }
  86%  { transform: scaleX(0); }
  99%  { transform: scaleX(0); }
  100% { transform: scaleX(1); }
}

Where it breaks — the trap

Number 08 held things up the longest. It sat comfortably inside 480×300 yet reported a document height of 208px in the 320×200 render, spilling by 8px, and the cause was a narrow-screen block that had trimmed the terms box only as far as 88px. Dropping the box to 76px and pulling the card padding in by one step turned the same measurement into a clean pass. Banner-shaped layouts stack a lot of pieces vertically, so a few pixels leak easily on a phone, and measuring the real document height in a narrow viewport beats eyeballing it. The second was the timer line in number 09. A line left at scaleX(0) snaps back to a full bar at the start of the next lap, so the rewind is hidden at the 99% mark while the toast already sits offscreen, which makes 0% and 100% identical. The third was not code at all. All nine share a single demo phrase dictionary, and both the status chip in 01 and the button in 04 used the same Korean word for settings even though one means a stored value and the other means opening a detail view, and no single English string could cover both. Renaming the 01 label so that the two keys split apart is what finally kept the three languages in step. To give the copy and buttons texture, the leather and paper grains of Skeuomorphism Design Kit layer straight onto that banner. The nine folders behind that fix open with the zip password r26nqrbj, and the React port mirrors the same markup as CSS modules. Number 03 reports 67.8% of its pixels as changed because the backdrop covers the whole frame, which is what a modal is supposed to do, not a defect.

Accessibility and reduced-motion

All nine switch off the looping animation and the transitions under prefers-reduced-motion: reduce, and every click still behaves as before. 01, 04, and 05 hold the banner or card in its arrived position, 03 holds the modal open, and 08 holds the progress bar full. 06 parks the focus ring on the reject button, 07 leaves the crumbs where they scattered, and 09 leaves the toast in view. 02 has no arrived state worth keeping, so it sits in its collapsed shape and changes only when a person presses something. Every control is a real button or checkbox input and reachable by tab, switches use role="switch" with aria-checked, disclosures use aria-expanded, and the modal in 03 uses role="dialog" with aria-modal. The toast in 09 carries role="status" and aria-live="polite", so the confirmation is announced without looking. The exact query conditions are spelled out in the MDN prefers-reduced-motion page.

FAQ

Do these cookie consent banner examples make my site GDPR compliant?

No. What you get here is the interface and the interaction; the law also asks you to hold analytics and advertising scripts back until consent arrives, and to keep a record of that consent. These nine only place a value in localStorage, so a real deployment has to move that value to a cookie or a server and tie script loading to it. Once consent is granted, the quantity and coupon assembly of the buying screen is Checkout Page Design.

Why does my page content jump down when the banner appears?

Because the banner lives in the document flow and grows a real height. The five here that overlay a mock page — 01, 03, 04, 05, and 09 — float the banner with position: absolute and move it with transform alone, so layout is never recalculated when it appears. The MDN translateY page covers why that property is cheap.

Would a smaller reject button raise my acceptance rate?

In the short run yes, and that is exactly what regulators watch for. Number 06 gives both buttons the same size and weight for that reason, and the sentence above them saying the site still works after a refusal is part of the same pattern.

Need more of the same surfaces? Modal animations covers the opening and closing side, toast notifications covers other shapes of the disappearing notice, and toggle switches has the switch on its own.

Enter the archive password

The password is inside this article. You will find it as you read.