87Course listing
The catalog: a filter row (tags with aria-pressed + a sort Select) over the course-card grid. This demo is LIVE — the tags actually filter the grid; hidden cards keep their colors and order when they return.
Use it for
- The /courses page, tag archives, search results
Not for
- A shelf of 4 on the home page — Carousel or a plain grid
Live filter + grid
Beginner
// CourseApex basics
Beginner
// CourseAdmin fundamentals
Intermediate
// CourseFlows, end to end
Advanced
// CourseBulk-safe Apex
markup
<div id="cl-demo" style="inline-size:100%">
<div class="ns-course-filters">
<button class="ns-tag" data-filter="all" aria-pressed="true">All</button>
<button class="ns-tag" data-filter="admin" aria-pressed="false">Admin</button>
<button class="ns-tag" data-filter="dev" aria-pressed="false">Developer</button>
<span class="ns-ccard__meta" data-count role="status" style="margin-inline-start:var(--space-2)"></span>
<select class="ns-select" aria-label="Sort">
<option>Newest</option><option>Most popular</option><option>Shortest first</option>
</select>
</div>
<div class="ns-course-grid">
<div class="ns-card ns-ccard" data-topic="dev"><span class="ns-ccard__mediawrap"><span class="ns-card__media ns-ph" aria-hidden="true"></span><span class="ns-tag ns-ccard__level">Beginner</span></span><div class="ns-card__body"><span class="ns-card__kicker">// Course</span><a class="ns-card__link" href="#"><span class="ns-card__title">Apex basics</span></a><span class="ns-ccard__meta"><span>12 lessons</span></span></div></div>
<div class="ns-card ns-ccard" data-topic="admin"><span class="ns-ccard__mediawrap"><span class="ns-card__media ns-ph" aria-hidden="true"></span><span class="ns-tag ns-ccard__level">Beginner</span></span><div class="ns-card__body"><span class="ns-card__kicker">// Course</span><a class="ns-card__link" href="#"><span class="ns-card__title">Admin fundamentals</span></a><span class="ns-ccard__meta"><span>14 lessons</span></span></div></div>
<div class="ns-card ns-ccard" data-topic="admin"><span class="ns-ccard__mediawrap"><span class="ns-card__media ns-ph" aria-hidden="true"></span><span class="ns-tag ns-ccard__level">Intermediate</span></span><div class="ns-card__body"><span class="ns-card__kicker">// Course</span><a class="ns-card__link" href="#"><span class="ns-card__title">Flows, end to end</span></a><span class="ns-ccard__meta"><span>9 lessons</span></span></div></div>
<div class="ns-card ns-ccard" data-topic="dev"><span class="ns-ccard__mediawrap"><span class="ns-card__media ns-ph" aria-hidden="true"></span><span class="ns-tag ns-ccard__level">Advanced</span></span><div class="ns-card__body"><span class="ns-card__kicker">// Course</span><a class="ns-card__link" href="#"><span class="ns-card__title">Bulk-safe Apex</span></a><span class="ns-ccard__meta"><span>9 lessons</span></span></div></div>
</div>
</div>Full page
markup
<p><a class="ns-btn ns-btn--outline ns-btn--sm" href="./demo-course-listing.html" target="_blank" rel="noopener">Open the full-screen demo <i class="ph ph-arrow-up-right" aria-hidden="true"></i></a></p>Accessibility contract
- Filters are real buttons; the result count is a live region so the change is announced
- An emptied grid shows the Empty state with the one obvious fix — clear filters