82Course card
The Card with course anatomy: level tag riding the media, mono meta row, and a progress foot with the real
Use it for
- Catalog grids, related-course shelves, continue-learning rows
Not for
- The course detail hero — that page IS the course, no card around it
Default
markup
<div class="ns-card ns-ccard" style="max-inline-size:18rem">
<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>
<p class="ns-card__text">The platform's own language, from zero to first deploy.</p>
<span class="ns-ccard__meta"><span>12 lessons</span><span>3h 40m</span></span>
</div>
</div>With progress
Continue learning: the foot swaps meta for the real progress element.
markup
<div class="ns-card ns-ccard" style="max-inline-size:18rem">
<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>Next: 07 · Approval flows</span></span>
</div>
<div class="ns-ccard__progress">
<progress class="ns-progress" value="6" max="12" aria-label="6 of 12 lessons complete"></progress>
<span class="ns-ccard__pct">6/12</span>
</div>
</div>Row — continue learning
The horizontal form for the dashboard's resume shelf.
markup
<div class="ns-card ns-card--row ns-ccard" style="max-inline-size:30rem">
<span class="ns-card__media ns-ph" aria-hidden="true"></span>
<div class="ns-card__body">
<span class="ns-card__kicker">// Continue</span>
<a class="ns-card__link" href="#"><span class="ns-card__title">Admin certification path</span></a>
<span class="ns-ccard__meta"><span>Lesson 08 of 14</span><span>21:15 left</span></span>
<progress class="ns-progress" value="8" max="14" aria-label="8 of 14 lessons complete"></progress>
</div>
</div>Members-only
Locked stays a real link — to the join page; the lock is a tag, never an overlay wash.
markup
<div class="ns-card ns-ccard" style="max-inline-size:18rem">
<span class="ns-ccard__mediawrap">
<span class="ns-card__media ns-ph" aria-hidden="true"></span>
<span class="ns-tag ns-ccard__level"><i class="ph ph-lock-simple" aria-hidden="true"></i> Members</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 patterns</span></a>
<span class="ns-ccard__meta"><span>9 lessons</span><span>2h 10m</span></span>
</div>
</div>Full meta
Everything a card can carry, so the parts are visible in one place: a corner flag, the runtime chip on the media, the play affordance, rating, learner count, level, authors and price. A real card picks three or four of these — this is the vocabulary, not a recommendation. The rating's NUMBER comes first in the DOM; the stars are decoration over it.
The platform's own language, from zero to first deploy.
$49 $129 62% offmarkup
<div class="ns-card ns-ccard" style="max-inline-size:19rem">
<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 class="ns-ccard__badge">Bestseller</span>
<span class="ns-ccard__dur">3h 40m</span>
<span class="ns-ccard__play" aria-hidden="true"><i class="ph ph-play-circle"></i></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, end to end</span></a>
<p class="ns-card__text">The platform's own language, from zero to first deploy.</p>
<span class="ns-rating" data-value="4.8" data-of="5">
<span class="ns-rating__value">4.8</span>
<span class="ns-rating__stars" aria-hidden="true">
<span><i class="ph ph-star"></i><i class="ph ph-star"></i><i class="ph ph-star"></i><i class="ph ph-star"></i><i class="ph ph-star"></i></span>
<span class="ns-rating__fill"><i class="ph-fill ph-star"></i><i class="ph-fill ph-star"></i><i class="ph-fill ph-star"></i><i class="ph-fill ph-star"></i><i class="ph-fill ph-star"></i></span>
</span>
<span class="ns-rating__count">(1,240)</span>
</span>
<span class="ns-ccard__meta">
<span><i class="ph ph-list" aria-hidden="true"></i>12 lessons</span>
<span><i class="ph ph-users-three" aria-hidden="true"></i>8,410</span>
<span><i class="ph ph-globe" aria-hidden="true"></i>English</span>
</span>
<span class="ns-authors ns-authors--sm">
<span class="ns-authors__stack" aria-hidden="true">
<img class="ns-authors__avatar" src="../assets/logo/favicon.svg" alt="">
<img class="ns-authors__avatar" src="../assets/logo/favicon.svg" alt="">
<span class="ns-authors__more">+2</span>
</span>
<span class="ns-authors__names"><b>Swarnil Singhai</b> and 3 others</span>
</span>
<span class="ns-price ns-price--sm">
<span class="ns-price__now">$49</span>
<span class="ns-price__was">$129</span>
<span class="ns-price__off">62% off</span>
</span>
</div>
</div>Lesson peek
What is actually IN the course, without leaving the grid. Hover or tab into the card. It is real content in the DOM at all times — collapsed with a grid-template-rows transition rather than display:none, so in-page find reaches it and a screen reader reads it whether or not a pointer is over the card. Where hover does not exist it is simply open, because the alternative is content no touch user can reach. Show three or four rows: a peek listing twenty is the syllabus in the wrong place.
markup
<div class="ns-card ns-ccard" style="max-inline-size:19rem">
<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 class="ns-ccard__dur">2h 10m</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 patterns</span></a>
<span class="ns-ccard__meta"><span>9 lessons</span><span>Updated Aug 2026</span></span>
</div>
<div class="ns-ccard__peek">
<div>
<a class="ns-lesson" href="#"><span class="ns-lesson__index" aria-hidden="true">01</span><span class="ns-lesson__title">Why one record at a time fails</span><span class="ns-lesson__time">08:12</span></a>
<a class="ns-lesson" href="#"><span class="ns-lesson__index" aria-hidden="true">02</span><span class="ns-lesson__title">Collections, maps and sets</span><span class="ns-lesson__time">14:40</span></a>
<a class="ns-lesson" href="#" data-state="locked"><span class="ns-lesson__index" aria-hidden="true">03</span><span class="ns-lesson__title">Governor limits in practice</span><span class="ns-lesson__time"><i class="ph ph-lock" aria-hidden="true"></i></span></a>
<a class="ns-ccard__peek-more" href="#">See all 9 lessons →</a>
</div>
</div>
</div>Shapes
Five shapes, one anatomy — a course in a grid, in a search result, in a rail and in a related strip is the same object at four sizes, and four bespoke cards is how the meta rows drift apart. --row for lists, --compact for dense rails, --minimal for a card inside another card.
markup
<div style="display:grid;gap:var(--space-4);max-inline-size:32rem">
<div class="ns-card ns-ccard ns-ccard--row">
<span class="ns-ccard__mediawrap"><span class="ns-card__media ns-ph" aria-hidden="true"></span></span>
<div class="ns-card__body">
<span class="ns-card__kicker">// Continue</span>
<a class="ns-card__link" href="#"><span class="ns-card__title">Admin certification path</span></a>
<span class="ns-ccard__meta"><span>Lesson 08 of 14</span><span>21:15 left</span></span>
<progress class="ns-progress" value="8" max="14" aria-label="8 of 14 lessons complete"></progress>
</div>
</div>
<div class="ns-card ns-ccard ns-ccard--compact" style="max-inline-size:14rem">
<div class="ns-card__body">
<span class="ns-card__kicker">// Related</span>
<a class="ns-card__link" href="#"><span class="ns-card__title">SOQL, properly</span></a>
<span class="ns-ccard__meta"><span>6 lessons</span><span>1h 05m</span></span>
</div>
</div>
<div class="ns-card ns-ccard ns-ccard--minimal">
<div class="ns-card__body">
<a class="ns-card__link" href="#"><span class="ns-card__title">Integration patterns</span></a>
<span class="ns-ccard__meta"><span>11 lessons</span><span>Advanced</span></span>
</div>
</div>
</div>Featured
The one promoted course. Two columns, more meta, a larger title — and exactly one of these per screen, or it is not featured.
Six courses, one order: the data model, Apex, LWC, integration, testing and deployment.
markup
<div class="ns-card ns-ccard ns-ccard--featured" style="max-inline-size:38rem">
<span class="ns-ccard__mediawrap">
<span class="ns-card__media ns-ph" aria-hidden="true"></span>
<span class="ns-ccard__badge ns-ccard__badge--new">New</span>
</span>
<div class="ns-card__body">
<span class="ns-card__kicker">// Featured path</span>
<a class="ns-card__link" href="#"><span class="ns-card__title">The Salesforce developer track</span></a>
<p class="ns-card__text">Six courses, one order: the data model, Apex, LWC, integration, testing and deployment.</p>
<span class="ns-ccard__meta">
<span><i class="ph ph-books" aria-hidden="true"></i>6 courses</span>
<span><i class="ph ph-clock" aria-hidden="true"></i>24h</span>
<span><i class="ph ph-medal" aria-hidden="true"></i>Certificate</span>
</span>
<div style="display:flex;gap:var(--space-2);margin-block-start:var(--space-2)">
<a class="ns-btn ns-btn--primary ns-btn--sm" href="#">Start the track</a>
<a class="ns-btn ns-btn--outline ns-btn--sm" href="#">What's inside</a>
</div>
</div>
</div>Accessibility contract
- The title link is stretched over the card — one real link
- progress carries an aria-label with the human numbers