UI Components Easy
Status Indicator
Online/offline/idle/busy status dots with pulse animation, badge variants, and text labels.
Open in Lab
MCP
css
Targets: HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 640px;
text-align: center;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2.5rem;
}
/* ── Sections ── */
.sections {
display: flex;
flex-direction: column;
gap: 2rem;
}
.section {
}
.section-heading {
font-size: 0.6875rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #334155;
margin-bottom: 1rem;
}
.row {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 1.5rem;
}
/* ── Status colors (shared) ── */
:root {
--online: #22c55e;
--idle: #f59e0b;
--busy: #ef4444;
--offline: #475569;
}
/* ── Dot ── */
.dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
position: relative;
flex-shrink: 0;
}
.dot--online {
background: var(--online);
}
.dot--idle {
background: var(--idle);
}
.dot--busy {
background: var(--busy);
}
.dot--offline {
background: var(--offline);
}
/* Pulse ring for online only */
.dot--online::after {
content: "";
position: absolute;
inset: -4px;
border-radius: 50%;
border: 2px solid var(--online);
animation: pulse 2s ease-out infinite;
}
@keyframes pulse {
0% {
opacity: 0.7;
transform: scale(1);
}
70% {
opacity: 0;
transform: scale(1.7);
}
100% {
opacity: 0;
transform: scale(1.7);
}
}
@media (prefers-reduced-motion: reduce) {
.dot--online::after {
animation: none;
}
}
.dot-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.dot-label {
font-size: 0.75rem;
color: #64748b;
}
/* ── Badge ── */
.badge {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.3125rem 0.75rem;
border-radius: 999px;
font-size: 0.8125rem;
font-weight: 600;
}
.badge-dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.badge--online {
background: rgba(34, 197, 94, 0.12);
color: var(--online);
}
.badge--online .badge-dot {
background: var(--online);
}
.badge--idle {
background: rgba(245, 158, 11, 0.12);
color: var(--idle);
}
.badge--idle .badge-dot {
background: var(--idle);
}
.badge--busy {
background: rgba(239, 68, 68, 0.12);
color: var(--busy);
}
.badge--busy .badge-dot {
background: var(--busy);
}
.badge--offline {
background: rgba(71, 85, 105, 0.18);
color: var(--offline);
}
.badge--offline .badge-dot {
background: var(--offline);
}
/* ── Avatar ── */
.avatar-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
position: relative;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8125rem;
font-weight: 700;
color: #f2f6ff;
letter-spacing: 0.02em;
border: 2px solid rgba(255, 255, 255, 0.08);
flex-shrink: 0;
}
.avatar-dot {
position: absolute;
top: 33px;
right: -2px;
width: 13px;
height: 13px;
border-radius: 50%;
border: 2.5px solid #050910;
}
.avatar-dot--online {
background: var(--online);
}
.avatar-dot--idle {
background: var(--idle);
}
.avatar-dot--busy {
background: var(--busy);
}
.avatar-dot--offline {
background: var(--offline);
}
/* Pulse ring on avatar online dot */
.avatar-dot--online::after {
content: "";
position: absolute;
inset: -3px;
border-radius: 50%;
border: 2px solid var(--online);
animation: pulse 2s ease-out infinite;
}
.avatar-name {
font-size: 0.75rem;
color: #64748b;
}<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Status Indicator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Status Indicator</h1>
<p class="demo-sub">Online, idle, busy, and offline — dot, badge, and avatar variants.</p>
<div class="sections">
<!-- Dot variants -->
<section class="section">
<h2 class="section-heading">Dot</h2>
<div class="row">
<div class="dot-item">
<span class="dot dot--online" role="img" aria-label="Online"></span>
<span class="dot-label">Online</span>
</div>
<div class="dot-item">
<span class="dot dot--idle" role="img" aria-label="Idle"></span>
<span class="dot-label">Idle</span>
</div>
<div class="dot-item">
<span class="dot dot--busy" role="img" aria-label="Busy"></span>
<span class="dot-label">Busy</span>
</div>
<div class="dot-item">
<span class="dot dot--offline" role="img" aria-label="Offline"></span>
<span class="dot-label">Offline</span>
</div>
</div>
</section>
<!-- Badge variants -->
<section class="section">
<h2 class="section-heading">Badge</h2>
<div class="row">
<span class="badge badge--online">
<span class="badge-dot" aria-hidden="true"></span>
Online
</span>
<span class="badge badge--idle">
<span class="badge-dot" aria-hidden="true"></span>
Idle
</span>
<span class="badge badge--busy">
<span class="badge-dot" aria-hidden="true"></span>
Busy
</span>
<span class="badge badge--offline">
<span class="badge-dot" aria-hidden="true"></span>
Offline
</span>
</div>
</section>
<!-- Avatar + dot -->
<section class="section">
<h2 class="section-heading">Avatar</h2>
<div class="row">
<div class="avatar-wrap">
<div class="avatar" style="background: #1e3a5f;" aria-label="Alex">AX</div>
<span class="avatar-dot avatar-dot--online" role="img" aria-label="Online"></span>
<span class="avatar-name">Alex</span>
</div>
<div class="avatar-wrap">
<div class="avatar" style="background: #3b1e5f;" aria-label="Blake">BL</div>
<span class="avatar-dot avatar-dot--idle" role="img" aria-label="Idle"></span>
<span class="avatar-name">Blake</span>
</div>
<div class="avatar-wrap">
<div class="avatar" style="background: #5f1e1e;" aria-label="Casey">CY</div>
<span class="avatar-dot avatar-dot--busy" role="img" aria-label="Busy"></span>
<span class="avatar-name">Casey</span>
</div>
<div class="avatar-wrap">
<div class="avatar" style="background: #1e2a3f;" aria-label="Drew">DR</div>
<span class="avatar-dot avatar-dot--offline" role="img" aria-label="Offline"></span>
<span class="avatar-name">Drew</span>
</div>
</div>
</section>
</div>
</div>
</body>
</html>Status Indicator
Presence and availability indicators for user avatars, system health dashboards, and service status pages. Three display formats — dot, badge, and avatar-with-dot — all CSS-only.
Status values
| Status | Color | Meaning |
|---|---|---|
| Online | Green | Active, connected, available |
| Idle | Amber | Away, inactive, low-priority |
| Busy | Red | Do not disturb, in a meeting |
| Offline | Grey | Disconnected, unavailable |
Variants
- Dot — standalone colored circle, optionally animated with a ring pulse
- Badge — pill-shaped chip with dot + label, for inline use in lists/tables
- Avatar dot — dot overlaid on an avatar image/initials at the corner
Animation
The online dot has a CSS @keyframes pulse ring — a fading scale-up of a ring element. Use prefers-reduced-motion media query to disable if needed.
Accessibility
- Status text is visually present as a label (not hidden)
- Color is never the sole differentiator — each status has a distinct label
- Use
aria-labelon the dot element when the label is elsewhere