30 CSS Badges20 / 30

Pure CSSMIT licensed

Reading Time Indicator

Three states — not started, in progress, done. A 2px bar at the bottom fills to 100% on hover so the user can see what completion looks like. Clean type, no decoration.

Published

Live Demo
Try it

The code

<div class="bdg-20-stage">
  <div class="bdg-20-stack">
    <div class="bdg-20-badge" style="--bdg-20-progress: 0%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">2 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-unread"><span class="bdg-20-dot"></span>Not started</div>
    </div>

    <div class="bdg-20-badge" style="--bdg-20-progress: 55%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">7 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-progress"><span class="bdg-20-dot"></span>In progress</div>
    </div>

    <div class="bdg-20-badge" style="--bdg-20-progress: 100%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">14 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-done"><span class="bdg-20-dot"></span>Finished</div>
    </div>

    <div class="bdg-20-hint">↑ hover any badge to complete it</div>
  </div>
</div>
.bdg-20-stage {
  background: #fafaf7;
  padding: 60px 48px;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
}

.bdg-20-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: flex-start;
}

.bdg-20-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px 10px 12px;
  border: 1.5px solid #1a1612;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  position: relative;
  overflow: hidden;
  cursor: default;
}

.bdg-20-badge::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  background: #1a1612;
  width: var(--bdg-20-progress, 0%);
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

.bdg-20-badge:hover::after {
  width: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  .bdg-20-badge::after {
    transition: none;
  }
}

.bdg-20-icon {
  font-size: 16px;
  line-height: 1;
  flex-shrink: 0;
}

.bdg-20-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bdg-20-duration {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #1a1612;
  line-height: 1;
}

.bdg-20-label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #4a4239;
  line-height: 1;
}

.bdg-20-divider {
  width: 1px;
  height: 28px;
  background: #cfc6b1;
  flex-shrink: 0;
}

.bdg-20-status {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1.2;
}

.bdg-20-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
  margin-right: 4px;
  margin-bottom: 1px;
}

.bdg-20-status-done {
  color: #1d8f4a;
}

.bdg-20-status-progress {
  color: #b8442d;
}

.bdg-20-status-unread {
  color: #aaa;
}

.bdg-20-hint {
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 9px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #bbb;
  text-align: center;
  margin-top: 8px;
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Badge from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Reading Time Indicator
Source: https://codefronts.com/snippets/css-badges/reading-time-indicator/

Three states — not started, in progress, done. A 2px bar at the bottom fills to 100% on hover so the user can see what completion looks like. Clean type, no decoration.
## HTML
```html
<div class="bdg-20-stage">
  <div class="bdg-20-stack">
    <div class="bdg-20-badge" style="--bdg-20-progress: 0%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">2 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-unread"><span class="bdg-20-dot"></span>Not started</div>
    </div>

    <div class="bdg-20-badge" style="--bdg-20-progress: 55%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">7 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-progress"><span class="bdg-20-dot"></span>In progress</div>
    </div>

    <div class="bdg-20-badge" style="--bdg-20-progress: 100%">
      <span class="bdg-20-icon">⌛</span>
      <div class="bdg-20-main">
        <div class="bdg-20-duration">14 min</div>
        <div class="bdg-20-label">reading time</div>
      </div>
      <div class="bdg-20-divider"></div>
      <div class="bdg-20-status bdg-20-status-done"><span class="bdg-20-dot"></span>Finished</div>
    </div>

    <div class="bdg-20-hint">↑ hover any badge to complete it</div>
  </div>
</div>
```
## CSS
```css
.bdg-20-stage {
  background: #fafaf7;
  padding: 60px 48px;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
}

.bdg-20-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: flex-start;
}

.bdg-20-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px 10px 12px;
  border: 1.5px solid #1a1612;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  position: relative;
  overflow: hidden;
  cursor: default;
}

.bdg-20-badge::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  background: #1a1612;
  width: var(--bdg-20-progress, 0%);
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

.bdg-20-badge:hover::after {
  width: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  .bdg-20-badge::after {
    transition: none;
  }
}

.bdg-20-icon {
  font-size: 16px;
  line-height: 1;
  flex-shrink: 0;
}

.bdg-20-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bdg-20-duration {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #1a1612;
  line-height: 1;
}

.bdg-20-label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #4a4239;
  line-height: 1;
}

.bdg-20-divider {
  width: 1px;
  height: 28px;
  background: #cfc6b1;
  flex-shrink: 0;
}

.bdg-20-status {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1.2;
}

.bdg-20-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
  margin-right: 4px;
  margin-bottom: 1px;
}

.bdg-20-status-done {
  color: #1d8f4a;
}

.bdg-20-status-progress {
  color: #b8442d;
}

.bdg-20-status-unread {
  color: #aaa;
}

.bdg-20-hint {
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 9px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #bbb;
  text-align: center;
  margin-top: 8px;
}
```

How this works

A reading-time indicator badge: “5 min read” with a small book / clock / eye icon. The pattern Medium, Substack, The Atlantic, The New Yorker, Wired, and every long-form editorial site ships to set reader expectations.

Pure CSS. The badge is a small pill with an inline SVG icon + text. Reading time is computed at build time (typically Math.ceil(wordCount / 265)) and injected into the text content — no runtime JS needed.

Make it yours

  • Rebrand the pill colour from --pill-bg and --pill-fg on the wrapper.
  • Swap the icon — book (📖), clock (🕐), eye (👁), coffee (☕) all read as &ldquo;reading time&rdquo;.
  • Change the words-per-minute (WPM) baseline — 200 for slow readers, 265 (default), 300 for skimmers.
  • Add a &ldquo;progress&rdquo; variant showing how much of the article the user has read so far.
  • Include estimated audio-narration time as a secondary readout for accessibility.

Gotchas — read before shipping

  • WPM baselines vary by content type — dense academic vs light editorial can differ by 2×. Choose a baseline that matches your typical content.
  • Round up, not down — &ldquo;5 min read&rdquo; sets a more honest expectation than &ldquo;4.7 min read&rdquo;.
  • The baseline (typically 265 WPM per Nielsen Norman Group) is Western literacy — for non-Latin scripts (Chinese, Japanese, Arabic), adjust because character-per-minute differs.
  • Reading time is an editorial signal, not a promise — reader speed varies 3-4×.

Browser support

ChromeSafariFirefoxEdge
45+9+16+45+

CSS pills, inline SVG, and text content are universally supported.

Techniques used in this demo

Search CodeFronts

Loading…