NETFRAME_DOCS // BADGE
GITHUB

Badge

Compact status labels rendered in the label font. All variants share .nf-badge as the base.

Variants

ALL VARIANTS

ONLINECRITICALINFOERROROUTLINEGHOST
<span class="nf-badge nf-badge-primary">ONLINE</span>
<span class="nf-badge nf-badge-secondary">CRITICAL</span>
<span class="nf-badge nf-badge-tertiary">INFO</span>
<span class="nf-badge nf-badge-error">ERROR</span>
<span class="nf-badge nf-badge-outline">OUTLINE</span>
<span class="nf-badge nf-badge-ghost">GHOST</span>

Dot badge

The square marker means new or unread notifications are waiting. It is not a severity label or a count. The larger colour centre, dark keyline and outer frame keep it distinct from the element underneath—without flashing or pulsing.

Pair it with text that explains the state. Remove .nf-badge-dot when everything is read; don’t just change its colour.

UNREAD VERSUS ALL READ

UNREAD ALERTSALL READ
<span class="nf-badge nf-badge-outline nf-badge-dot">UNREAD ALERTS</span>
<span class="nf-badge nf-badge-ghost">ALL READ</span>

On a notification control

Open ALERTS to review three sample notifications. Click it again, click outside, press Escape, or tab out to close the panel. Opening does not mark items as read: MARK ALL READ clears the dot, updates the count and announces the new state. The preview reserves space so opening the panel does not recenter the control or push it over the code.

NOTIFICATION CONTROL

3 unread notifications

<div class="notification-demo" data-notifications>
  <button type="button" class="nf-button nf-button-outline nf-badge-dot" data-notification-trigger aria-expanded="false" aria-controls="notification-panel">
    ALERTS <span class="notification-trigger-count" data-notification-count aria-hidden="true">3</span><span class="nf-sr-only" data-notification-label> — 3 unread notifications</span>
  </button>
  <p class="notification-status" data-notification-status role="status">3 unread notifications</p>
  <section class="notification-panel" id="notification-panel" aria-labelledby="notification-heading" hidden>
    <h3 class="notification-heading" id="notification-heading">NOTIFICATIONS</h3>
    <ul class="notification-list" aria-label="Notifications">
      <li data-unread><strong>Deployment complete.</strong><span class="notification-item-state">UNREAD · JUST NOW</span></li>
      <li data-unread><strong>New access request.</strong><span class="notification-item-state">UNREAD · 2 MIN AGO</span></li>
      <li data-unread><strong>Backup ready for review.</strong><span class="notification-item-state">UNREAD · 5 MIN AGO</span></li>
    </ul>
    <div class="notification-footer"><button type="button" class="nf-button nf-button-ghost" data-mark-read>MARK ALL READ</button></div>
  </section>
</div>

<style>
/* Docs-only layout for the notification example; not a library component. */
.notification-demo {
  width: 100%;
  max-width: 26rem;
  min-width: 0;
  min-height: 28rem;
  text-align: left;
}

.notification-demo .notification-trigger-count {
  padding: 0 var(--nf-space-2);
  border-left: 1px solid currentColor;
  font-size: var(--nf-text-sm);
}

.notification-demo .notification-status {
  margin-top: var(--nf-space-3);
  color: var(--nf-color-text-muted);
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-sm);
}

.notification-demo .notification-panel {
  margin-top: var(--nf-space-4);
  border: 2px solid var(--nf-color-primary);
  background: var(--nf-color-bg);
  box-shadow: 4px 4px 0 var(--nf-color-primary);
}

.notification-demo .notification-panel[hidden] { display: none; }

.notification-demo .notification-heading {
  margin: 0;
  padding: var(--nf-space-4);
  border-bottom: 1px solid var(--nf-color-border);
  font-size: var(--nf-text-sm);
  letter-spacing: var(--nf-tracking-display);
  color: var(--nf-color-primary);
}

.notification-demo .notification-list { margin: 0; padding: 0; list-style: none; }

.notification-demo .notification-list li {
  display: grid;
  gap: var(--nf-space-1);
  padding: var(--nf-space-3) var(--nf-space-4);
  border-bottom: 1px solid var(--nf-color-border);
}

.notification-demo .notification-list strong {
  font-size: var(--nf-text-sm);
  font-weight: var(--nf-font-weight-medium);
  color: var(--nf-color-text);
}

.notification-demo .notification-item-state {
  color: var(--nf-color-text-subtle);
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-xs);
  letter-spacing: var(--nf-tracking-wide);
}

.notification-demo [data-unread] .notification-item-state { color: var(--nf-color-secondary); }

.notification-demo .notification-footer { padding: var(--nf-space-4); }

.notification-demo .notification-footer button {
  max-width: 100%;
  white-space: normal;
  text-align: center;
}

</style>

JavaScript

The HTML and demo-only CSS above plus this optional controller make the example work. NetFrame itself remains CSS-only. Use unique panel and heading IDs if you render more than one notification control.

document.querySelectorAll('[data-notifications]').forEach((root) => {
  const trigger = root.querySelector('[data-notification-trigger]');
  const panel = root.querySelector('.notification-panel');
  const status = root.querySelector('[data-notification-status]');
  const count = root.querySelector('[data-notification-count]');
  const label = root.querySelector('[data-notification-label]');
  const markRead = root.querySelector('[data-mark-read]');

  function setOpen(open) {
    trigger.setAttribute('aria-expanded', String(open));
    panel.hidden = !open;
  }

  trigger.addEventListener('click', () => {
    setOpen(trigger.getAttribute('aria-expanded') !== 'true');
  });

  markRead.addEventListener('click', () => {
    if (markRead.getAttribute('aria-disabled') === 'true') return;
    root.querySelectorAll('[data-unread]').forEach((item) => {
      item.removeAttribute('data-unread');
      const state = item.querySelector('.notification-item-state');
      state.textContent = state.textContent.replace('UNREAD', 'READ');
    });
    trigger.classList.remove('nf-badge-dot');
    count.textContent = '0';
    label.textContent = ' — no unread notifications';
    status.textContent = 'All notifications read';
    markRead.textContent = 'ALL READ';
    markRead.setAttribute('aria-disabled', 'true');
  });

  document.addEventListener('click', (event) => {
    if (!root.contains(event.target)) setOpen(false);
  });
  document.addEventListener('keydown', (event) => {
    if (event.key !== 'Escape' || trigger.getAttribute('aria-expanded') !== 'true') return;
    event.preventDefault();
    setOpen(false);
    trigger.focus();
  });
  root.addEventListener('focusout', (event) => {
    if (!root.contains(event.relatedTarget)) setOpen(false);
  });
});

The pseudo-element is decorative and cannot provide an accessible notification message on its own. For changing unread counts, update the visible and accessible text together; use a live region only when an announcement is needed. Keep overflow visible around the marker. Optional custom properties: --nf-badge-dot-size (default 0.75rem) and --nf-badge-dot-color (default error token).

Class reference

Class Description
nf-badge Base — required on all badges
nf-badge-primary Terminal Green fill
nf-badge-secondary Cyber Magenta fill
nf-badge-tertiary Tertiary fill
nf-badge-error Error red fill
nf-badge-outline Transparent with primary border
nf-badge-ghost Low-opacity primary tint, no border
nf-badge-dot Framed square unread-notification marker — add to its parent, pair with state text