Integrate an Interactive "Latest Posts" Widget with Pinning Feature (Multi-Platform Guide)
Install an Interactive Latest Posts Widget with Persistent Visual Pinning (Multi-Platform Guide)
Imagine offering your visitors more than just titles. Picture a "Latest Posts" widget showcasing eye-catching thumbnails, revealing quick "Read" or "Pin" actions on hover, and even remembering which posts a user visually pinned when they return to your site! This isn't just about aesthetics; it's about creating an interactive element that encourages exploration and helps users connect with the most relevant content.
This comprehensive guide is your key to unlocking that potential. We provide the complete code and step-by-step integration instructions for an enhanced "Latest Posts" widget featuring:
Attractive Thumbnails: Catch the eye instantly.Interactive Hover Actions: Offer quick "Read" and "Pin" options.Persistent Visual Pinning: Uses browser storage so users' pinned choices are remembered across visits (on the same browser).CMS Control: You can pre-define featured posts using simple tags or labels in your backend (like Blogger, WordPress, Shopify, Joomla, Drupal, Squarespace, etc.).
Let's get this powerful tool, "Latest Posts" Widget with Pinning Feature, working on your site!
The Widget Code of the latest post pinning:
First, here is the complete code block you'll need. It includes the necessary HTML structure, the JavaScript logic for fetching posts and handling the interactive features, and the CSS for styling. Copy this entire block.
<!-- Latest Posts Widget with Thumbnails, Hover Actions & VISUAL PERSISTENT Pinning (Bootstrap Enhanced) -->
<div class="recent-posts-widget card mb-4">
<div class="card-header bg-primary text-white">
Latest Posts
</div>
<div class="card-body">
<ul id="recent-posts-with-thumbs" class="list-unstyled">
<!-- Loading Indicator (Optional but Recommended) -->
<li id="loading-indicator" style="text-align: center; padding: 15px; color: #6c757d;">Loading posts...</li>
<!-- The latest posts will load here -->
</ul>
<div class="widget-credit">
Widget by <a href="https://www.seosiri.com/" target="_blank" rel="noopener noreferrer" title="SEO & Content Strategy Insights">SEOsiri.com</a>
</div>
</div> <!-- Closing card-body -->
<script type="text/javascript">
//<![CDATA[
// --- Configuration ---
const pinnedLabel = "Pinned"; // <<< CHANGE THIS Label to match the one you use in your CMS
const numberOfPosts = 5; // How many total posts to show
const defaultThumb = "https://via.placeholder.com/72"; // Default thumbnail
const localStorageKey = 'userPinnedPostsWidget'; // Key for storing pinned IDs
// --- End Configuration ---
function recentPostsWithThumbs(json) {
const postList = document.getElementById("recent-posts-with-thumbs");
const loadingIndicator = document.getElementById("loading-indicator");
postList.innerHTML = ''; // Clear
// --- Feed Structure Check (Basic) ---
// Adapt this check based on YOUR CMS's feed/API response structure
if (!json || !json.feed || !json.feed.entry || json.feed.entry.length === 0) {
postList.innerHTML = '<li style="text-align: center; color: #6c757d;">No posts found or invalid feed format.</li>';
console.warn("Feed data missing or in unexpected format:", json);
return;
}
// --- End Feed Structure Check ---
// --- Get user's locally pinned posts ---
let userPinnedIds = new Set();
try {
userPinnedIds = new Set(JSON.parse(localStorage.getItem(localStorageKey) || '[]'));
} catch (e) {
console.error("Error reading pinned posts from localStorage:", e);
localStorage.removeItem(localStorageKey); // Clear corrupted data
}
// ---
const entries = json.feed.entry; // Assumes Blogger structure, **ADAPT FOR YOUR CMS**
let cmsPinnedPosts = []; // Store entries pinned via CMS label
let regularPosts = []; // Store other entries
let postsDataMap = new Map(); // Store data for easier lookup later
let postsProcessed = 0;
for (let i = 0; i < entries.length && postsProcessed < (numberOfPosts + 10); i++) { // Fetch more to ensure enough non-pinned available
const entry = entries[i];
// --- Data Extraction (NEEDS ADAPTATION FOR YOUR SPECIFIC CMS) ---
const postTitle = (entry.title && entry.title.$t) ? entry.title.$t : null; // Use null if no title
if (!postTitle) continue; // Skip entries without titles
const postDate = (entry.published && entry.published.$t) ? entry.published.$t.substring(0, 10) : "";
let postLink = '#';
let postImage = defaultThumb;
let postId = `post-${i}`; // Fallback ID
if (entry.link) { // Adapt link extraction
for (let j = 0; j < entry.link.length; j++) {
if (entry.link[j].rel == "alternate") {
postLink = entry.link[j].href;
try { // Create a more stable ID
const urlParts = postLink.split('/');
const lastPart = urlParts[urlParts.length - 1];
if (lastPart) postId = `post-${lastPart.split('.')[0].replace(/[^a-zA-Z0-9-_]/g, '')}`;
} catch(e) { /* Ignore */ }
break;
}
}
}
if (entry.media$thumbnail && entry.media$thumbnail.url) { // Adapt thumbnail extraction
postImage = entry.media$thumbnail.url.replace(/\/s\d+(\-c)?\//, '/s100-c/');
}
const hasCMSPinnedLabel = (entry.category && Array.isArray(entry.category)) ?
entry.category.some(cat => cat.term === pinnedLabel) : false; // Adapt category/tag check
// --- End Data Extraction Adaptation ---
const postData = { postId, postTitle, postDate, postLink, postImage, hasCMSPinnedLabel };
postsDataMap.set(postId, postData); // Store data by ID
if (hasCMSPinnedLabel) {
cmsPinnedPosts.push(postData);
} else {
regularPosts.push(postData);
}
postsProcessed++;
}
// --- Combine and Sort Posts ---
let combinedPosts = [];
// 1. Add posts pinned by user (from localStorage) that WEREN'T pinned by CMS
userPinnedIds.forEach(id => {
if (postsDataMap.has(id) && !postsDataMap.get(id).hasCMSPinnedLabel) {
combinedPosts.push(postsDataMap.get(id));
postsDataMap.delete(id); // Remove from map to avoid duplicates
}
});
// 2. Add posts pinned by CMS (already separated)
cmsPinnedPosts.forEach(postData => {
if (postsDataMap.has(postData.postId)) { // Check if not already added by user pin
combinedPosts.push(postData);
postsDataMap.delete(postData.postId);
}
});
// 3. Add remaining regular posts until numberOfPosts is reached
regularPosts.forEach(postData => {
if (combinedPosts.length < numberOfPosts && postsDataMap.has(postData.postId)) {
combinedPosts.push(postData);
postsDataMap.delete(postData.postId);
}
});
// Ensure we don't exceed numberOfPosts due to pinning logic
combinedPosts = combinedPosts.slice(0, numberOfPosts);
// --- Build Final HTML ---
let finalHTML = '';
combinedPosts.forEach(postData => {
const isVisuallyPinned = postData.hasCMSPinnedLabel || userPinnedIds.has(postData.postId);
const liClasses = `media mb-3 post-item has-actions-on-hover ${isVisuallyPinned ? 'pinned-post' : ''}`;
const pinIconHTML = isVisuallyPinned ? '<span class="pin-icon" aria-hidden="true">📌</span> ' : '';
const pinButtonText = isVisuallyPinned ? 'Unpin' : 'Pin';
finalHTML += `
<li class="${liClasses}" data-post-id="${postData.postId}">
<div class="post-content-area">
<a href="${postData.postLink}" tabindex="-1">
<img src="${postData.postImage}" class="mr-3 rounded post-thumbnail" alt="${postData.postTitle}" width="72" height="72" loading="lazy">
</a>
<div class="media-body post-details">
<h6 class="mt-0 mb-1 post-title">
${pinIconHTML}
<a href="${postData.postLink}" class="text-decoration-none post-title-link">${postData.postTitle}</a>
</h6>
<small class="text-muted post-date">${postData.postDate}</small>
</div>
</div>
<div class="post-actions">
<a href="${postData.postLink}" class="btn btn-sm btn-outline-primary action-button read-button">Read</a>
<button class="btn btn-sm ${isVisuallyPinned ? 'btn-outline-danger' : 'btn-outline-secondary'} action-button pin-toggle-button" data-post-id="${postData.postId}" aria-label="${pinButtonText} Post">${pinButtonText}</button>
</div>
</li>`;
});
postList.innerHTML = finalHTML;
// Attach Event Listeners
attachPinToggleListeners();
}
// Function to Attach Event Listeners for Pinning (with localStorage)
function attachPinToggleListeners() {
const listContainer = document.getElementById("recent-posts-with-thumbs");
if (!listContainer) return;
let userPinnedIds = new Set(JSON.parse(localStorage.getItem(localStorageKey) || '[]'));
listContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('pin-toggle-button')) {
event.preventDefault();
const button = event.target;
const targetPostId = button.getAttribute('data-post-id');
const listItem = listContainer.querySelector(`li[data-post-id="${targetPostId}"]`);
if (listItem) {
const isBecomingPinned = !listItem.classList.contains('pinned-post');
listItem.classList.toggle('pinned-post');
const postTitleElement = listItem.querySelector('.post-title');
if (!postTitleElement) return;
if (isBecomingPinned) {
userPinnedIds.add(targetPostId); // Add to Set
// Add icon
if (!postTitleElement.querySelector('.pin-icon')) {
const newPinIcon = document.createElement('span');
newPinIcon.className = 'pin-icon';
newPinIcon.setAttribute('aria-hidden', 'true');
newPinIcon.innerHTML = '📌 '; // Add space after
postTitleElement.insertBefore(newPinIcon, postTitleElement.firstChild);
}
// Update button
button.textContent = 'Unpin'; button.setAttribute('aria-label', 'Unpin Post');
button.classList.remove('btn-outline-secondary'); button.classList.add('btn-outline-danger');
// Visually move to top
if(listContainer.firstChild) listContainer.insertBefore(listItem, listContainer.firstChild);
} else {
userPinnedIds.delete(targetPostId); // Remove from Set
// Remove icon
const pinIconSpan = postTitleElement.querySelector('.pin-icon');
if (pinIconSpan) pinIconSpan.remove();
// Update button
button.textContent = 'Pin'; button.setAttribute('aria-label', 'Pin Post');
button.classList.remove('btn-outline-danger'); button.classList.add('btn-outline-secondary');
// Optional: Move back down or let reload handle order
}
// Save updated Set to localStorage
try {
localStorage.setItem(localStorageKey, JSON.stringify(Array.from(userPinnedIds)));
} catch (e) {
console.error("Error saving pinned posts to localStorage:", e);
// Handle potential storage quota errors
}
console.log(`Saved pinned IDs: ${localStorage.getItem(localStorageKey)}`);
}
}
});
}
// --- Fetch Posts (NEEDS CMS-SPECIFIC ADAPTATION) ---
var script = document.createElement('script');
var feedUrl;
try { // Use origin for potentially custom domains
feedUrl = window.location.origin + `/feeds/posts/default?orderby=published&alt=json-in-script&callback=recentPostsWithThumbs&max-results=${numberOfPosts + 10}`; // Fetch more for sorting flexibility
} catch (e) {
feedUrl = `/feeds/posts/default?orderby=published&alt=json-in-script&callback=recentPostsWithThumbs&max-results=${numberOfPosts + 10}`;
}
// --- !! IMPORTANT !! --- Adapt this section if NOT using Blogger JSON-P ---
// (Keep commented out fetch examples for WP/Custom API if needed)
// --- This part runs ONLY if using the Blogger JSON-P method ---
// If using fetch for WP/Other API, ensure the script.src line below is deleted or commented out
if (typeof fetch === 'undefined' || !feedUrl.includes('/wp-json/')) { // Basic check to avoid running if fetch is intended
console.log("Using JSON-P method. Attempting to fetch feed from:", feedUrl);
script.src = feedUrl;
script.onerror = function() {
const postList = document.getElementById("recent-posts-with-thumbs");
if (postList) postList.innerHTML = '<li style="text-align: center; color: #dc3545;">Error loading posts feed. Check console or feed URL.</li>';
console.error("Failed to load script from:", feedUrl);
};
document.getElementsByTagName('head')[0].appendChild(script);
}
//]]>
</script>
<style>
/* --- Styles (Includes general, hover, pinned, responsive, credit) --- */
/* --- General Widget & Base Item Styles --- */
.recent-posts-widget .card-header { font-size: 1.1rem; }
.recent-posts-widget .list-unstyled { padding: 0; margin: 0; }
.recent-posts-widget .post-item { position: relative; transition: background-color 0.2s ease-in-out; border-bottom: 1px solid #eee; padding: 12px 0; }
.recent-posts-widget .post-item:last-child { border-bottom: none; }
.recent-posts-widget .post-content-area { display: flex; align-items: flex-start; padding: 0 12px; }
.recent-posts-widget .post-thumbnail { flex-shrink: 0; object-fit: cover; border-radius: 0.25rem; margin-right: 12px; width: 72px; height: 72px;} /* Fixed width/height */
.recent-posts-widget .media-body { flex-grow: 1; min-width: 0; }
.recent-posts-widget .post-title { margin-bottom: 0.2rem !important; line-height: 1.3; }
.recent-posts-widget .post-title-link { color: #212529; text-decoration: none; font-weight: 500; }
.recent-posts-widget .post-title-link:hover { color: #0d6efd; text-decoration: underline; }
.recent-posts-widget .post-date { font-size: 0.8em; color: #6c757d; display: block; }
/* --- Hover Actions Styles --- */
.post-actions { position: absolute; bottom: 8px; right: 12px; background-color: rgba(248, 249, 250, 0.95); padding: 4px 6px; border-radius: 5px; border: 1px solid #dee2e6; opacity: 0; visibility: hidden; transform: translateY(5px); transition: opacity 0.2s ease-out, visibility 0.2s ease-out, transform 0.2s ease-out; display: flex; gap: 6px; z-index: 5; }
.post-item:hover .post-actions { opacity: 1; visibility: visible; transform: translateY(0); }
.action-button { font-size: 0.75rem; padding: 0.15rem 0.4rem; cursor: pointer; }
/* --- Pinned Post Specific Styles --- */
.recent-posts-widget .pinned-post { background-color: #e7f1ff; border-left: 4px solid #0d6efd; margin-left: -1.25rem; margin-right: -1.25rem; margin-top: -1px; border-bottom: 1px solid #cfe2ff; border-top: 1px solid #cfe2ff; }
.recent-posts-widget .pinned-post .post-content-area { padding: 12px 12px 12px 8px; }
.recent-posts-widget .pinned-post:first-child { margin-top: -1.25rem; border-top: none; }
.recent-posts-widget .pinned-post:last-of-type { border-bottom: 1px solid #cfe2ff; }
.recent-posts-widget .pinned-post + .post-item:not(.pinned-post) { border-top: 1px solid #eee; margin-top: 0; }
.pin-icon { display: inline-block; margin-right: 5px; color: #0d6efd; font-size: 0.9em; vertical-align: baseline; }
.pinned-post .post-title a { font-weight: 600; color: #0a58ca; }
.pinned-post:hover { background-color: #dbeaff; }
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.recent-posts-widget .post-thumbnail { width: 55px; height: 55px; margin-right: 10px; }
.recent-posts-widget .media-body h6 { font-size: 0.85rem; }
.recent-posts-widget .post-date { font-size: 0.75em; }
.action-button { font-size: 0.7rem; }
.post-actions { bottom: 5px; right: 5px; }
.recent-posts-widget .pinned-post { margin-left: -1rem; margin-right: -1rem; }
.recent-posts-widget .pinned-post:first-child { margin-top: -1rem; }
.recent-posts-widget .pinned-post .post-content-area { padding: 10px 10px 10px 6px; }
}
@media (min-width: 992px) {
.recent-posts-widget .card-header { font-size: 1.2rem; }
.recent-posts-widget .post-thumbnail { width: 72px; height: 72px; }
.recent-posts-widget .media-body h6 { font-size: 0.95rem; }
}
/* --- Style for the Credit Link --- */
.widget-credit { font-size: 0.75em; text-align: right; margin-top: 10px; padding-top: 5px; border-top: 1px solid #eee; color: #6c757d; }
.widget-credit a { color: #0d6efd; text-decoration: none; }
.widget-credit a:hover { text-decoration: underline; }
</style>
</div>
<!-- End Widget -->
How to Add This Interactive Widget to Your Website (Smart Guide)
Integrating this dynamic widget can significantly boost engagement. Follow these steps carefully, paying close attention to the platform-specific adaptations required.
Step 1: Copy the Full Widget Code
Use the "Copy Code" button above to grab the entire HTML, JavaScript, and CSS code block for the widget. This includes the main <div>, the <script> section, and the <style> section.
Step 2: Configure Basic Settings (Inside the Copied Code)
Find the // --- Configuration --- section near the top of the <script> block you just copied.
pinnedLabel = "Pinned"; : CRITICAL: Change "Pinned" to the exact tag, category, or label name you use within your website's CMS to identify posts you want initially pinned. Remember, it's case-sensitive!
numberOfPosts = 5; : Adjust this number to control how many total posts appear in the widget.
defaultThumb = "..."; : If desired, replace the placeholder URL with a link to your preferred default thumbnail image.
localStorageKey = 'userPinnedPostsWidget'; : You generally don't need to change this unless you have multiple similar widgets and need unique storage keys.
Step 3: Adapt the Data Source & Parsing (THE MOST IMPORTANT STEP!)
The Challenge: The provided JavaScript is pre-configured for Blogger's specific JSON feed format . It needs modification to work with other platforms like WordPress, Shopify, Squarespace, Joomla, Drupal, or your custom site.
Your Task:
Identify Your Platform's Data Source: Find out how your specific CMS provides a list of recent posts, including titles, links, publication dates, featured image URLs, and tags/categories, preferably in JSON format . Common sources are platform APIs (like WP REST API) or specialized feeds/plugins. Consult your platform's documentation or developer resources.
Modify the JavaScript Fetch Method:
Locate the // --- Fetch Posts --- section in the script.
If using Blogger: The existing script.src = feedUrl; method (Option 1) should work (check custom domain configurations).
If using WordPress, Shopify, Custom API, etc.: You must comment out or delete the Blogger script.src method. Uncomment and adapt the fetch() examples (Option 2 or 3). Replace the placeholder feedUrl with your actual API endpoint URL. You'll also need to ensure the fetch call correctly triggers the recentPostsWithThumbs function with the received JSON data (e.g., .then(data => recentPostsWithThumbs(data)); - important: adjust the data variable if your API wraps the posts array, e.g., recentPostsWithThumbs({ feed: { entry: data } }) to mimic Blogger's structure if you don't want to change the parsing function heavily).
Modify the JavaScript Data Parsing:
Locate the // --- Data Extraction --- section inside the for loop within the recentPostsWithThumbs function.
Crucially, change the property accessors (e.g., entry.title.$t, entry.link[j].href, entry.media$thumbnail.url, entry.category.some(...)) to match the exact names and structure of the data fields in the JSON response from your platform's API/feed. Use your browser's Developer Tools (Network tab) to inspect the JSON response or consult the API documentation.
Step 4: Insert the Modified Code into Your Platform
Choose the best method for your site:
Blogger: Layout -> Add a Gadget -> HTML/JavaScript -> Paste.
WordPress: Appearance -> Widgets -> Custom HTML -> Paste. (Or advanced: theme files/code snippets).
Shopify: Online Store -> Themes -> Edit code -> Add as Custom Content section or paste into relevant .liquid file.
Squarespace: Use a Code Block (HTML mode). Check plan limits.
Joomla/Drupal: Use a Custom HTML module/block. Ensure scripts/styles aren't stripped.
Custom Site: Paste HTML div where needed. Add <script> before </body>. Add <style> to <head> or the main CSS file.
Step 5: Tag/Label Posts in Your CMS
Log in to your website's backend/CMS.
Edit the posts you wish to feature at the top of the widget initially.
Add the exact tag or label (case-sensitive) defined for pinnedLabel in Step 2. Save the posts.
Step 6: Test, Troubleshoot, and Refine
Clear Caches: Crucial after adding code.
Verify Loading: Check if the widget appears. Use the Browser Developer Console (F12 -> Console) for errors (feed URL, parsing issues, etc.).
Check Pinning: Confirm labeled posts appear first with pinned styling.
Test Interactions: Hover, click "Read," click "Pin/Unpin." Reload the page and verify that posts you visually pinned stay pinned and at the top (thanks to localStorage). Check localStorage in DevTools (Application tab).
Responsiveness: Test on various screen sizes.
CSS Conflicts: Use browser developer tools (Inspector) to diagnose styling issues caused by theme conflicts. Adjust widget CSS specificity if needed.
(How Persistent Pinning Works - Important Note)
The "Pin/Unpin" functionality in this widget uses browser localStorage . This means:
It's Visual & Persistent (Per Browser): When a user clicks "Pin," the post looks pinned and stays that way even after reloading the page in that specific browser .
It Does NOT Change Backend Labels: Clicking the button does not add or remove the actual tag/label in your CMS (Blogger, WordPress, etc.). The initial pinned state on page load is always determined by the labels you set in your CMS.
Browser/Device Specific: Preferences are saved only in the browser where the action was taken.
Just double-check:
Does the "How
Persistent Pinning Works" section in the blog post you're looking at
mention localStorage and explain that the visual pinning should persist after a
reload? If yes, you have the correct guide content.
Does the
"Testing" section mention reloading the page and checking
localStorage to verify persistence? If yes, you have the correct guide content.
Adding dynamic elements like this interactive "Latest Posts" widget with persistent visual pinning can significantly elevate the user experience on your site. It encourages deeper content exploration, effectively highlights crucial articles, and gives visitors a sense of personalized control. While setup requires careful attention to platform-specific data sources, the payoff in increased engagement is substantial.
At SEOsiri.com , we understand that effective SEO isn't just about keywords; it's about creating positive user experiences that keep visitors engaged and signal value to search engines. Tools like this widget, focused on improving content discoverability and user interaction, directly support a human-centered SEO philosophy. When you make your site better for users, search engines notice.
We provide the complete code and detailed,
step-by-step instructions to integrate
this powerful widget, whether you're using a popular CMS or a custom-built
website. Ready to make your content more discoverable and engaging? Let's get
started!
We hope this detailed guide helps you implement this feature successfully! For more advanced insights on enhancing user engagement, content optimization, and technical SEO, explore the wealth of resources available on SEOsiri.com.
Connect with me on Dev.to. Connect with me on Quora. Join me on Web Developers.
Thank youMomenul Ahmad
MomenulAhmad: Helping businesses, brands, and professionals with ethical SEO and digital Marketing. Digital Marketing Writer, Digital Marketing Blog (Founding) Owner at SEOSiri, Pabna, Partner at Brand24, Triple Whale, Shopify, CookieYes, Automattic, Inc.
Use the "Copy Code" button above to grab the entire HTML, JavaScript, and CSS code block for the widget. This includes the main <div>, the <script> section, and the <style> section.
Find the // --- Configuration --- section near the top of the <script> block you just copied. pinnedLabel = "Pinned"; :CRITICAL: Change "Pinned" to theexact tag, category, or label name you use within your website's CMS to identify posts you want initially pinned.Remember, it's case-sensitive! numberOfPosts = 5; : Adjust this number to control how many total posts appear in the widget.defaultThumb = "..."; : If desired, replace the placeholder URL with a link to your preferred default thumbnail image.localStorageKey = 'userPinnedPostsWidget'; : You generally don't need to change this unless you have multiple similar widgets and need unique storage keys.
The Challenge: The provided JavaScript ispre-configured for Blogger's specific JSON feed format . It needs modification to work with other platforms like WordPress, Shopify, Squarespace, Joomla, Drupal, or your custom site.Your Task: Identify Your Platform's Data Source: Find out howyour specific CMS provides a list of recent posts, including titles, links, publication dates, featured image URLs, and tags/categories, preferably inJSON format . Common sources are platform APIs (like WP REST API) or specialized feeds/plugins. Consult your platform's documentation or developer resources.Modify the JavaScript Fetch Method: Locate the // --- Fetch Posts --- section in the script. If using Blogger: The existing script.src = feedUrl; method (Option 1) should work (check custom domain configurations).If using WordPress, Shopify, Custom API, etc.: Youmust comment out or delete the Blogger script.src method. Uncomment and adapt the fetch() examples (Option 2 or 3). Replace the placeholder feedUrl with youractual API endpoint URL. You'll also need to ensure the fetch call correctly triggers the recentPostsWithThumbs function with the received JSON data (e.g., .then(data => recentPostsWithThumbs(data)); -important: adjust the data variable if your API wraps the posts array, e.g., recentPostsWithThumbs({ feed: { entry: data } }) to mimic Blogger's structure if you don't want to change the parsing function heavily).
Modify the JavaScript Data Parsing: Locate the // --- Data Extraction --- section inside the for loop within the recentPostsWithThumbs function. Crucially, change the property accessors (e.g., entry.title.$t, entry.link[j].href, entry.media$thumbnail.url, entry.category.some(...)) to match theexact names and structure of the data fields in the JSON response fromyour platform's API/feed. Use your browser's Developer Tools (Network tab) to inspect the JSON response or consult the API documentation.
Choose the best method for your site: Blogger: Layout -> Add a Gadget -> HTML/JavaScript -> Paste.WordPress: Appearance -> Widgets -> Custom HTML -> Paste. (Or advanced: theme files/code snippets).Shopify: Online Store -> Themes -> Edit code -> Add as Custom Content section or paste into relevant .liquid file.Squarespace: Use a Code Block (HTML mode). Check plan limits.Joomla/Drupal: Use a Custom HTML module/block. Ensure scripts/styles aren't stripped.Custom Site: Paste HTML div where needed. Add <script> before </body>. Add <style> to <head> or the main CSS file.
Log in to your website's backend/CMS. Edit the posts you wish to feature at the top of the widget initially. Add the exact tag or label (case-sensitive) defined for pinnedLabel in Step 2. Save the posts.
Clear Caches: Crucial after adding code.Verify Loading: Check if the widget appears. Use the Browser Developer Console (F12 -> Console) for errors (feed URL, parsing issues, etc.).Check Pinning: Confirm labeled posts appear first with pinned styling.Test Interactions: Hover, click "Read," click "Pin/Unpin."Reload the page and verify that posts you visually pinnedstay pinned and at the top (thanks to localStorage). Check localStorage in DevTools (Application tab).Responsiveness: Test on various screen sizes.CSS Conflicts: Use browser developer tools (Inspector) to diagnose styling issues caused by theme conflicts. Adjust widget CSS specificity if needed.
It's Visual & Persistent (Per Browser): When a user clicks "Pin," the postlooks pinned and stays that way even after reloading the pagein that specific browser .It Does NOT Change Backend Labels: Clicking the buttondoes not add or remove the actual tag/label in your CMS (Blogger, WordPress, etc.). The initial pinned state on page load isalways determined by the labels you set in your CMS.Browser/Device Specific: Preferences are saved only in the browser where the action was taken.
Just double-check:
Does the "How
Persistent Pinning Works" section in the blog post you're looking at
mention localStorage and explain that the visual pinning should persist after a
reload? If yes, you have the correct guide content.
Does the
"Testing" section mention reloading the page and checking
localStorage to verify persistence? If yes, you have the correct guide content.
Adding dynamic elements like this interactive "Latest Posts" widget with persistent visual pinning can significantly elevate the user experience on your site. It encourages deeper content exploration, effectively highlights crucial articles, and gives visitors a sense of personalized control. While setup requires careful attention to platform-specific data sources, the payoff in increased engagement is substantial.
We provide the
Connect with me on Dev.to. Connect with me on Quora. Join me on Web Developers.
MomenulAhmad: Helping businesses, brands, and professionals with ethical SEO and digital Marketing. Digital Marketing Writer, Digital Marketing Blog (Founding) Owner at SEOSiri, Pabna, Partner at Brand24, Triple Whale, Shopify, CookieYes, Automattic, Inc.
Expand Your Expertise: Supplemental Reading for Mastering Audience Engagement & SEO
Welcome back to our journey into creating content that truly converts! Throughout our 5-part series, "Content That Converts: A Step-by-Step Guide to Mastering Audience Engagement (AE) for SEO Success," we're covering the essential strategies from understanding metrics to crafting problem-solving content.
Content and Audience Engagement, Guide (Part1 to Part5, link below in article footer):
- Part 1: The Engagement Equation
- Part 2: Unlocking the Engagement
Drivers
- Part 3: Audience Research
- Part 4: Uncovering the Pain
- Part 5: The Power in Content
While the main series provides a
comprehensive framework, the worlds of SEO and content marketing are vast and
ever-evolving. To help you dive even deeper into specific tactics and related
concepts, we've curated this co-guided reading list featuring valuable
resources from SEOsiri.com. Think of this as your advanced toolkit to further
sharpen your skills and amplify your results.
Reading List 1: Mastering User Intent & the Holistic View
Focus: This first set of resources dives deep into understanding what
your audience is searching for (user intent) using tools like Google's PAA
feature, and reinforces the essential connection between content and SEO for
long-term success, themes central to our main series.
- PAA SEO Guide (SEOsiri.com, 2025/02)
- Abstract:
This guide dives into the "People Also Ask" (PAA) feature on
Google, revealing strategies to identify relevant questions, optimize
your content to answer those questions, and ultimately improve your
website's visibility in search results.
- Relevance:
Directly connects to understanding audience questions (discussed in Part
4 of our series) by focusing on a specific, powerful SERP feature.
- Cited Source:
https://www.seosiri.com/2025/02/paa-seo-guide.html
- Win SEO With Content Marketing: A Holistic View
(SEOsiri.com, 2025/04)
- Abstract:
This resource explains how to connect SEO with content marketing
effectively for sustained growth and impact.
- Relevance:
This perfectly complements the overarching theme of our entire series,
emphasizing the crucial synergy between creating great content and
ensuring it's technically optimized for search.
- Cited Source:
https://www.seosiri.com/2025/04/win-seo-content-marketing.html
Reading
List 2: High-Performance Search & Compelling Content
Focus: This list broadens the scope to include crucial elements
like Search Experience Optimization (SXO) and Search Quality Index (SQI), while
also providing tactical advice on crafting truly compelling content that drives
action, building upon the foundations laid in our series.
- SEO, SXO, SERP, SQI (SEOsiri.com, 2025/04)
- Abstract:
This resource explores the interconnectedness of SEO, Search Experience
Optimization (SXO), Search Engine Results Pages (SERP), and Search
Quality Index (SQI), detailing how these elements work together for
high-performance results.
- Relevance:
While our series focuses heavily on AE within content, this guide expands
on the technical and experiential factors (like SXO) that Google
considers, adding another layer to your optimization strategy.
- Cited Source:
https://www.seosiri.com/2025/04/seo-sxo-serp-sqi.html
- Compelling Content Writing and Marketing: A Quick Guide
(SEOsiri.com, 2024/10)
- Abstract:
This guide provides actionable tips on crafting compelling content that
captures attention, resonates deeply with your audience, and ultimately
drives desired conversions.
- Relevance:
Directly supports the practical content creation advice in Part 5 of our
series, offering specific techniques to make your writing more persuasive
and effective.
- Cited Source:
https://www.seosiri.com/2024/10/compelling-content-writting.html
Reading
List 3: Strategy, Writing & Visual Power
Focus: Developing a strong content strategy is vital. This list
explores strategic flows, the role of the writer in creating captivating
content, and the undeniable importance of visual elements in enhancing
engagement, themes woven throughout our main guide.
- Content Strategy Flows: A Quick Look (SEOsiri.com,
2022/10)
- Abstract:
Explores effective processes and workflows for creating content that achieves
high reach and impact.
- Relevance:
Provides a higher-level view of content strategy, complementing the
specific techniques discussed in our series by focusing on the overall
planning and execution process.
- Cited Source:
https://www.seosiri.com/2022/10/content-strategy-flows.html
- Captivating Content Writer (SEOsiri.com, 2025/03)
- Abstract:
Offers insights and techniques for writers aiming to improve audience
reach and engagement through their craft.
- Relevance:
Deepens the discussion from Part 5 by focusing specifically on the skills
and mindset required to be a truly captivating content writer who
fosters engagement.
- Cited Source:
https://www.seosiri.com/2025/03/captivating-content-writer.html
- Visual Content Creation Service (SEOsiri.com, 2025/01)
- Abstract:
Offers insights into creating various types of visual content formats
effectively to enhance communication and engagement.
- Relevance:
Builds directly on the importance of visuals mentioned in Part 5, offering
more specific ideas and considerations for incorporating different visual
formats into your content.
- Cited Source:
https://www.seosiri.com/2025/01/visual-content-creation-service.html
Reading
List 4: Boosting Your Click-Through Rates (CTR)
Focus: Getting your content seen in the SERPs is the first hurdle.
This final list provides targeted strategies for increasing your Click-Through
Rate (CTR), ensuring that your compelling titles and descriptions entice users
to click and engage with the amazing content you've created.
- Contents CTR (Growth Hacking Guide) (SEOsiri.com,
2019/02)
- Abstract:
This resource provides foundational insights into how content structure
and elements impact CTR and contribute to traffic growth, using
SEOsiri.com's experience as context.
- Relevance:
Connects to the engagement metrics in Part 1 by focusing on how to
improve one of the earliest indicators of interest – the click itself.
- Cited Source:
https://www.seosiri.com/2019/02/contents-ctr-growth-hacking-guide.html
- Increase Google SERPs CTR (SEOsiri.com, 2021/11)
- Abstract:
Provides more specific insights and tactics aimed at improving CTR
directly within Google's Search Engine Results Pages for higher rankings
and visibility.
- Relevance:
Offers practical techniques to optimize your SERP snippets (titles, meta
descriptions) – a crucial step before users even reach your
content to engage with it.
- Cited Source:
https://www.seosiri.com/2021/11/increase-google-serps-ctr.html
Mastering audience engagement for
SEO success is an ongoing process of learning and refinement. While our 5-part
series provides a solid foundation, these supplemental resources offer
opportunities to explore specific areas in greater detail.
We encourage you to dip into the
articles that resonate most with your current challenges and goals. Use them to
deepen your understanding, refine your tactics, and continue building content
that not only ranks but truly connects and converts.
We hope this curated reading list proves valuable! Which topics are you most excited to explore further? Let us know in the comments below! And don't forget to revisit the main Content That Converts 5 part series overview (Content That Converts: A Step-by-Step Guide to Mastering Audience Engagement (AE) for SEO Success) anytime you need a refresher on the core framework.
MomenulAhmad: Helping businesses, brands, and professionals with ethical SEO and digital Marketing. Digital Marketing Writer, Digital Marketing Blog (Founding) Owner at SEOSiri, Partner at Brand24, Triple Whale, Shopify, CookieYes, Automattic, Inc.