
By Yamuno Team
08 Apr 2026
10 min read
5 Things You Can Build with HTML Macro+ for Confluence
Confluence is a solid documentation platform. But the moment you need a colored status badge, a countdown to your launch date, or a table that readers can sort, you hit the limits of the built-in macros.
HTML Macro+ for Confluence fills that gap. It adds an HTML, CSS and JavaScript editor with live preview to any Confluence Cloud page, with admin controls for external content and for who can edit. It runs on Atlassian Forge, and your code is stored in Confluence. To insert it, edit a page, type /html and select HTML Macro+ for Confluence. Put each snippet below in the matching tab: HTML, CSS or JavaScript.
New to HTML in Confluence? Start with 5 ways to use HTML in Confluence Cloud. Here are five concrete things you can build.
1. Custom Status Badge or Traffic Light Indicator
The problem: Your project status page needs a RAG (Red/Amber/Green) indicator that's quick to read and easy for editors to update. Confluence's built-in status lozenge (/status) is fine for short colored labels inside text. Sometimes you want something larger and more visual at the top of a page.
The solution: A simple HTML/CSS badge with a colored circle and a label. Editors change one word to flip the status.
<!-- HTML -->
<div class="rag-badge">
<span class="dot green"></span>
<span class="label">On Track</span>
</div>
/* CSS */
.rag-badge {
display: flex;
align-items: center;
gap: 10px;
font-family: sans-serif;
font-size: 16px;
font-weight: 600;
}
.dot {
width: 18px;
height: 18px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.dot.green { background-color: #22c55e; box-shadow: 0 0 8px #22c55e88; }
.dot.amber { background-color: #f59e0b; box-shadow: 0 0 8px #f59e0b88; }
.dot.red { background-color: #ef4444; box-shadow: 0 0 8px #ef444488; }
.label { color: #1e293b; }
Why it's useful: Change the dot class to green, amber or red and update the label text. That's the entire update. Engineers, PMs and project leads can glance at a page and see where things stand. For a fuller version with several services, see how to build a custom status dashboard in Confluence.
2. Embedded Countdown Timer
The problem: Your team has a product launch date, a sprint end, or a compliance deadline coming up. A static date in the page body gets ignored. Teams want something that actively reminds them how much runway is left.
The solution: A JavaScript countdown that calculates the days, hours and minutes remaining until a target date, updating every second.
<!-- HTML -->
<div class="countdown-wrapper">
<div class="countdown-title">Launch Countdown</div>
<div class="countdown-timer">
<div class="unit"><span id="days">--</span><label>Days</label></div>
<div class="unit"><span id="hours">--</span><label>Hours</label></div>
<div class="unit"><span id="minutes">--</span><label>Minutes</label></div>
</div>
</div>
/* CSS */
.countdown-wrapper { font-family: sans-serif; text-align: center; padding: 24px; background: #f8fafc; border-radius: 12px; }
.countdown-title { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin-bottom: 16px; }
.countdown-timer { display: flex; justify-content: center; gap: 24px; }
.unit { display: flex; flex-direction: column; align-items: center; }
.unit span { font-size: 48px; font-weight: 700; color: #1e293b; line-height: 1; }
.unit label { font-size: 12px; color: #94a3b8; margin-top: 4px; text-transform: uppercase; letter-spacing: 0.06em; }
// JavaScript: set your target date here (example date, replace with yours)
const target = new Date("2027-03-01T09:00:00");
function tick() {
const diff = target - new Date();
if (diff <= 0) {
document.querySelector(".countdown-timer").innerHTML = "<p>We're live!</p>";
return;
}
document.getElementById("days").textContent = Math.floor(diff / 86400000);
document.getElementById("hours").textContent = Math.floor((diff % 86400000) / 3600000);
document.getElementById("minutes").textContent = Math.floor((diff % 3600000) / 60000);
}
tick();
setInterval(tick, 1000);
Why it's useful: Change one date string and you have a live countdown for any milestone: sprint reviews, feature freezes, go-live dates. The date above is only an example, so set your own before you publish. Once the date passes, the timer shows "We're live!" until you update it.
3. Interactive Decision Tree for Runbooks
The problem: Runbooks and incident response guides are often long, linear documents. Engineers in the middle of an incident don't want to read paragraphs. They want to answer a question and be told exactly what to do next.
The solution: A clickable HTML/CSS decision tree that walks users through branching logic step by step. No external charting library required.
<!-- HTML -->
<div class="tree" id="tree">
<div class="node active" id="q1">
<p>Is the service returning errors?</p>
<div class="choices">
<button data-show="q1-yes">Yes</button>
<button data-show="q1-no">No</button>
</div>
</div>
<div class="node" id="q1-yes">
<p>Are errors 5xx (server-side)?</p>
<div class="choices">
<button data-show="check-logs">Yes</button>
<button data-show="check-upstream">No</button>
</div>
</div>
<div class="node answer" id="check-logs">
<p>Check application logs in Datadog. Look for OOM errors or unhandled exceptions. Page the on-call engineer if needed.</p>
<button data-show="q1">Start over</button>
</div>
<div class="node answer" id="check-upstream">
<p>Likely a 4xx or gateway issue. Check upstream dependencies and API gateway logs.</p>
<button data-show="q1">Start over</button>
</div>
<div class="node answer" id="q1-no">
<p>Service appears healthy. Check monitoring dashboards for latency spikes or queue depth issues.</p>
<button data-show="q1">Start over</button>
</div>
</div>
/* CSS */
.tree { font-family: sans-serif; max-width: 480px; }
.node { display: none; padding: 20px; border: 1px solid #e2e8f0; border-radius: 10px; margin-bottom: 12px; background: #fff; }
.node.active { display: block; }
.node p { margin: 0 0 14px; font-size: 15px; color: #1e293b; font-weight: 500; }
.choices { display: flex; gap: 10px; }
button { padding: 8px 18px; border: none; border-radius: 6px; background: #6366f1; color: #fff; font-size: 14px; cursor: pointer; }
button:hover { background: #4f46e5; }
.answer { background: #f0fdf4; border-color: #86efac; }
.answer p { color: #166534; font-weight: 400; }
.answer button { background: #64748b; }
// JavaScript
function show(id) {
document.querySelectorAll(".node").forEach(n => n.classList.remove("active"));
document.getElementById(id).classList.add("active");
}
document.querySelectorAll("#tree button[data-show]").forEach(button => {
button.addEventListener("click", () => show(button.dataset.show));
});
Why it's useful: Runbook authors can build trees of any depth by adding nodes and pointing each button's data-show attribute at the next node's id. No diagramming tool is needed. The tree lives inside the Confluence page and can be updated by anyone with access to the macro editor.
4. Custom Data Table with Sort and Filter
The problem: Confluence tables are static. You can't click a column header to sort, and there's no filter input. For any table with more than a dozen rows, such as vendor lists, feature matrices or environment configs, that gets in the way.
The solution: A vanilla JavaScript snippet that adds click-to-sort to any HTML table, plus a live filter input.
<!-- HTML -->
<div class="table-wrapper">
<input id="filter" type="text" placeholder="Filter rows..." />
<table id="data-table">
<thead>
<tr>
<th>Service</th>
<th>Owner</th>
<th>Status</th>
<th>SLA</th>
</tr>
</thead>
<tbody>
<tr><td>Auth API</td><td>Platform</td><td>Healthy</td><td>99.9%</td></tr>
<tr><td>Billing</td><td>Payments</td><td>Degraded</td><td>99.5%</td></tr>
<tr><td>Notifications</td><td>Growth</td><td>Healthy</td><td>99.0%</td></tr>
<tr><td>Search</td><td>Platform</td><td>Healthy</td><td>99.9%</td></tr>
<tr><td>Analytics</td><td>Data</td><td>Maintenance</td><td>98.0%</td></tr>
</tbody>
</table>
</div>
/* CSS */
.table-wrapper { font-family: sans-serif; }
#filter { width: 100%; padding: 8px 12px; margin-bottom: 12px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { background: #f1f5f9; padding: 10px 14px; text-align: left; cursor: pointer; user-select: none; border-bottom: 2px solid #e2e8f0; color: #475569; }
th:hover { background: #e2e8f0; }
td { padding: 10px 14px; border-bottom: 1px solid #f1f5f9; color: #1e293b; }
tr:hover td { background: #f8fafc; }
th.asc::after { content: " ▲"; font-size: 11px; }
th.desc::after { content: " ▼"; font-size: 11px; }
// JavaScript
const table = document.getElementById("data-table");
const tbody = table.querySelector("tbody");
const headers = table.querySelectorAll("th");
let sortCol = -1, sortAsc = true;
headers.forEach((th, i) => {
th.addEventListener("click", () => {
if (sortCol === i) { sortAsc = !sortAsc; } else { sortCol = i; sortAsc = true; }
headers.forEach(h => h.classList.remove("asc", "desc"));
th.classList.add(sortAsc ? "asc" : "desc");
const rows = Array.from(tbody.querySelectorAll("tr"));
rows.sort((a, b) => {
const av = a.cells[i].textContent.trim();
const bv = b.cells[i].textContent.trim();
return sortAsc ? av.localeCompare(bv) : bv.localeCompare(av);
});
rows.forEach(r => tbody.appendChild(r));
});
});
document.getElementById("filter").addEventListener("input", function () {
const q = this.value.toLowerCase();
tbody.querySelectorAll("tr").forEach(row => {
row.style.display = row.textContent.toLowerCase().includes(q) ? "" : "none";
});
});
Why it's useful: Replace the <tbody> rows with your actual data and you have a fully sortable, filterable table with zero dependencies. Works for service registries, on-call rosters, feature flags and release checklists: anything that lives in a Confluence table but deserves better UX.
5. Branded Announcement Banner
The problem: HR needs to announce open enrollment. Marketing wants to flag a product launch to all readers of a space. The built-in Confluence info panel is functional, but it doesn't match company branding and can blend into the rest of the page.
The solution: A CSS-styled announcement banner with a company-colored left border, an icon, a headline and body text.
<!-- HTML -->
<div class="announcement">
<div class="announcement-icon">!</div>
<div class="announcement-body">
<div class="announcement-title">Open Enrollment: Action Required</div>
<div class="announcement-message">
Benefits enrollment is open until <strong>March 31, 2027</strong>.
Log in to Workday and complete your selections before the deadline.
Contact <a href="mailto:[email protected]">[email protected]</a> with questions.
</div>
</div>
</div>
/* CSS */
.announcement {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 18px 20px;
border-left: 5px solid #6366f1;
background: #eef2ff;
border-radius: 0 10px 10px 0;
font-family: sans-serif;
max-width: 680px;
}
.announcement-icon {
width: 28px;
height: 28px;
border-radius: 50%;
background: #6366f1;
color: #fff;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.announcement-title {
font-size: 15px;
font-weight: 700;
color: #3730a3;
margin-bottom: 6px;
}
.announcement-message {
font-size: 14px;
color: #1e293b;
line-height: 1.6;
}
.announcement-message a {
color: #6366f1;
text-decoration: none;
font-weight: 500;
}
.announcement-message a:hover { text-decoration: underline; }
Why it's useful: Change the border color and background tint to match your brand palette, swap in your own headline and message, and you have a callout that stands out. The same template works for IT alerts, policy updates, and event announcements.
A Note on Security
Embedding HTML and JavaScript in a documentation platform is something admins need to control. HTML Macro+ for Confluence has three modes for external content:
- Block all: no external resources (scripts, fonts, images, iframes) can load. All five examples above are self-contained and work in this mode.
- Whitelist only: only the domains you list can load resources. If your team uses Chart.js from a CDN or embeds a map, you add those domains and nothing else gets through.
- Allow all: any domain can load. Best kept for testing or fully trusted environments.
JavaScript you write in the JavaScript tab runs in every mode; the modes only restrict externally loaded resources. Combined with editor permissions, which can be granted to specific users or Confluence groups, you control who can change code while everyone with page access sees the rendered output. See the security settings and permissions docs.
Try It Free
All five examples work on the free app, and none of them load anything from outside Confluence.
HTML Macro+ for Confluence is free on the Atlassian Marketplace. Install it, add it to a page, and paste in one of the snippets above to see the live preview update as you type. For something more data-heavy, try embedding interactive charts with Chart.js, or browse the HTML Macro+ documentation.
Install HTML Macro+ for Confluence, free on the Atlassian Marketplace
Have a use case we didn't cover? We'd love to hear what your team builds. Reach out through our support portal.
Featured App
HTML Macro+
A free way to add HTML, CSS, JavaScript and iframes to Confluence Cloud pages, with live preview, version history and admin security controls.
Stay in the loop
Get product updates and tips straight to your inbox.
No spam, ever.
Related Articles
Confluence Inline Code & Code Blocks (Complete Guide)
How to add inline code and code blocks in Confluence Cloud: shortcuts, Markdown backticks, languages, line numbers, wrapping and fixes for common problems.
5 Ways to Use HTML in Confluence Cloud (With Examples)
Confluence Cloud has no built-in HTML macro. Five practical ways teams still use HTML, CSS and JavaScript in Cloud pages, with copy-paste examples.
What Confluence's Built-In PDF Export Can't Do
Confluence Cloud's built-in PDF export handles single pages and space exports. Here is where it gets hard, so you know when you've outgrown it.





