Section Statistics

Display post counts, word counts, and reading time totals

3 min read 664 words

Display statistics about sections like post counts, total word count, and reading time.

Note

Partial Default Theme Support

Bengal's default theme includes some statistics:

  • Word count in docs meta (partials/docs-meta.html)
  • Post count in author listings

This recipe shows how to use section-level properties likesection.post_count,section.word_count, andsection.total_reading_time.

The Pattern

<div class="section-stats">
  <span>{{ section.post_count }} articles</span>
  <span>{{ section.word_count | commas }} words</span>
  <span>{{ section.total_reading_time }} min total</span>
</div>

What's Happening

Property Purpose
section.post_count Number of regular pages in section
section.post_count_recursive Including all subsections
section.word_count Total words across all pages (counts from rendered HTML content)
section.total_reading_time Sum of all reading times (minutes)

Note

Word Count Implementation

section.word_countcounts words from each page's rendered HTML content (with HTML tags stripped). This differs frompage.word_count, which counts words from the raw markdown source. For consistency, you can also sum individual page word counts:

{% let total_words = section.sorted_pages |> map(attribute='word_count') |> sum %}

Variations

<header class="section-header">
  <h1>{{ section.title }}</h1>
  <p class="section-description">{{ section.description }}</p>

  <ul class="stats">
    <li>
      <strong>{{ section.post_count }}</strong>
      <span>Articles</span>
    </li>
    <li>
      <strong>{{ section.word_count | commas }}</strong>
      <span>Words</span>
    </li>
    <li>
      <strong>{{ section.total_reading_time }}</strong>
      <span>Minutes to read all</span>
    </li>
  </ul>
</header>
<aside class="stats-widget">
  <h3>Blog Stats</h3>
  <dl>
    <dt>Total Posts</dt>
    <dd>{{ section.post_count_recursive }}</dd>

    <dt>Categories</dt>
    <dd>{{ section.subsections | length }}</dd>

    <dt>Total Words</dt>
    <dd>{{ section.word_count | commas }}</dd>

    <dt>Reading Time</dt>
    <dd>{{ section.total_reading_time }} min</dd>
  </dl>
</aside>
<div class="subsection-stats">
  <h2>Categories</h2>
  <table>
    <thead>
      <tr>
        <th>Category</th>
        <th>Posts</th>
        <th>Words</th>
        <th>Reading Time</th>
      </tr>
    </thead>
    <tbody>
    {% for sub in section.subsections |> sort_by('post_count', reverse=true) %}
      <tr>
        <td><a href="{{ sub.href }}">{{ sub.title }}</a></td>
        <td>{{ sub.post_count }}</td>
        <td>{{ sub.word_count | commas }}</td>
        <td>{{ sub.total_reading_time }} min</td>
      </tr>
    {% end %}
    </tbody>
  </table>
</div>
{% let max_posts = section.subsections |> map(attribute='post_count') |> max %}

<div class="category-bars">
{% for sub in section.subsections |> sort_by('post_count', reverse=true) %}
  <div class="category-bar">
    <a href="{{ sub.href }}">{{ sub.title }}</a>
    <div class="bar">
      <div class="fill" style="width: {{ (sub.post_count / max_posts * 100) | round }}%"></div>
    </div>
    <span>{{ sub.post_count }}</span>
  </div>
{% end %}
</div>
{% let blog = get_section('blog') %}
{% let docs = get_section('docs') %}

<footer class="site-stats">
  <h3>This Site</h3>
  <div class="stats-grid">
    <div class="stat">
      <span class="value">{{ blog.post_count_recursive }}</span>
      <span class="label">Blog Posts</span>
    </div>
    <div class="stat">
      <span class="value">{{ docs.post_count_recursive }}</span>
      <span class="label">Doc Pages</span>
    </div>
    <div class="stat">
      <span class="value">{{ (blog.word_count + docs.word_count) | commas }}</span>
      <span class="label">Total Words</span>
    </div>
  </div>
</footer>

Combine with author data:

{% let posts = section.regular_pages %}
{% let authors = posts |> map(attribute='author') |> uniq %}

<div class="author-stats">
  <h3>Contributors</h3>
  <ul>
  {% for author in authors if author %}
    {% let author_posts = posts |> where('author.name', author.name) %}
    <li>
      <span>{{ author.name }}</span>
      <span>{{ author_posts | length }} posts</span>
    </li>
  {% end %}
  </ul>
</div>

Example CSS

.section-stats {
  display: flex;
  gap: 2rem;
  padding: 1rem;
  background: var(--bg-secondary);
  border-radius: 8px;
}

.section-stats span {
  font-size: 0.875rem;
  color: var(--text-muted);
}

.stats {
  display: flex;
  gap: 2rem;
  list-style: none;
  padding: 0;
  margin: 1rem 0;
}

.stats li {
  text-align: center;
}

.stats strong {
  display: block;
  font-size: 2rem;
  color: var(--accent);
}

.stats span {
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* Progress bars */
.category-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.category-bar a {
  min-width: 120px;
}

.category-bar .bar {
  flex: 1;
  height: 8px;
  background: var(--bg-tertiary);
  border-radius: 4px;
  overflow: hidden;
}

.category-bar .fill {
  height: 100%;
  background: var(--accent);
}

/* Stats grid */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.stat {
  text-align: center;
  padding: 1rem;
  background: var(--bg-secondary);
  border-radius: 8px;
}

.stat .value {
  display: block;
  font-size: 1.5rem;
  font-weight: bold;
  color: var(--accent);
}

.stat .label {
  font-size: 0.875rem;
  color: var(--text-muted);
}

Seealso