Badge
Compact status labels rendered in the label font. All variants share .nf-badge as the base.
Variants
ALL VARIANTS
<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
<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
NOTIFICATIONS
- Deployment complete.UNREAD · JUST NOW
- New access request.UNREAD · 2 MIN AGO
- Backup ready for review.UNREAD · 5 MIN AGO
<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 |