Open demo

CSS code

HTML
<article class="course">
  <div class="cover"><span class="level">Intermediate</span></div>
  <div class="pad">
    <div class="top">
      <div>
        <h2>Modern CSS Layout: Grid, Flexbox and Beyond</h2>
        <p>17 of 25 lessons · 6h 40m total</p>
      </div>
      <div class="ring" style="--p:68" role="progressbar" aria-label="Course progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68"><span>68%</span></div>
    </div>
    <div class="next">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg>
      <div><b>Up next</b><span>Subgrid in production</span></div>
    </div>
    <a class="btn" href="#">Continue course</a>
  </div>
</article>
CSS
/* Course card. The ring is a conic-gradient filled to --p percent with a white disc on top — no SVG,
   no canvas — exposed as role="progressbar". "Continue course" is a stretched link: its ::after
   covers the whole card, so the card is one click target with one accessible name. */
.course{--accent:#4f46e5;position:relative;width:340px;max-width:100%;overflow:hidden;background:#fff;
  border:1px solid #e9ecf1;border-radius:16px;transition:box-shadow .18s,transform .18s}
.course:hover{transform:translateY(-4px);box-shadow:0 18px 36px -16px rgba(16,24,40,.16)}
.course svg{display:block}
.course .cover{height:130px;position:relative;background:linear-gradient(135deg,#4f46e5,#a855f7 55%,#ec4899)}
.course .cover::after{content:"";position:absolute;inset:0;
  background:radial-gradient(60% 80% at 78% 18%,rgba(255,255,255,.3),transparent 62%)}
.course .level{position:absolute;top:12px;left:12px;z-index:1;font-size:.7rem;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;color:#312e81;background:rgba(255,255,255,.92);
  padding:4px 10px;border-radius:999px}
.course .pad{padding:18px 20px 20px}
.course .top{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}
.course h2{font-size:1.02rem;line-height:1.35;letter-spacing:-.01em}
.course .top p{font-size:.8rem;color:#667085;margin-top:5px}
.course .ring{--p:68;width:56px;height:56px;flex-shrink:0;border-radius:50%;display:grid;place-items:center;
  background:conic-gradient(var(--accent) calc(var(--p)*1%),#eef0f4 0)}
.course .ring span{width:44px;height:44px;border-radius:50%;background:#fff;display:grid;place-items:center;
  font-size:.78rem;font-weight:800;color:#312e81}
.course .next{display:flex;align-items:center;gap:10px;background:#f7f8fa;border-radius:11px;padding:11px 13px;margin-top:16px}
.course .next svg{width:17px;height:17px;stroke:var(--accent);fill:none;stroke-width:2;flex-shrink:0}
.course .next div{min-width:0}
.course .next b{display:block;font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:#5f6778}
.course .next span{font-size:.87rem;font-weight:600}
.course .btn{display:block;width:100%;margin-top:15px;background:var(--accent);color:#fff;font-size:.9rem;
  font-weight:700;text-align:center;text-decoration:none;padding:11px;border-radius:10px}
.course .btn::after{content:"";position:absolute;inset:0}
.course:hover .btn{background:#4338ca}
.course .btn:focus-visible{outline:2px solid transparent}
.course:has(.btn:focus-visible){outline:2px solid var(--accent);outline-offset:3px}
@supports not selector(:has(*)){.course .btn:focus-visible{outline-color:var(--accent);outline-offset:2px}}
@media (prefers-reduced-motion:reduce){.course{transition:none}.course:hover{transform:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<article class="card course">
  <div class="cover"><span class="badge rounded-pill text-uppercase level">Intermediate</span></div>
  <div class="card-body">
    <div class="d-flex align-items-start justify-content-between gap-3">
      <div>
        <h2 class="card-title">Modern CSS Layout: Grid, Flexbox and Beyond</h2>
        <p class="meta mb-0">17 of 25 lessons · 6h 40m total</p>
      </div>
      <div class="ring" style="--p:68" role="progressbar" aria-label="Course progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68"><span>68%</span></div>
    </div>
    <div class="next d-flex align-items-center">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg>
      <div class="text-truncate"><b>Up next</b><span>Subgrid in production</span></div>
    </div>
    <a class="btn w-100 stretched-link" href="#">Continue course</a>
  </div>
</article>
CSS
/* Bootstrap .card + .badge + .btn.stretched-link. The ring is a conic-gradient filled to --p percent
   with a white disc on top, exposed as role="progressbar". The stretched link makes the whole card
   one click target. */
.course{--accent:#4f46e5;--bs-card-border-color:#e9ecf1;--bs-card-border-radius:16px;--bs-card-inner-border-radius:15px;
  --bs-card-spacer-y:18px;--bs-card-spacer-x:20px;width:340px;max-width:100%;overflow:hidden;
  transition:box-shadow .18s,transform .18s}
.course:hover{transform:translateY(-4px);box-shadow:0 18px 36px -16px rgba(16,24,40,.16)}
.course .card-body{padding-bottom:20px}
.course .cover{height:130px;position:relative;background:linear-gradient(135deg,#4f46e5,#a855f7 55%,#ec4899)}
.course .cover::after{content:"";position:absolute;inset:0;
  background:radial-gradient(60% 80% at 78% 18%,rgba(255,255,255,.3),transparent 62%)}
.course .level{--bs-badge-font-size:.7rem;--bs-badge-font-weight:700;--bs-badge-padding-x:10px;--bs-badge-padding-y:4px;
  --bs-badge-color:#312e81;position:absolute;top:12px;left:12px;z-index:1;letter-spacing:.05em;
  background:rgba(255,255,255,.92);line-height:normal}
.course .card-title{font-size:1.02rem;font-weight:700;line-height:1.35;letter-spacing:-.01em;margin:0}
.course .meta{font-size:.8rem;color:#667085;margin-top:5px}
.course .ring{--p:68;width:56px;height:56px;flex-shrink:0;border-radius:50%;display:grid;place-items:center;
  background:conic-gradient(var(--accent) calc(var(--p)*1%),#eef0f4 0)}
.course .ring span{width:44px;height:44px;border-radius:50%;background:#fff;display:grid;place-items:center;
  font-size:.78rem;font-weight:800;color:#312e81}
.course .next{gap:10px;background:#f7f8fa;border-radius:11px;padding:11px 13px;margin-top:16px}
.course .next svg{width:17px;height:17px;stroke:var(--accent);fill:none;stroke-width:2;flex-shrink:0}
.course .next b{display:block;font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:#5f6778}
.course .next span{font-size:.87rem;font-weight:600}
.course .btn{--bs-btn-bg:var(--accent);--bs-btn-border-color:var(--accent);--bs-btn-color:#fff;
  --bs-btn-hover-bg:#4338ca;--bs-btn-hover-border-color:#4338ca;--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:#3730a3;--bs-btn-active-border-color:#3730a3;
  --bs-btn-font-size:.9rem;--bs-btn-font-weight:700;--bs-btn-padding-y:10px;--bs-btn-border-radius:10px;
  --bs-btn-line-height:normal;margin-top:15px}
.course:hover .btn{background:#4338ca;border-color:#4338ca}
.course .stretched-link:focus-visible{outline:2px solid transparent;box-shadow:none}
.course:has(.stretched-link:focus-visible){outline:2px solid var(--accent);outline-offset:3px}
@supports not selector(:has(*)){.course .stretched-link:focus-visible{outline-color:var(--accent);outline-offset:2px}}
@media (prefers-reduced-motion:reduce){.course,.course .btn{transition:none}.course:hover{transform:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<article class="group relative w-[340px] max-w-full overflow-hidden rounded-2xl border border-[#e9ecf1] bg-white transition-[box-shadow,translate] duration-[180ms] hover:-translate-y-1 hover:shadow-[0_18px_36px_-16px_rgba(16,24,40,.16)] has-[a:focus-visible]:outline-2 has-[a:focus-visible]:outline-offset-[3px] has-[a:focus-visible]:outline-[#4f46e5] motion-reduce:transition-none motion-reduce:hover:translate-y-0">
  <div class="relative h-[130px] bg-[linear-gradient(135deg,#4f46e5,#a855f7_55%,#ec4899)] after:absolute after:inset-0 after:bg-[radial-gradient(60%_80%_at_78%_18%,rgba(255,255,255,.3),transparent_62%)]"><span class="absolute top-3 left-3 z-10 rounded-full bg-white/92 px-2.5 py-1 text-[.7rem] font-bold tracking-[.05em] text-[#312e81] uppercase">Intermediate</span></div>
  <div class="px-5 pt-[18px] pb-5">
    <div class="flex items-start justify-between gap-3.5">
      <div>
        <h2 class="text-[1.02rem] leading-[1.35] font-bold tracking-[-.01em]">Modern CSS Layout: Grid, Flexbox and Beyond</h2>
        <p class="mt-[5px] text-[.8rem] text-[#667085]">17 of 25 lessons · 6h 40m total</p>
      </div>
      <div class="grid size-14 shrink-0 place-items-center rounded-full bg-[conic-gradient(#4f46e5_calc(var(--p)*1%),#eef0f4_0)] [--p:68]" role="progressbar" aria-label="Course progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68"><span class="grid size-11 place-items-center rounded-full bg-white text-[.78rem] font-extrabold text-[#312e81]">68%</span></div>
    </div>
    <div class="mt-4 flex items-center gap-2.5 rounded-[11px] bg-[#f7f8fa] px-[13px] py-[11px]">
      <svg class="block size-[17px] shrink-0 fill-none stroke-[#4f46e5] stroke-2" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg>
      <div class="min-w-0"><b class="block text-[.7rem] tracking-[.06em] text-[#5f6778] uppercase">Up next</b><span class="text-[.87rem] font-semibold">Subgrid in production</span></div>
    </div>
    <a class="mt-[15px] block w-full rounded-[10px] bg-[#4f46e5] p-[11px] text-center text-[.9rem] font-bold text-white group-hover:bg-[#4338ca] after:absolute after:inset-0 focus-visible:outline-2 focus-visible:outline-transparent" href="#">Continue course</a>
  </div>
</article>

About this set

A single online-course card for a learner dashboard. A gradient cover carries an Intermediate level pill, and below it sit the course title, a lessons and duration line, a circular progress ring showing 68 percent, an Up next panel naming the next lesson, and a full-width Continue course button. The ring is a conic-gradient filled to a –p custom property with a white disc on top, so it needs no SVG and no canvas; change –p and the number inside to update it. It is exposed as role=”progressbar” with aria-valuenow, so the percentage is announced properly. Continue course is a link, not a button, and it is a stretched link: its ::after covers the card, so clicking anywhere on the card continues the course, while assistive technology still finds exactly one link. The card lifts slightly on hover. Use it in course catalogues, dashboards and resume-learning rows. The Bootstrap version builds it from .card, .badge and .btn with .stretched-link; the Tailwind version writes the conic gradient as an arbitrary background that reads the same –p variable.

What’s included

  • Conic-gradient progress ring set by one --p variable
  • Ring exposed as role="progressbar" with its value
  • Whole card clickable through one stretched link
  • Up next panel for the following lesson
  • Hover lift that is removed under reduced motion

Accessibility

The course title is an h2, the ring is a progressbar named Course progress with aria-valuenow, and the card has a single link, Continue course, whose click area is stretched over the card. Keyboard focus on that link draws a 2px indigo ring around the whole card. The grey text was darkened to meet 4.5:1, and the hover lift and shadow transitions are switched off under prefers-reduced-motion.

Customise it

Change --accent for the ring, play icon and button colour, and --p plus the label and aria-valuenow for progress. The cover is one linear-gradient on .cover; in Tailwind it is the bg-[linear-gradient(...)] class on the cover div.