A Black Friday bar with a countdown and a coupon code
A sale needs three things on the page: a reason to act now, the code, and a way to use it without hunting for it. This bar does all three with one custom code block. It counts down to the end of your sale, shows the coupon code with a copy button, and takes itself down when the sale is over.
The bar, live
This course is free, so there's nothing to discount, but the bar works the same. Try the Copy button, and the close button on the right.
Black Friday demo: on a paid course, this code would take 30% off at checkout.
BLACKFRIDAY30
What it does
- Counts down to the end of your sale, in days, hours, minutes and seconds.
- Shows the code in a box, with a Copy button. One click and it's ready to paste at checkout.
- Takes itself down when the countdown reaches zero, so you don't need to remember to remove it at midnight.
-
Remembers a close. If someone closes it, it stays closed for them on that browser. It's saved in the browser's local storage as
ph-announce-bf-dismissed, not as a cookie, and nothing is saved unless they close it. Change the bar's ID and it comes back for everyone. - Sits at the top or the bottom. At the bottom it stays on screen as people scroll.
Set up the coupon first
The bar only shows a code, it doesn't create one. Make the coupon in Teachable first (there's more on coupons in Coupons, bundles and upsells, back in section 2), and give it the same end date as your countdown. Otherwise the bar says the sale is over while the code still works, or worse, the other way round.
Copy the code across exactly as you typed it in Teachable. If you'd rather nobody had to type anything, Teachable can also give you a link with the coupon already applied, which you could put in the message.
Adding it to a page
- Open the page in Teachable's page editor (your home page, or a sales page).
- Add a Custom code block and move it to the very top.
- Paste the whole of the code below into it, change the settings, and save.
It only appears on pages where you add the block, so if you want it on your home page and two sales pages, that's three blocks. It won't appear at checkout, because nothing you add reaches checkout.
If Teachable's own header is still showing on the page, the bar will sit
under it rather than above. If that bothers you, set the position to
'bottom' and it'll stay fixed along the bottom of the screen instead.
The settings
Everything you'd want to change is in a short list at the top of the script.
The message itself is in the HTML, just above it, in the line that starts
<strong>Black Friday:.
| Setting | What it does |
|---|---|
PH_ANNOUNCE_END |
When the sale ends. Keep the time zone on the end (see below). |
PH_ANNOUNCE_COUPON |
The code to show. Leave it blank and the box disappears. |
PH_ANNOUNCE_POSITION |
'top' or 'bottom'. |
PH_ANNOUNCE_ID |
Change it for a new sale, so people who closed the last one see this one. |
PH_ANNOUNCE_DISMISSIBLE |
false removes the close button. |
PH_ANNOUNCE_HIDE_ON_END |
false keeps the bar up after the end, showing PH_ANNOUNCE_ENDED_MSG instead. |
The end time has a time zone on the end of it: 2026-11-27T23:59:59+00:00
is midnight in Ireland and the UK. Use -05:00 for New York or
-08:00 for Los Angeles. Leave it off and every visitor gets
midnight in their own time zone, so the sale ends at a different moment for
each of them, and not when your coupon does.
Get the code
Use it on your own school or a client's. The colours are fixed, so it doesn't need anything else from this school to work.
Show the code
<style>
/* =================================================================
Black Friday announcement bar, with countdown and coupon code
From purplehippo.teachable.com, free to use
Self-contained: fixed colours, no shared tokens needed, so it
works on any Teachable school as a single custom code block.
1rem = 10px (Teachable 2.0 sets html { font-size: 62.5% }).
Colours are fixed (black #000000, orange #E8913A) so the bar
stands out from the rest of the page. Change the hex values
below to match your own brand.
Position: PH_ANNOUNCE_POSITION below, 'top' or 'bottom'.
Bottom mode pads <body> so it doesn't cover
content.
Countdown: set PH_ANNOUNCE_END below to the sale's end date/time,
with a UTC offset on the end (+00:00 for Ireland/UK in winter,
-05:00 for New York) so it ends at the same moment for everyone.
Without one, each visitor's own clock zone is used.
Once it passes, the bar hides itself automatically (set
PH_ANNOUNCE_HIDE_ON_END = false to instead swap in
PH_ANNOUNCE_ENDED_MSG and keep the bar showing).
Coupon: PH_ANNOUNCE_COUPON below shows the code in a dashed
"ticket" box with a Copy button (copies to the clipboard and
shows "Copied" for a moment). Blank it out to drop the box and
keep the countdown message-only.
Dismiss: closing the bar remembers it in the visitor's browser
so it won't reappear for them on this device/browser. Change
PH_ANNOUNCE_ID whenever the sale/message changes so it
resurfaces for everyone.
What's stored: NO cookie is set. It's one localStorage entry
key: ph-announce-bf-dismissed
value: the PH_ANNOUNCE_ID at the time (e.g. 'bf-2026')
It's only written when someone clicks the close button, and the
bar stays hidden while the value matches PH_ANNOUNCE_ID.
To see or delete it: browser DevTools > Application (Chrome) or
Storage (Firefox/Safari) > Local Storage > your school's address.
Or paste this in the DevTools console on your school and reload:
localStorage.removeItem('ph-announce-bf-dismissed')
================================================================= */
.ph-announce--bf {
box-sizing: border-box;
position: relative;
z-index: 950;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
column-gap: 1.8rem;
row-gap: 0.6rem;
padding: 1.2rem clamp(1.8rem, 4vw, 3.2rem);
background: #000000;
color: #FFFFFF;
font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
font-size: 1.4rem;
font-weight: 500;
line-height: 1.4;
text-align: center;
}
/* display:flex above would beat the hidden attribute, so the close
button and the dismissed/ended checks wouldn't hide the bar */
.ph-announce--bf[hidden] { display: none !important; }
.ph-announce--bf *, .ph-announce--bf *::before, .ph-announce--bf *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) {
.ph-announce--bf *, .ph-announce--bf *::before, .ph-announce--bf *::after { transition-duration: 1ms !important; }
}
.ph-announce--bf[data-position="bottom"] {
position: fixed;
left: 0;
right: 0;
bottom: 0;
}
.ph-announce--bf__msg { margin: 0; }
.ph-announce--bf__msg strong {
color: #E8913A; /* --ph-sun */
font-weight: 700;
}
.ph-announce--bf__msg a {
color: inherit;
text-decoration: underline;
text-underline-offset: 0.2em;
}
/* ---- Countdown ---- */
.ph-announce--bf__clock {
display: flex;
align-items: center;
gap: 0.5rem;
}
.ph-announce--bf__unit {
display: flex;
flex-direction: column;
align-items: center;
min-width: 3.6rem;
padding: 0.3rem 0.4rem;
border-radius: 0.6rem;
background: rgba(232, 145, 58, 0.16);
}
.ph-announce--bf__num {
font-family: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
font-size: 1.7rem;
font-weight: 700;
line-height: 1.1;
color: #E8913A; /* --ph-sun */
font-variant-numeric: tabular-nums;
}
.ph-announce--bf__label {
margin-top: 0.1rem;
font-size: 0.95rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
}
.ph-announce--bf__sep {
color: rgba(255, 255, 255, 0.35);
font-weight: 700;
}
/* ---- Coupon ---- */
.ph-announce--bf__coupon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 1rem;
padding: 0.4rem 0.4rem 0.4rem 1.2rem;
border: 2px dashed #E8913A; /* --ph-sun */
border-radius: 0.8rem;
background: rgba(232, 145, 58, 0.12);
}
.ph-announce--bf__coupon-label {
font-size: 0.95rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
}
.ph-announce--bf__code {
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: 0.08em;
color: #E8913A; /* --ph-sun */
user-select: all; /* one click selects the whole code */
/* Bootstrap (Teachable) styles <code> as a pink box, so reset it */
padding: 0;
border: 0;
border-radius: 0;
background: none;
white-space: nowrap;
}
@media (max-width: 400px) {
.ph-announce--bf__coupon { gap: 0.6rem; padding-left: 0.8rem; }
.ph-announce--bf__code { font-size: 1.3rem; letter-spacing: 0.04em; }
}
.ph-announce--bf__copy {
min-width: 7.2rem;
padding: 0.5rem 1.2rem;
border: 0;
border-radius: 0.5rem;
background: #E8913A; /* --ph-sun */
color: #1D1729;
font: inherit;
font-size: 1.2rem;
font-weight: 700;
cursor: pointer;
transition: background-color 200ms ease, transform 120ms ease;
}
.ph-announce--bf__copy:hover { background: #FFA751; }
.ph-announce--bf__copy:active { transform: scale(0.97); }
.ph-announce--bf__copy:focus-visible {
outline: 2px solid #E8913A;
outline-offset: 2px;
}
/* ---- Close ---- */
.ph-announce--bf__close {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.2rem;
height: 2.2rem;
padding: 0;
border: 0;
border-radius: 50%;
background: transparent;
color: #FFFFFF;
opacity: 0.75;
cursor: pointer;
transition: opacity 200ms ease, background-color 200ms ease;
}
.ph-announce--bf__close:hover { opacity: 1; background: rgba(255, 255, 255, 0.14); }
.ph-announce--bf__close:focus-visible { outline: 2px solid #E8913A; outline-offset: 2px; }
.ph-announce--bf__close svg { width: 1.3rem; height: 1.3rem; }
.ph-announce--bf__close { position: absolute; right: clamp(1.2rem, 3vw, 2.4rem); }
</style>
<div class="ph-announce--bf" id="ph-announce-bf" data-position="top" hidden>
<p class="ph-announce--bf__msg" id="ph-announce-bf-msg">
<strong>Black Friday:</strong> 30% off every course with this code.
</p>
<div class="ph-announce--bf__clock" id="ph-announce-bf-clock" aria-live="off">
<div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="d">00</span><span class="ph-announce--bf__label">Days</span></div>
<span class="ph-announce--bf__sep">:</span>
<div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="h">00</span><span class="ph-announce--bf__label">Hrs</span></div>
<span class="ph-announce--bf__sep">:</span>
<div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="m">00</span><span class="ph-announce--bf__label">Min</span></div>
<span class="ph-announce--bf__sep">:</span>
<div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="s">00</span><span class="ph-announce--bf__label">Sec</span></div>
</div>
<div class="ph-announce--bf__coupon" id="ph-announce-bf-coupon">
<span class="ph-announce--bf__coupon-label">Code</span>
<code class="ph-announce--bf__code" id="ph-announce-bf-code">BLACKFRIDAY30</code>
<button type="button" class="ph-announce--bf__copy" id="ph-announce-bf-copy" aria-label="Copy coupon code">Copy</button>
</div>
<button type="button" class="ph-announce--bf__close" id="ph-announce-bf-close" aria-label="Dismiss announcement">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round">
<path d="M6 6l12 12M18 6L6 18"/>
</svg>
</button>
</div>
<script>
(function () {
/* ---- settings ---- */
var PH_ANNOUNCE_ID = 'bf-2026'; // change whenever the sale/message changes, to re-show it to everyone
var PH_ANNOUNCE_POSITION = 'top'; // 'top' or 'bottom'
var PH_ANNOUNCE_DISMISSIBLE = true; // false = no close button, always shows
var PH_ANNOUNCE_END = '2026-11-27T23:59:59+00:00'; // sale end; keep the UTC offset (+00:00 = Irish/UK time in winter)
var PH_ANNOUNCE_HIDE_ON_END = true; // true = bar disappears once the timer hits zero
var PH_ANNOUNCE_ENDED_MSG = 'The sale has ended. Thanks for stopping by.'; // used only if PH_ANNOUNCE_HIDE_ON_END is false
var PH_ANNOUNCE_COUPON = 'BLACKFRIDAY30'; // leave blank to drop the coupon box, countdown-only
var STORAGE_KEY = 'ph-announce-bf-dismissed';
var bar = document.getElementById('ph-announce-bf');
var msg = document.getElementById('ph-announce-bf-msg');
var clock = document.getElementById('ph-announce-bf-clock');
var close = document.getElementById('ph-announce-bf-close');
var coupon = document.getElementById('ph-announce-bf-coupon');
var code = document.getElementById('ph-announce-bf-code');
var copy = document.getElementById('ph-announce-bf-copy');
if (!bar) return;
if (!PH_ANNOUNCE_DISMISSIBLE && close) close.remove();
if (coupon) {
if (PH_ANNOUNCE_COUPON) {
code.textContent = PH_ANNOUNCE_COUPON;
var copyTimer;
copy.addEventListener('click', function () {
function done() {
copy.textContent = 'Copied';
clearTimeout(copyTimer);
copyTimer = setTimeout(function () { copy.textContent = 'Copy'; }, 2000);
}
function fallback() { // older browsers / non-https: select the code so Cmd/Ctrl+C works
var range = document.createRange();
range.selectNodeContents(code);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
try { if (document.execCommand('copy')) done(); } catch (e) {}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(PH_ANNOUNCE_COUPON).then(done, fallback);
} else {
fallback();
}
});
} else {
coupon.remove();
coupon = null;
}
}
var dismissed = false;
try { dismissed = window.localStorage.getItem(STORAGE_KEY) === PH_ANNOUNCE_ID; } catch (e) {}
if (dismissed) return;
var endTime = new Date(PH_ANNOUNCE_END).getTime();
if (isNaN(endTime)) { clock.remove(); } // no valid date - just show the message, no timer
function pad(n) { return String(n).padStart(2, '0'); }
function tick() {
var diff = endTime - Date.now();
if (diff <= 0) {
clearInterval(timer);
if (PH_ANNOUNCE_HIDE_ON_END) {
bar.hidden = true;
if (PH_ANNOUNCE_POSITION === 'bottom') document.body.style.paddingBottom = '';
return;
}
if (clock) clock.remove();
if (coupon) coupon.remove();
if (msg) msg.textContent = PH_ANNOUNCE_ENDED_MSG;
return;
}
var d = Math.floor(diff / 86400000);
var h = Math.floor((diff % 86400000) / 3600000);
var m = Math.floor((diff % 3600000) / 60000);
var s = Math.floor((diff % 60000) / 1000);
var nums = clock.querySelectorAll('[data-unit]');
nums[0].textContent = pad(d);
nums[1].textContent = pad(h);
nums[2].textContent = pad(m);
nums[3].textContent = pad(s);
}
bar.setAttribute('data-position', PH_ANNOUNCE_POSITION);
bar.hidden = false;
if (PH_ANNOUNCE_POSITION === 'bottom') {
document.body.style.paddingBottom = bar.offsetHeight + 'px';
window.addEventListener('resize', function () {
document.body.style.paddingBottom = bar.offsetHeight + 'px';
});
}
var timer;
if (!isNaN(endTime) && clock) {
tick();
timer = setInterval(tick, 1000);
}
if (close) {
close.addEventListener('click', function () {
bar.hidden = true;
if (timer) clearInterval(timer);
if (PH_ANNOUNCE_POSITION === 'bottom') document.body.style.paddingBottom = '';
try { window.localStorage.setItem(STORAGE_KEY, PH_ANNOUNCE_ID); } catch (e) {}
});
}
})();
</script>
If you'd like a bar like this in your own colours, or something bigger built for your school, that's what Purple Hippo is for.
In short
- Create the coupon in Teachable first, with the same end date as the countdown.
- Paste the bar into a custom code block at the top of each page you want it on.
- Keep the time zone on the end time, and change the ID for each new sale.