I built an article collection page to collect and summarize articles I find interesting in the past. I no longer actively use and maintain it, but I thought the design and the code could still be interesting, so here it is.
Why make an article collection page?
I often come across interesting articles, but I tend to forget the details and struggle to find them later. I wanted a personal reference hub where I could collect articles, summarize them, and leave notes for future reference. It also serves as a way to share what I find interesting with others.
Design rationale
I wanted the article page to achieve the following:
- List all saved articles
- Show key fields: title, author, publication date, a summary, and a personal note.
- Support article filtering
- Classify entries with tags
I wanted to use tags instead of categories because I will likely have many related topics, which would make categorization into a single topic difficult. Tags allow for associating with multiple topics.
Tools and setup
Custom Post Type UI (CPT UI)
A custom post type was created to manage article entries separately from both standard blog posts and from one another. This approach supports cleaner organization and greater flexibility in content management.
Advanced Custom Fields (ACF)
Structured fields such as summary, author, and publication date were defined using ACF. These fields ensure consistent formatting across articles and allows for feature expansion if desired.
Custom PHP Template
A dedicated page template was developed using PHP. The content is dynamically populated based on entered entries, so the page updates automatically as new entries are added.
WP File Manager Plugin
This plugin provides direct access to theme files from within the WordPress admin panel, making it convenient to update PHP templates without using FTP.
Visual Studio Code
Code development were carried out in VS Code before deploying changes to the live WordPress site. This helped maintain code clarity and version control.
PHP codes
The PHP code I used for the article page template (2025/7/27) is shown below. The code has been updated since then, but I will leave the following version as it is to avoid additional update work.
This code is a result of vibe coding, written with ChatGPT. I find generative AI to be useful for non-experts like me in creating functional code segments. However, due to the difficulty of capturing my full idea in one prompt, and the limitations of ChatGPT’s output length, the coding process becomes modular, with inconsistencies and redundancies between code segments.
<?php
/* Template Name: Articles Page */
get_header(); ?>
<main id="main_content" class="l-mainContent l-article">
<div class="l-mainContent__inner" data-clarity-region="article">
<?php SWELL_Theme::get_parts( 'parts/page_head' ); ?>
<div class="post_content">
<?php
$tags = get_terms(array(
'taxonomy' => 'article_tag',
'hide_empty' => true,
));
// Slugs of tags to show by default
$default_tag_slugs = array('ngs', 'bioinformatics', 'biodiversity');
?>
<?php if ($tags): ?>
<form method="get" style="margin-bottom: 2em;">
<strong>Filter by tags:</strong><br>
<div class="tag-list">
<?php foreach ($tags as $tag): ?>
<?php
$slug = $tag->slug;
$is_default = in_array($slug, $default_tag_slugs);
$hidden_class = $is_default ? '' : 'tag-hidden';
$display_style = $is_default ? '' : 'style="display:none;"';
?>
<label <?php echo $display_style; ?> class="<?php echo $hidden_class; ?>" style="margin-right: 15px;">
<input type="checkbox" name="tags[]" value="<?php echo esc_attr($slug); ?>">
<?php echo esc_html($tag->name); ?>
</label>
<?php endforeach; ?>
</div>
<div class="tag-filter-actions" style="margin-top: 1em; display: flex; gap: 1em; align-items: center;">
<button type="button" id="toggle-tags">Show all tags</button>
<button type="submit">Apply Filter</button>
</div>
</form>
<?php endif; ?>
<div class="articles-list">
<?php
$selected_tags = isset($_GET['tags']) ? array_map('sanitize_text_field', (array) $_GET['tags']) : array();
$args = array(
'post_type' => 'article_summary',
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'DESC',
);
if (!empty($selected_tags)) {
$args['tax_query'] = array(
array(
'taxonomy' => 'article_tag',
'field' => 'slug',
'terms' => $selected_tags,
'operator' => 'AND',
),
);
}
$query = new WP_Query($args);
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post(); ?>
<div class="article-entry" style="margin-bottom: 2.5em; padding: 1em 0; border-bottom: 1px solid #ccc;">
<p><strong><?php the_title(); ?></strong></p>
<p><strong>Author:</strong> <?php the_field('author'); ?></p>
<!-- Read More Button -->
<a href="javascript:void(0);" class="expand-toggle show-more" style="display: inline-block; margin-bottom: 0.5em;">
Read More
</a>
<!-- Hidden Content -->
<div class="article-details" style="display: none;">
<p><strong>Date:</strong> <?php the_field('published'); ?></p>
<p><strong>Summary:</strong></p>
<div class="article-summary">
<?php the_content(); ?>
</div>
<?php
$link = get_field('link');
if ($link): ?>
<p>
<a href="<?php echo esc_url($link); ?>" target="_blank" rel="noopener noreferrer">
Read Full Article
</a>
</p>
<?php endif; ?>
<!-- Read Less Button (inside) -->
<a href="javascript:void(0);" class="expand-toggle show-less" style="display: inline-block; margin-top: 0.5em;">
Read Less
</a>
</div>
</div>
<?php endwhile;
wp_reset_postdata();
else : ?>
<p>No articles found.</p>
<?php endif; ?>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
const entries = document.querySelectorAll('.article-entry');
entries.forEach(function (entry) {
const showMoreBtn = entry.querySelector('.show-more');
const showLessBtn = entry.querySelector('.show-less');
const details = entry.querySelector('.article-details');
if (!showMoreBtn || !showLessBtn || !details) return;
showMoreBtn.addEventListener('click', function () {
details.style.display = 'block';
showMoreBtn.style.display = 'none';
});
showLessBtn.addEventListener('click', function () {
details.style.display = 'none';
showMoreBtn.style.display = 'inline-block';
});
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const toggleBtn = document.getElementById("toggle-tags");
const hiddenTags = document.querySelectorAll(".tag-hidden");
let isExpanded = false;
toggleBtn.addEventListener("click", function () {
isExpanded = !isExpanded;
hiddenTags.forEach(tag => {
tag.style.display = isExpanded ? "inline-block" : "none";
});
toggleBtn.textContent = isExpanded ? "Hide extra tags" : "Show all tags";
});
});
</script>
<?php get_footer(); ?>Points I paid attention to
Consistent Styling
I made sure that the layout and class names used in the template matched the styling conventions of my site’s existing SWELL theme. For example, the main structure uses SWELL’s layout classes like l-mainContent, l-mainContent__inner, and post_content to maintain a consistent look and feel.
Custom Tag Filtering
I wanted to filter articles by tags, but manage them separately from regular blog post tags. So, I created a custom taxonomy, article_tag, using CPT UI for the article post type. Rather than displaying all available tags at once, I chose to show only a few key tags by default. The remaining tags are hidden initially and can be revealed with a “Show all tags” button powered by JavaScript.
Minimal Article Preview
I wanted to initially show only the most essential metadata: the title and author, while hiding other information to avoid clutter. Clicking “Read More” expands the full entry, and clicking “Read Less” collapses it again.
Future improvements
As the number of tags grows, a more compact method for displaying and selecting them may be needed. Additionally, while the current styling is functional, it could be refined for a cleaner, more polished user experience.
