Customize status page with CSS and JavaScript

You can add custom CSS and JavaScript to your status page to match your brand, add tracking scripts, or customize texts.

Prerequisites

  • Custom CSS requires a paid plan with the Custom CSS & JavaScript feature enabled.
  • Custom JavaScript requires a paid plan and a custom domain configured on your status page.

Settings location

Go to Better Stack → Status pages → select your status page → Settings → scroll to Custom CSS and Custom JavaScript.

Page background

The simplest way to start customizing is changing the background color:

Custom CSS
body {
  background: #f0f4f8;
}

.dark body {
  background: #141821;
}

The nav bar is translucent by default — it uses a semi-transparent white background with a backdrop blur. Here are some ways to adjust it.

If you'd like to change the nav background tint to match a colored page background, update the backdrop, the link hover states, and the subscribe button together:

Custom CSS — tinted nav
nav::before {
  background: #f0f4f8 !important;
  border-bottom-color: transparent !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* Nav links container (has its own white background) */
nav a:first-child + div {
  background: transparent !important;
  box-shadow: none !important;
  border-color: transparent !important;
}

/* Active nav link (current page) */
nav .bg-statuspage-neutral-60 {
  background: #fff !important;
}

/* Nav link hover */
nav a:first-child + div a > div:hover {
  background: #fff !important;
}

/* Subscribe button */
nav > div:last-child a {
  background: #fff !important;
  border-color: transparent !important;
  box-shadow: none !important;
}

/* Dark mode */
.dark nav::before {
  background: #141821 !important;
  border-bottom-color: transparent !important;
}

.dark nav a:first-child + div {
  background: transparent !important;
  box-shadow: none !important;
  border-color: transparent !important;
}

.dark nav .bg-statuspage-neutral-60 {
  background: #000 !important;
}

.dark nav a:first-child + div a > div:hover {
  background: #000 !important;
}

.dark nav > div:last-child a {
  background: #000 !important;
  border-color: transparent !important;
  box-shadow: none !important;
}

Subscribe button

If you'd like to change the subscribe button shape or color, you can target it independently. These small tweaks work on their own without changing the rest of the nav:

Custom CSS — pill-shaped button
nav > div:last-child a {
  border-radius: 999px;
}
Custom CSS — brand-colored button
nav > div:last-child a {
  background: #2563eb !important;
  color: #fff !important;
  border-color: #2563eb !important;
  box-shadow: none !important;
}

.dark nav > div:last-child a {
  background: #3b82f6 !important;
  color: #fff !important;
  border-color: #3b82f6 !important;
  box-shadow: none !important;
}

Sections

Each service group is wrapped in a .section element. You can adjust its background, spacing, or remove the default top border that separates sections:

Custom CSS
.section {
  background: #f8fafc;
  border-top: none !important;
  padding: 4px 12px;
  margin-top: 10px;
}

.dark .section {
  background: rgba(200, 180, 255, 0.03);
  border-top: none !important;
}

Uptime history bars

If you'd like to change the size of the history bars, adjust the height and border-radius on the .tick class:

Custom CSS
.tick {
  height: 24px !important;
  border-radius: 3px !important;
}

Theme examples

These combine the techniques above into cohesive themes. Pick one and paste it into Custom CSS.

Slate (light mode) Navy (dark mode)
body {
  background: #f0f4f8;
}

nav::before {
  background: rgba(240, 244, 248, 0.9) !important;
  border-bottom-color: #dde4ed !important;
}

.tick {
  height: 20px !important;
  border-radius: 2px !important;
}

/* Dark mode */
.dark body {
  background: #141821;
}

.dark nav::before {
  background: rgba(20, 24, 33, 0.9) !important;
  border-bottom-color: #252b3d !important;
}
html, body {
  background: #0b1120 !important;
  color: #c8d2e0 !important;
}

nav::before {
  background: rgba(11, 17, 32, 0.95) !important;
  border-bottom-color: #1a2540 !important;
}

h1 {
  color: #e4eaf4 !important;
}

.section-toggle:hover {
  background: #162036 !important;
}

/* Nav links (Status, Maintenance, Previous incidents) */
nav a:first-child ~ div div {
  color: #8896b0;
}

nav a:first-child ~ div div:hover {
  color: #e4eaf4 !important;
  background: #162036 !important;
}

/* Subscribe button */
nav > div:last-child a {
  background: #162036 !important;
  border-color: #1a2540 !important;
  color: #c8d2e0 !important;
}

.tick {
  height: 20px !important;
  border-radius: 2px !important;
}

footer {
  opacity: 0.4;
}

Selector reference

Here are the most commonly targeted elements organized by area.

Page layout Header and navigation Status overview Sections and resources Incidents and maintenance Subscribe popup
/* Full page background */
body { }

/* Main content container */
main { }
/* Navigation bar background (translucent pseudo-element) */
nav::before { }

/* Navigation bar layout and spacing */
nav { }

/* Company logo or name */
nav a:first-child { }

/* Navigation links container */
nav a:first-child ~ div { }

/* Individual navigation link pill */
nav a:first-child ~ div div { }

/* Subscribe button */
nav > div:last-child a { }
/* Status heading ("All services are online", etc.) */
h1 { }

/* "Updated" timestamp below the heading */
h1 + p { }

/* Status icon (SVG next to heading) */
h1 ~ svg { }

/* Announcement banner */
.prose-status-page { }
/* Section container (collapsible service group) */
.section { }

/* Section toggle button (expand/collapse) */
.section-toggle { }

/* Section status badge ("Operational", "Degraded", etc.) */
.section-status-badge { }

/* Individual resource row */
.section-content > div { }

/* Uptime history bar (colored tick) */
.tick { }

/* History time range ("90 days ago" / "Today") */
.section-content .leading-none { }
/* "Previous incidents" heading */
h3 { }

/* Maintenance banner link */
main > div:first-child a { }
/* Email/URL input field */
.subscribe-input { }

Your status page supports dark mode. Use the .dark selector to target dark mode, for example .dark body { background: #0d1117; }.

Custom fonts

Import a font from Google Fonts and apply it to the page:

Custom CSS
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

body {
  font-family: 'Inter', sans-serif;
}

Section expand and collapse

Override the default collapse behavior so all sections start expanded or stay permanently collapsed.

All sections expanded (CSS + JS) All sections collapsed (CSS + JS)
/* Custom CSS */
html:not(.sections-ready) .section-collapsed .section-content {
  height: auto !important;
  overflow: visible !important;
}

html:not(.sections-ready) .section-collapsed .section-chevron {
  display: none;
}

/* Custom JavaScript */
document.addEventListener("DOMContentLoaded", function () {
  document.querySelectorAll(".section-collapsed").forEach(function (el) {
    el.classList.remove("section-collapsed");
  });
  document.documentElement.classList.add("sections-ready");
});
/* Custom CSS */
html:not(.sections-ready) .section .section-content {
  height: 0;
  overflow: hidden;
}

/* Custom JavaScript */
document.addEventListener("DOMContentLoaded", function () {
  document.querySelectorAll(".section:not(.section-collapsed)").forEach(function (el) {
    el.classList.add("section-collapsed");
  });
  document.documentElement.classList.add("sections-ready");
});

Hide elements

Use display: none to remove any element from view:

Custom CSS
/* Hide the footer */
footer { display: none; }

/* Hide the subscribe button */
nav > div:last-child a { display: none; }

/* Hide uptime history bars and time axis — shows only the SLA percentage */
.tick { display: none; }
.section-content .leading-none { display: none; }

External tracking scripts

Google Analytics has a dedicated field — you do not need custom JavaScript for it. Go to Status pageAdvanced settingsAnalytics and paste your tag ID into the Google tag ID field.

For other analytics providers, use the Custom JavaScript field. Paste the full <script> tag:

Custom JavaScript — Plausible Analytics
<script defer data-domain="status.example.com" src="https://plausible.io/js/script.js"></script>
Custom JavaScript — Fathom Analytics
<script src="https://cdn.usefathom.com/script.js" data-site="ABCDEFGH" defer></script>

Custom JavaScript only runs on status pages with a custom domain configured. It will not run on the default *.betterstack.com subdomain.

Subscribe component picker

When a visitor clicks Get updates, the subscribe popup opens with a general email field. If you'd like the "Notify me about specific components" checkbox to be pre-selected so the component list is visible right away, add this to Custom JavaScript:

Custom JavaScript
document.addEventListener("DOMContentLoaded", function () {
  var button = document.querySelector(
    'a[data-action*="status-pages-v2--subscribe#handleToggleClick"]'
  );

  if (button) {
    button.addEventListener("click", function () {
      var checkbox = document.getElementById("specific_resources");
      if (checkbox && !checkbox.checked) {
        checkbox.checked = true;
        checkbox.dispatchEvent(new Event("change"));
      }

      var components = document.querySelector(
        '[data-target="status-pages-v2--subscribe.specificComponents"]'
      );
      if (components) {
        components.style.display = "block";
      }
    });
  }
});

Intro card

You can inject a description card that appears between the status heading and your service sections. This is useful for telling visitors what the page monitors, how to get help, or where to subscribe.

The example uses both Custom CSS for the card styling and Custom JavaScript to inject it into the page. It also includes a MutationObserver so the card persists through auto-refreshes.

Paste this into Custom CSS:

Custom CSS
.company-description {
  max-width: 700px;
  margin: 1.5rem auto 0;
  padding: 1.5rem 2rem;
  background: rgba(249, 250, 251, 0.6);
  border: 1px solid rgba(209, 213, 219, 0.4);
  border-radius: 12px;
  font-size: 0.9375rem;
  line-height: 1.7;
  color: #4b5563;
  text-align: center;
  backdrop-filter: blur(8px);
  transition: all 0.3s ease;
}

.dark .company-description {
  background: rgba(31, 41, 55, 0.5);
  border-color: rgba(75, 85, 99, 0.4);
  color: #d1d5db;
}

.company-description:hover {
  border-color: rgba(156, 163, 175, 0.5);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.dark .company-description:hover {
  border-color: rgba(107, 114, 128, 0.5);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

.company-description strong {
  color: #1f2937;
  font-weight: 600;
}

.dark .company-description strong {
  color: #f9fafb;
}

.company-description a {
  color: #2563eb;
  text-decoration: none;
  font-weight: 500;
  transition: color 0.2s ease;
}

.company-description a:hover {
  color: #1d4ed8;
  text-decoration: underline;
}

.dark .company-description a {
  color: #60a5fa;
}

.dark .company-description a:hover {
  color: #93c5fd;
}

.company-description-title {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  font-size: 1rem;
  color: #374151;
  margin-bottom: 0.75rem;
}

.dark .company-description-title {
  color: #e5e7eb;
}

.company-icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
  opacity: 0.7;
}

.company-description-text p {
  margin: 0;
}

.company-description-text p + p {
  margin-top: 0.75rem;
}

@media (max-width: 640px) {
  .company-description {
    margin: 1rem 1rem 0;
    padding: 1rem;
    font-size: 0.875rem;
  }
}

Paste this into Custom JavaScript:

Custom JavaScript
<script>
  document.addEventListener("DOMContentLoaded", function () {
    function injectDescription() {
      var anchor = document.querySelector(".mt-16.mb-12.sm\\:mt-20 p.mt-3.font-medium");
      if (!anchor || document.querySelector(".company-description")) return;

      var card = document.createElement("div");
      card.className = "company-description";
      card.innerHTML =
        '<div class="company-description-title">' +
          '<svg class="company-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">' +
            '<path d="M10 2C5.58 2 2 5.58 2 10s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm3.71 6.71a1 1 0 0 1-1.42 0L9 10.59 7.71 9.29a1 1 0 0 0-1.42 1.42l2 2a1 1 0 0 0 1.42 0l4-4a1 1 0 0 0 0-1.42z"/>' +
          "</svg>" +
          "About this status page" +
        "</div>" +
        '<div class="company-description-text">' +
          "<p>Real-time monitoring for <strong>Acme Corp</strong> production infrastructure with automatic incident detection.</p>" +
          '<p><strong>Subscribe above</strong> for instant alerts, or visit our <a href="https://example.com/support" target="_blank" rel="noopener">Support Center</a> for assistance.</p>' +
        "</div>";

      anchor.parentNode.insertBefore(card, anchor.nextSibling);
    }

    injectDescription();

    var main = document.querySelector("main.container");
    if (main) {
      new MutationObserver(function () {
        if (!document.querySelector(".company-description")) injectDescription();
      }).observe(main, { childList: true, subtree: true });
    }
  });
</script>

Replace "Acme Corp", the title text, and the support link with your own company details.

Live chat widget

If you'd like to embed a support chat on your status page, paste the provider's snippet into Custom JavaScript. Here are examples for common providers:

Custom JavaScript — Intercom
<script>
  window.intercomSettings = {
    api_base: "https://api-iam.intercom.io",
    app_id: "YOUR_APP_ID"
  };

  (function () {
    var w = window;
    var ic = w.Intercom;
    if (typeof ic === "function") {
      ic("reattach_activator");
      ic("update", w.intercomSettings);
    } else {
      var d = document;
      var i = function () { i.c(arguments); };
      i.q = [];
      i.c = function (args) { i.q.push(args); };
      w.Intercom = i;
      var l = function () {
        var s = d.createElement("script");
        s.type = "text/javascript";
        s.async = true;
        s.src = "https://widget.intercom.io/widget/YOUR_APP_ID";
        var x = d.getElementsByTagName("script")[0];
        x.parentNode.insertBefore(s, x);
      };
      if (document.readyState === "complete") {
        l();
      } else {
        w.addEventListener("load", l, false);
      }
    }
  })();
</script>
Custom JavaScript — Crisp
<script>
  window.$crisp = [];
  window.CRISP_WEBSITE_ID = "YOUR_WEBSITE_ID";
  (function () {
    var s = document.createElement("script");
    s.src = "https://client.crisp.chat/l.js";
    s.async = true;
    document.head.appendChild(s);
  })();
</script>

Replace YOUR_APP_ID or YOUR_WEBSITE_ID with the value from your chat provider's dashboard.

Status indicator colors

If you'd like to change the colors used for operational, degraded, downtime, or maintenance states, override the status page color variables in Custom CSS:

Custom CSS
.bg-statuspage-green {
  background-color: #059669 !important;
}

.bg-statuspage-yellow {
  background-color: #d97706 !important;
}

.bg-statuspage-red {
  background-color: #dc2626 !important;
}

.bg-statuspage-blue {
  background-color: #2563eb !important;
}

These classes apply to uptime history bars, status icons, and status badges throughout the page. The hover variants follow automatically since they use opacity.

To change the text color of status labels like "Operational" or "Degraded", target the corresponding text classes:

Custom CSS
.text-statuspage-green {
  color: #059669 !important;
}

.text-statuspage-yellow {
  color: #d97706 !important;
}

.text-statuspage-red {
  color: #dc2626 !important;
}

.text-statuspage-blue {
  color: #2563eb !important;
}

Auto-refreshing content

The status page refreshes dynamic sections every 30 seconds. Any DOM changes made by your JavaScript will be overwritten after each refresh.

To make changes persist, use a MutationObserver that re-applies modifications whenever the content updates:

Custom JavaScript
var observer = new MutationObserver(function () {
  var heading = document.querySelector("h1");
  if (heading && heading.textContent === "All services are online") {
    heading.textContent = "Everything is running smoothly";
  }
});

observer.observe(document.body, { childList: true, subtree: true });

CSS changes are not affected by auto-refresh. Only use a MutationObserver if you are modifying the page with JavaScript.

API access

You can set custom_css and custom_javascript through the Status Page API:

Update custom CSS via the API
curl -X PATCH "https://uptime.betterstack.com/api/v2/status-pages/{status_page_id}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"custom_css": "body { background: #f0f4f8; }"}'


Before using custom CSS

Many common customizations are available in the status page settings without writing any code:

  • Branding — upload separate light and dark mode logos, a custom favicon, and set a company URL for the logo link.
  • White-labeling — remove the "Powered by Better Stack" footer.
  • Navigation links — replace the default "Status", "Maintenance", and "Previous incidents" tabs with up to 4 custom links.
  • Translations — adjust any text on the page, including headings, status labels, and timestamp formats.
  • History length — show 7, 14, 30, 90, 180, or 365 days of uptime history.
  • Minimum incident length — filter out short incidents from the history bars.
  • Header layout — choose between vertical and horizontal layouts.
  • Theme — choose light, dark, or system (follows the visitor's OS preference).
  • Password protection and IP allowlisting — restrict access to the page.
  • Hide from search engines — prevent indexing.
  • Announcements — display a banner with optional subscriber notifications.

Tips

  • Use browser developer tools (right-click → Inspect) to find selectors and test changes in the Styles panel before saving.
  • Add !important to declarations that do not take effect — the status page uses Tailwind utility classes.
  • Changes take effect within seconds. No deploy or restart is needed.
  • Test on a non-production status page first, and check on mobile — the navigation collapses into a dropdown on smaller screens.

Need help?

Let us know at hello@betterstack.com.