Open demo

CSS code

HTML
<div><span class="lbl">Forum topic with a solved answer and a reply box</span>
<section class="df" data-forum aria-labelledby="df-h">
  <div class="df-top">
    <p class="df-crumbs"><span class="df-tag">3D printing</span><span class="df-tag t2">Solved</span></p>
    <h2 class="df-h" id="df-h">First layer stopped sticking to my PEI sheet</h2>
    <p class="df-stats"><span><b>3</b> replies</span><span><b>1.2k</b> views</span><span>Last reply <time datetime="2026-10-16T18:30">3 days ago</time></span></p>
  </div>
  <ol class="df-posts">
    <li class="df-post" id="df-post-1" data-post>
      <div class="df-user"><span class="df-av c3" aria-hidden="true">KM</span><b class="df-uname">kai.makes</b><span class="df-mod op">Original poster</span><span class="df-umeta">Joined Jan 2025 · 42 posts</span></div>
      <article class="df-body" aria-label="Post 1 by kai.makes">
        <p class="df-pmeta"><a class="df-num" href="#df-post-1">#1</a><time datetime="2026-10-15T20:14">4 days ago</time></p>
        <div class="df-content"><p>After about 200 prints my textured PEI sheet stopped holding PLA. Bed at 60 °C, nozzle at 210 °C, first layer <code>0.2 mm</code>, and I wipe it with isopropyl before every print.</p><p>The corners lift within the first five minutes. Any ideas before I buy a new sheet?</p></div>
        <div class="df-acts"><button class="df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="df-sr">Like post 1,</span> <span data-n>3</span></button><button class="df-act" type="button" data-quote="After about 200 prints my textured PEI sheet stopped holding PLA." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="df-sr"> post 1</span></button></div>
      </article>
    </li>
    <li class="df-post ok" id="df-post-2" data-post>
      <div class="df-user"><span class="df-av c1" aria-hidden="true">FP</span><b class="df-uname">ferro_print</b><span class="df-mod">Moderator</span><span class="df-umeta">Joined Jun 2019 · 8,412 posts</span></div>
      <article class="df-body" aria-label="Post 2 by ferro_print">
        <p class="df-pmeta"><a class="df-num" href="#df-post-2">#2</a><time datetime="2026-10-15T21:02">4 days ago</time><span class="df-sol"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 5 5 9-10"/></svg>Accepted solution</span></p>
        <div class="df-content"><p>Isopropyl removes grease, but not the film that builds up over time. Wash the sheet with hot water and a drop of washing-up liquid, dry it with a clean paper towel and do not touch the surface afterwards.</p><p>Every 200 to 300 prints is normal.</p></div>
        <div class="df-acts"><button class="df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="df-sr">Like post 2,</span> <span data-n>27</span></button><button class="df-act" type="button" data-quote="Every 200 to 300 prints is normal." data-user="ferro_print"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="df-sr"> post 2</span></button></div>
      </article>
    </li>
    <li class="df-post" id="df-post-3" data-post>
      <div class="df-user"><span class="df-av c2" aria-hidden="true">LP</span><b class="df-uname">linnea.p</b><span class="df-umeta">Joined Mar 2024 · 310 posts</span></div>
      <article class="df-body" aria-label="Post 3 by linnea.p">
        <p class="df-pmeta"><a class="df-num" href="#df-post-3">#3</a><time datetime="2026-10-16T07:45">3 days ago</time></p>
        <div class="df-content"><blockquote class="df-quote"><cite>ferro_print wrote:</cite>Every 200 to 300 prints is normal.</blockquote><p>Can confirm. I also raised the first layer to <code>0.24 mm</code> and it has stuck perfectly since.</p></div>
        <div class="df-acts"><button class="df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="df-sr">Like post 3,</span> <span data-n>5</span></button><button class="df-act" type="button" data-quote="I also raised the first layer to 0.24 mm." data-user="linnea.p"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="df-sr"> post 3</span></button></div>
      </article>
    </li>
    <li class="df-post" id="df-post-4" data-post>
      <div class="df-user"><span class="df-av c3" aria-hidden="true">KM</span><b class="df-uname">kai.makes</b><span class="df-mod op">Original poster</span><span class="df-umeta">Joined Jan 2025 · 42 posts</span></div>
      <article class="df-body" aria-label="Post 4 by kai.makes">
        <p class="df-pmeta"><a class="df-num" href="#df-post-4">#4</a><time datetime="2026-10-16T18:30">3 days ago</time></p>
        <div class="df-content"><p>Washing-up liquid did it. Twenty prints since, not one lifted corner. Thanks both, marking this as solved.</p></div>
        <div class="df-acts"><button class="df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="df-sr">Like post 4,</span> <span data-n>2</span></button><button class="df-act" type="button" data-quote="Washing-up liquid did it." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="df-sr"> post 4</span></button></div>
      </article>
    </li>
  </ol>
  <form class="df-compose" data-composer>
    <label class="df-clabel" for="df-reply">Your reply</label>
    <textarea class="df-field" id="df-reply" rows="3" placeholder="Share what worked for you" aria-describedby="df-reply-hint"></textarea>
    <div class="df-crow"><p class="df-hint" id="df-reply-hint">Markdown works. Use Quote on any post to reply to it.</p><button class="df-btn" type="submit">Post reply</button></div>
    <p class="df-status" role="status" data-status></p>
  </form>
</section></div>

<div><span class="lbl">Pinned and locked notices</span>
<div class="df-notice">
  <p class="df-nrow"><span class="df-nicon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 4h6l-1 6 4 4H6l4-4zM12 14v7"/></svg></span><span class="df-ntext"><b>Pinned by moderators</b>Add your printer model and slicer settings to every help topic. <a class="df-link" href="#">Posting guide</a></span></p>
  <p class="df-nrow"><span class="df-nicon t2"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span><span class="df-ntext"><b>This topic is locked</b>It closed automatically 30 days after the last reply. Start a new topic and link back to this one.</span></p>
</div></div>
CSS
/* Dark forum topic: a header with category and status tags, posts with a user column (role badge, join date, post count),
   an accepted solution, quotes, like toggles (aria-pressed) and a reply box that Quote fills in. */
.df{--df-bg:#0b0e14;--df-panel:#131823;--df-panel2:#1c2333;--df-line:#2a3245;--df-ink:#e6e9f2;--df-muted:#9aa3b8;--df-accent:#a78bfa;--df-ok:#4ade80;overflow:hidden;border:1px solid var(--df-line);border-radius:16px;background:var(--df-panel);color:var(--df-ink);font-size:.94rem;line-height:1.6;box-shadow:0 24px 60px -30px rgba(0,0,0,.8)}
.df :where(ol){list-style:none}
.df-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
:where(.df) :is(button,textarea){font:inherit;color:inherit}
:is(.df,.df-notice) :where(h2,p,ol,blockquote){margin:0}
.df-top{padding:22px 24px;border-bottom:1px solid var(--df-line);background:linear-gradient(180deg,#191f2d,var(--df-panel))}
.df-crumbs{display:flex;flex-wrap:wrap;gap:6px;font-size:.78rem;font-weight:600}
.df-h{margin-top:10px;color:#fff;font-size:1.35rem;font-weight:700;line-height:1.3;letter-spacing:-.01em}
.df-stats{display:flex;flex-wrap:wrap;gap:4px 16px;margin-top:8px;color:var(--df-muted);font-size:.82rem}
.df-stats b{color:var(--df-ink)}
.df-posts{margin:0;padding:0}
.df-post{display:grid;grid-template-columns:150px minmax(0,1fr);border-bottom:1px solid var(--df-line)}
.df-post.ok{background:linear-gradient(90deg,rgba(74,222,128,.07),transparent 50%);box-shadow:inset 3px 0 0 var(--df-ok)}
.df-user{display:flex;flex-direction:column;align-items:center;gap:4px;padding:20px 12px;border-right:1px solid var(--df-line);color:var(--df-muted);font-size:.78rem;text-align:center}
.df-av{display:grid;place-items:center;width:48px;height:48px;margin-bottom:4px;border-radius:14px;font-size:.9rem;font-weight:700}
:is(.df) .c1{background:#3b2a63;color:#ddd6fe}
:is(.df) .c2{background:#123b2b;color:#86efac}
:is(.df) .c3{background:#4a2318;color:#fdba74}
.df-uname{color:var(--df-ink);font-size:.9rem;font-weight:700}
.df-umeta{line-height:1.45}
.df-body{min-width:0;padding:20px 24px}
.df-pmeta{display:flex;flex-wrap:wrap;align-items:center;gap:10px;color:var(--df-muted);font-size:.8rem}
.df-num{border-radius:2px;color:var(--df-muted);font-weight:600;text-decoration:none}
.df-num:hover{color:var(--df-accent)}
.df-sol{display:inline-flex;align-items:center;gap:6px;color:var(--df-ok);font-weight:700}
.df-content{margin-top:8px}
.df-content p{text-wrap:pretty}
.df-content p+p{margin-top:10px}
.df-content code{padding:1px 6px;border-radius:5px;background:var(--df-panel2);color:#f0abfc;font-size:.86em}
.df-quote{margin:10px 0;padding:10px 14px;border-left:3px solid var(--df-accent);border-radius:0 10px 10px 0;background:var(--df-panel2);color:#c9cfdd;font-size:.9rem}
.df-quote cite{display:block;margin-bottom:2px;color:var(--df-accent);font-size:.78rem;font-style:normal;font-weight:700}
.df-acts{display:flex;flex-wrap:wrap;gap:4px;margin-top:12px}
[aria-pressed=true] .df-heart{fill:currentColor}
.df-compose{display:grid;gap:10px;padding:20px 24px;background:rgba(28,35,51,.6)}
.df-clabel{margin:0;font-size:.86rem;font-weight:700}
.df-crow{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px}
.df-hint{margin:0;color:var(--df-muted);font-size:.8rem}
.df-status{color:var(--df-ok);font-size:.86rem;font-weight:600}
.df-status:empty{display:none}
.df-notice{--df-bg:#0b0e14;--df-panel:#131823;--df-panel2:#1c2333;--df-line:#2a3245;--df-ink:#e6e9f2;--df-muted:#9aa3b8;--df-accent:#a78bfa;--df-ok:#4ade80;overflow:hidden;border:1px solid var(--df-line);border-radius:16px;background:var(--df-panel);color:var(--df-ink);font-size:.9rem;line-height:1.5}
.df-nrow{display:flex;align-items:flex-start;gap:12px;padding:16px 20px;border-bottom:1px solid var(--df-line)}
.df-nrow:last-child{border-bottom:0}
.df-nicon{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:10px;background:#3a2c0b;color:#fcd34d}
.df-nicon.t2{background:var(--df-panel2);color:var(--df-muted)}
.df-ntext{color:var(--df-muted)}
.df-ntext b{display:block;color:var(--df-ink)}
.df-link{border-radius:2px;color:var(--df-accent);font-weight:600;text-decoration:underline;text-underline-offset:2px}
.df-link:hover{color:#c4b5fd}
@media (max-width:640px){
  .df-top{padding:18px 16px}
  .df-post{grid-template-columns:1fr}
  .df-user{flex-direction:row;flex-wrap:wrap;align-items:center;gap:4px 10px;padding:16px 16px 0;border-right:0;text-align:left}
  .df-av{width:36px;height:36px;margin:0;border-radius:10px;font-size:.74rem}
  .df-body{padding:10px 16px 16px}
  .df-compose{padding:18px 16px}
}
.df-tag{padding:2px 8px;border-radius:6px;background:#2a1f45;color:var(--df-accent)}
.df-tag.t2{background:#10301f;color:var(--df-ok)}
.df-mod{padding:2px 6px;border-radius:6px;background:#3a2c0b;color:#fcd34d;font-size:.68rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
.df-mod.op{background:#1d2a44;color:#93c5fd}
.df-act{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:none;color:var(--df-muted);font-size:.82rem;font-weight:600;cursor:pointer;transition:background-color .15s,color .15s}
.df-act:hover{background:var(--df-panel2);color:var(--df-ink)}
.df-act[aria-pressed=true]{background:#2a1f45;color:var(--df-accent)}
.df-field{width:100%;min-height:96px;padding:12px 14px;border:1.5px solid #5b6680;border-radius:12px;background:var(--df-bg);color:var(--df-ink);resize:vertical}
.df-field::placeholder{color:var(--df-muted)}
.df-field:focus{border-color:var(--df-accent);outline:2px solid transparent;box-shadow:0 0 0 3px rgba(167,139,250,.3)}
.df-btn{display:inline-flex;align-items:center;min-height:40px;padding:0 20px;border:0;border-radius:999px;background:#7c3aed;color:#fff;font-weight:700;cursor:pointer}
.df-btn:hover{background:#6d28d9}
:is(.df,.df-notice) :is(.df-act,.df-btn,.df-num,.df-link):focus-visible{outline:2px solid var(--df-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.df-act{transition:none}}
JavaScript
/* Forum: like toggles (aria-pressed with a count), Quote puts the post into the reply box as a markdown quote,
   and Post reply confirms in a status line. Frontend only: send the reply to your backend in the submit handler. */
document.querySelectorAll('[data-forum]').forEach(function (root) {
  var form = root.querySelector('[data-composer]'), field = form.querySelector('textarea'), status = form.querySelector('[data-status]');
  root.addEventListener('click', function (e) {
    var like = e.target.closest('[data-like]'), quote = e.target.closest('[data-quote]');
    if (like) {
      var on = like.getAttribute('aria-pressed') !== 'true', n = like.querySelector('[data-n]');
      like.setAttribute('aria-pressed', on); n.textContent = +n.textContent + (on ? 1 : -1);
    }
    if (quote) {
      field.value = '> ' + quote.dataset.user + ' wrote: ' + quote.dataset.quote + '\n\n' + field.value;
      field.focus(); field.setSelectionRange(field.value.length, field.value.length);
    }
  });
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!field.value.trim()) { field.focus(); return; }
    status.textContent = 'Reply posted. Thanks for helping out.';
    field.value = '';
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Forum topic with a solved answer and a reply box</span>
<section class="card df" data-forum aria-labelledby="df-h">
  <div class="df-top">
    <p class="df-crumbs"><span class="badge df-tag">3D printing</span><span class="badge df-tag t2">Solved</span></p>
    <h2 class="df-h" id="df-h">First layer stopped sticking to my PEI sheet</h2>
    <p class="df-stats"><span><b>3</b> replies</span><span><b>1.2k</b> views</span><span>Last reply <time datetime="2026-10-16T18:30">3 days ago</time></span></p>
  </div>
  <ol class="list-unstyled df-posts">
    <li class="df-post" id="df-post-1" data-post>
      <div class="df-user"><span class="df-av c3" aria-hidden="true">KM</span><b class="df-uname">kai.makes</b><span class="badge df-mod op">Original poster</span><span class="df-umeta">Joined Jan 2025 · 42 posts</span></div>
      <article class="df-body" aria-label="Post 1 by kai.makes">
        <p class="df-pmeta"><a class="df-num" href="#df-post-1">#1</a><time datetime="2026-10-15T20:14">4 days ago</time></p>
        <div class="df-content"><p>After about 200 prints my textured PEI sheet stopped holding PLA. Bed at 60 °C, nozzle at 210 °C, first layer <code>0.2 mm</code>, and I wipe it with isopropyl before every print.</p><p>The corners lift within the first five minutes. Any ideas before I buy a new sheet?</p></div>
        <div class="df-acts"><button class="btn df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="visually-hidden">Like post 1,</span> <span data-n>3</span></button><button class="btn df-act" type="button" data-quote="After about 200 prints my textured PEI sheet stopped holding PLA." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="visually-hidden"> post 1</span></button></div>
      </article>
    </li>
    <li class="df-post ok" id="df-post-2" data-post>
      <div class="df-user"><span class="df-av c1" aria-hidden="true">FP</span><b class="df-uname">ferro_print</b><span class="badge df-mod">Moderator</span><span class="df-umeta">Joined Jun 2019 · 8,412 posts</span></div>
      <article class="df-body" aria-label="Post 2 by ferro_print">
        <p class="df-pmeta"><a class="df-num" href="#df-post-2">#2</a><time datetime="2026-10-15T21:02">4 days ago</time><span class="df-sol"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 5 5 9-10"/></svg>Accepted solution</span></p>
        <div class="df-content"><p>Isopropyl removes grease, but not the film that builds up over time. Wash the sheet with hot water and a drop of washing-up liquid, dry it with a clean paper towel and do not touch the surface afterwards.</p><p>Every 200 to 300 prints is normal.</p></div>
        <div class="df-acts"><button class="btn df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="visually-hidden">Like post 2,</span> <span data-n>27</span></button><button class="btn df-act" type="button" data-quote="Every 200 to 300 prints is normal." data-user="ferro_print"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="visually-hidden"> post 2</span></button></div>
      </article>
    </li>
    <li class="df-post" id="df-post-3" data-post>
      <div class="df-user"><span class="df-av c2" aria-hidden="true">LP</span><b class="df-uname">linnea.p</b><span class="df-umeta">Joined Mar 2024 · 310 posts</span></div>
      <article class="df-body" aria-label="Post 3 by linnea.p">
        <p class="df-pmeta"><a class="df-num" href="#df-post-3">#3</a><time datetime="2026-10-16T07:45">3 days ago</time></p>
        <div class="df-content"><blockquote class="df-quote"><cite>ferro_print wrote:</cite>Every 200 to 300 prints is normal.</blockquote><p>Can confirm. I also raised the first layer to <code>0.24 mm</code> and it has stuck perfectly since.</p></div>
        <div class="df-acts"><button class="btn df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="visually-hidden">Like post 3,</span> <span data-n>5</span></button><button class="btn df-act" type="button" data-quote="I also raised the first layer to 0.24 mm." data-user="linnea.p"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="visually-hidden"> post 3</span></button></div>
      </article>
    </li>
    <li class="df-post" id="df-post-4" data-post>
      <div class="df-user"><span class="df-av c3" aria-hidden="true">KM</span><b class="df-uname">kai.makes</b><span class="badge df-mod op">Original poster</span><span class="df-umeta">Joined Jan 2025 · 42 posts</span></div>
      <article class="df-body" aria-label="Post 4 by kai.makes">
        <p class="df-pmeta"><a class="df-num" href="#df-post-4">#4</a><time datetime="2026-10-16T18:30">3 days ago</time></p>
        <div class="df-content"><p>Washing-up liquid did it. Twenty prints since, not one lifted corner. Thanks both, marking this as solved.</p></div>
        <div class="df-acts"><button class="btn df-act" type="button" aria-pressed="false" data-like><svg class="df-heart" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="visually-hidden">Like post 4,</span> <span data-n>2</span></button><button class="btn df-act" type="button" data-quote="Washing-up liquid did it." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="visually-hidden"> post 4</span></button></div>
      </article>
    </li>
  </ol>
  <form class="df-compose" data-composer>
    <label class="form-label df-clabel" for="df-reply">Your reply</label>
    <textarea class="form-control df-field" id="df-reply" rows="3" placeholder="Share what worked for you" aria-describedby="df-reply-hint"></textarea>
    <div class="df-crow"><p class="form-text df-hint" id="df-reply-hint">Markdown works. Use Quote on any post to reply to it.</p><button class="btn df-btn" type="submit">Post reply</button></div>
    <p class="df-status" role="status" data-status></p>
  </form>
</section></div>

<div><span class="lbl">Pinned and locked notices</span>
<div class="card df-notice">
  <p class="df-nrow"><span class="df-nicon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 4h6l-1 6 4 4H6l4-4zM12 14v7"/></svg></span><span class="df-ntext"><b>Pinned by moderators</b>Add your printer model and slicer settings to every help topic. <a class="df-link" href="#">Posting guide</a></span></p>
  <p class="df-nrow"><span class="df-nicon t2"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span><span class="df-ntext"><b>This topic is locked</b>It closed automatically 30 days after the last reply. Start a new topic and link back to this one.</span></p>
</div></div>
CSS
/* Dark forum topic on Bootstrap in data-bs-theme="dark": .card panels, .badge tags, .btn actions and a .form-control reply box.
   Posts have a user column, an accepted solution, quotes and like toggles (aria-pressed); Quote fills in the reply box. */
.df,.df-notice{--df-bg:#0b0e14;--df-panel:#131823;--df-panel2:#1c2333;--df-line:#2a3245;--df-ink:#e6e9f2;--df-muted:#9aa3b8;--df-accent:#a78bfa;--df-ok:#4ade80;--bs-card-bg:var(--df-panel);--bs-card-border-color:var(--df-line);--bs-card-border-radius:16px;--bs-card-color:var(--df-ink);--bs-body-color:var(--df-ink)}
.df{overflow:hidden;font-size:.94rem;line-height:1.6;box-shadow:0 24px 60px -30px rgba(0,0,0,.8)}
:is(.df,.df-notice) :where(h2,p,ol,blockquote){margin:0}
.df-top{padding:22px 24px;border-bottom:1px solid var(--df-line);background:linear-gradient(180deg,#191f2d,var(--df-panel))}
.df-crumbs{display:flex;flex-wrap:wrap;gap:6px;font-size:.78rem;font-weight:600}
.df-h{margin-top:10px;color:#fff;font-size:1.35rem;font-weight:700;line-height:1.3;letter-spacing:-.01em}
.df-stats{display:flex;flex-wrap:wrap;gap:4px 16px;margin-top:8px;color:var(--df-muted);font-size:.82rem}
.df-stats b{color:var(--df-ink)}
.df-posts{margin:0;padding:0}
.df-post{display:grid;grid-template-columns:150px minmax(0,1fr);border-bottom:1px solid var(--df-line)}
.df-post.ok{background:linear-gradient(90deg,rgba(74,222,128,.07),transparent 50%);box-shadow:inset 3px 0 0 var(--df-ok)}
.df-user{display:flex;flex-direction:column;align-items:center;gap:4px;padding:20px 12px;border-right:1px solid var(--df-line);color:var(--df-muted);font-size:.78rem;text-align:center}
.df-av{display:grid;place-items:center;width:48px;height:48px;margin-bottom:4px;border-radius:14px;font-size:.9rem;font-weight:700}
:is(.df) .c1{background:#3b2a63;color:#ddd6fe}
:is(.df) .c2{background:#123b2b;color:#86efac}
:is(.df) .c3{background:#4a2318;color:#fdba74}
.df-uname{color:var(--df-ink);font-size:.9rem;font-weight:700}
.df-umeta{line-height:1.45}
.df-body{min-width:0;padding:20px 24px}
.df-pmeta{display:flex;flex-wrap:wrap;align-items:center;gap:10px;color:var(--df-muted);font-size:.8rem}
.df-num{border-radius:2px;color:var(--df-muted);font-weight:600;text-decoration:none}
.df-num:hover{color:var(--df-accent)}
.df-sol{display:inline-flex;align-items:center;gap:6px;color:var(--df-ok);font-weight:700}
.df-content{margin-top:8px}
.df-content p{text-wrap:pretty}
.df-content p+p{margin-top:10px}
.df-content code{padding:1px 6px;border-radius:5px;background:var(--df-panel2);color:#f0abfc;font-size:.86em}
.df-quote{margin:10px 0;padding:10px 14px;border-left:3px solid var(--df-accent);border-radius:0 10px 10px 0;background:var(--df-panel2);color:#c9cfdd;font-size:.9rem}
.df-quote cite{display:block;margin-bottom:2px;color:var(--df-accent);font-size:.78rem;font-style:normal;font-weight:700}
.df-acts{display:flex;flex-wrap:wrap;gap:4px;margin-top:12px}
[aria-pressed=true] .df-heart{fill:currentColor}
.df-compose{display:grid;gap:10px;padding:20px 24px;background:rgba(28,35,51,.6)}
.df-clabel{margin:0;font-size:.86rem;font-weight:700}
.df-crow{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px}
.df-hint{margin:0;color:var(--df-muted);font-size:.8rem}
.df-status{color:var(--df-ok);font-size:.86rem;font-weight:600}
.df-status:empty{display:none}
.df-notice{--df-bg:#0b0e14;--df-panel:#131823;--df-panel2:#1c2333;--df-line:#2a3245;--df-ink:#e6e9f2;--df-muted:#9aa3b8;--df-accent:#a78bfa;--df-ok:#4ade80;overflow:hidden;border:1px solid var(--df-line);border-radius:16px;background:var(--df-panel);color:var(--df-ink);font-size:.9rem;line-height:1.5}
.df-nrow{display:flex;align-items:flex-start;gap:12px;padding:16px 20px;border-bottom:1px solid var(--df-line)}
.df-nrow:last-child{border-bottom:0}
.df-nicon{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:10px;background:#3a2c0b;color:#fcd34d}
.df-nicon.t2{background:var(--df-panel2);color:var(--df-muted)}
.df-ntext{color:var(--df-muted)}
.df-ntext b{display:block;color:var(--df-ink)}
.df-link{border-radius:2px;color:var(--df-accent);font-weight:600;text-decoration:underline;text-underline-offset:2px}
.df-link:hover{color:#c4b5fd}
@media (max-width:640px){
  .df-top{padding:18px 16px}
  .df-post{grid-template-columns:1fr}
  .df-user{flex-direction:row;flex-wrap:wrap;align-items:center;gap:4px 10px;padding:16px 16px 0;border-right:0;text-align:left}
  .df-av{width:36px;height:36px;margin:0;border-radius:10px;font-size:.74rem}
  .df-body{padding:10px 16px 16px}
  .df-compose{padding:18px 16px}
}
.df-tag{--bs-badge-padding-x:8px;--bs-badge-padding-y:3px;--bs-badge-font-size:.78rem;--bs-badge-font-weight:600;--bs-badge-color:var(--df-accent);--bs-badge-border-radius:6px;background:#2a1f45}
.df-tag.t2{--bs-badge-color:var(--df-ok);background:#10301f}
.df-mod{--bs-badge-padding-x:6px;--bs-badge-padding-y:3px;--bs-badge-font-size:.68rem;--bs-badge-color:#fcd34d;--bs-badge-border-radius:6px;background:#3a2c0b;letter-spacing:.06em;text-transform:uppercase}
.df-mod.op{--bs-badge-color:#93c5fd;background:#1d2a44}
.df .btn{--bs-btn-focus-box-shadow:none;--bs-btn-padding-y:0;--bs-btn-border-width:0}
.df .btn:focus-visible,:is(.df,.df-notice) :is(.df-num,.df-link):focus-visible{outline:2px solid var(--df-accent);outline-offset:2px}
.df-act{--bs-btn-padding-x:10px;--bs-btn-font-size:.82rem;--bs-btn-font-weight:600;--bs-btn-color:var(--df-muted);--bs-btn-border-radius:8px;--bs-btn-hover-color:var(--df-ink);--bs-btn-hover-bg:var(--df-panel2);--bs-btn-active-color:var(--df-accent);--bs-btn-active-bg:#2a1f45;display:inline-flex;align-items:center;gap:6px;min-height:32px}
.df-act[aria-pressed=true]{--bs-btn-color:var(--df-accent);--bs-btn-bg:#2a1f45;--bs-btn-hover-color:var(--df-accent);--bs-btn-hover-bg:#2a1f45}
.df-field{--bs-body-bg:var(--df-bg);min-height:96px;padding:12px 14px;border:1.5px solid #5b6680;border-radius:12px;background:var(--df-bg);color:var(--df-ink);font-size:inherit}
.df-field::placeholder{color:var(--df-muted)}
.df-field:focus{border-color:var(--df-accent);background:var(--df-bg);color:var(--df-ink);box-shadow:0 0 0 3px rgba(167,139,250,.3)}
.df-btn{--bs-btn-padding-x:20px;--bs-btn-font-weight:700;--bs-btn-color:#fff;--bs-btn-bg:#7c3aed;--bs-btn-border-radius:999px;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6d28d9;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6d28d9;display:inline-flex;align-items:center;min-height:40px}
.df code{color:#f0abfc}
@media (prefers-reduced-motion:reduce){.df .btn{transition:none}}
JavaScript
/* Forum: like toggles (aria-pressed with a count), Quote puts the post into the reply box as a markdown quote,
   and Post reply confirms in a status line. Frontend only: send the reply to your backend in the submit handler. */
document.querySelectorAll('[data-forum]').forEach(function (root) {
  var form = root.querySelector('[data-composer]'), field = form.querySelector('textarea'), status = form.querySelector('[data-status]');
  root.addEventListener('click', function (e) {
    var like = e.target.closest('[data-like]'), quote = e.target.closest('[data-quote]');
    if (like) {
      var on = like.getAttribute('aria-pressed') !== 'true', n = like.querySelector('[data-n]');
      like.setAttribute('aria-pressed', on); n.textContent = +n.textContent + (on ? 1 : -1);
    }
    if (quote) {
      field.value = '> ' + quote.dataset.user + ' wrote: ' + quote.dataset.quote + '\n\n' + field.value;
      field.focus(); field.setSelectionRange(field.value.length, field.value.length);
    }
  });
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!field.value.trim()) { field.focus(); return; }
    status.textContent = 'Reply posted. Thanks for helping out.';
    field.value = '';
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa3b8] uppercase">Forum topic with a solved answer and a reply box</span>
<section class="overflow-hidden rounded-2xl border border-df-line bg-df-panel text-[.94rem] leading-[1.6] text-df-ink shadow-[0_24px_60px_-30px_rgba(0,0,0,.8)]" data-forum aria-labelledby="df-h">
  <div class="border-b border-df-line bg-[linear-gradient(180deg,#191f2d,#131823)] px-6 py-[22px] max-sm:px-4">
    <p class="flex flex-wrap gap-1.5 text-[.78rem] font-semibold"><span class="rounded-md bg-[#2a1f45] px-2 py-0.5 text-df-accent">3D printing</span><span class="rounded-md bg-[#10301f] px-2 py-0.5 text-df-ok">Solved</span></p>
    <h2 class="mt-2.5 text-[1.35rem] leading-[1.3] font-bold tracking-[-.01em] text-white" id="df-h">First layer stopped sticking to my PEI sheet</h2>
    <p class="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-[.82rem] text-df-muted [&_b]:text-df-ink"><span><b>3</b> replies</span><span><b>1.2k</b> views</span><span>Last reply <time datetime="2026-10-16T18:30">3 days ago</time></span></p>
  </div>
  <ol class="">
    <li class="grid grid-cols-[150px_minmax(0,1fr)] border-b border-df-line max-sm:grid-cols-1" id="df-post-1" data-post>
      <div class="flex flex-col items-center gap-1 border-r border-df-line px-3 py-5 text-center text-[.78rem] text-df-muted max-sm:flex-row max-sm:flex-wrap max-sm:items-center max-sm:gap-x-2.5 max-sm:border-r-0 max-sm:px-4 max-sm:pt-4 max-sm:pb-0 max-sm:text-left"><span class="mb-1 grid size-12 place-items-center rounded-[14px] text-[.9rem] font-bold max-sm:mb-0 max-sm:size-9 max-sm:rounded-[10px] max-sm:text-[.74rem] bg-[#4a2318] text-[#fdba74]" aria-hidden="true">KM</span><b class="text-[.9rem] font-bold text-df-ink">kai.makes</b><span class="rounded-md bg-[#1d2a44] px-1.5 py-0.5 text-[.68rem] font-bold tracking-[.06em] text-[#93c5fd] uppercase">Original poster</span><span class="leading-[1.45]">Joined Jan 2025 · 42 posts</span></div>
      <article class="min-w-0 px-6 py-5 max-sm:px-4 max-sm:pt-2.5 max-sm:pb-4" aria-label="Post 1 by kai.makes">
        <p class="flex flex-wrap items-center gap-2.5 text-[.8rem] text-df-muted"><a class="rounded-sm font-semibold text-df-muted no-underline hover:text-df-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" href="#df-post-1">#1</a><time datetime="2026-10-15T20:14">4 days ago</time></p>
        <div class="mt-2 [&_code]:rounded-[5px] [&_code]:bg-df-panel2 [&_code]:px-1.5 [&_code]:py-px [&_code]:text-[.86em] [&_code]:text-[#f0abfc] [&_p]:text-pretty [&_p+p]:mt-2.5"><p>After about 200 prints my textured PEI sheet stopped holding PLA. Bed at 60 °C, nozzle at 210 °C, first layer <code>0.2 mm</code>, and I wipe it with isopropyl before every print.</p><p>The corners lift within the first five minutes. Any ideas before I buy a new sheet?</p></div>
        <div class="mt-3 flex flex-wrap gap-1"><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" aria-pressed="false" data-like><svg class="group-aria-pressed/a:fill-current" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="sr-only">Like post 1,</span> <span data-n>3</span></button><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" data-quote="After about 200 prints my textured PEI sheet stopped holding PLA." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="sr-only"> post 1</span></button></div>
      </article>
    </li>
    <li class="grid grid-cols-[150px_minmax(0,1fr)] border-b border-df-line bg-[linear-gradient(90deg,rgba(74,222,128,.07),transparent_50%)] shadow-[inset_3px_0_0_#4ade80] max-sm:grid-cols-1" id="df-post-2" data-post>
      <div class="flex flex-col items-center gap-1 border-r border-df-line px-3 py-5 text-center text-[.78rem] text-df-muted max-sm:flex-row max-sm:flex-wrap max-sm:items-center max-sm:gap-x-2.5 max-sm:border-r-0 max-sm:px-4 max-sm:pt-4 max-sm:pb-0 max-sm:text-left"><span class="mb-1 grid size-12 place-items-center rounded-[14px] text-[.9rem] font-bold max-sm:mb-0 max-sm:size-9 max-sm:rounded-[10px] max-sm:text-[.74rem] bg-[#3b2a63] text-[#ddd6fe]" aria-hidden="true">FP</span><b class="text-[.9rem] font-bold text-df-ink">ferro_print</b><span class="rounded-md bg-[#3a2c0b] px-1.5 py-0.5 text-[.68rem] font-bold tracking-[.06em] text-[#fcd34d] uppercase">Moderator</span><span class="leading-[1.45]">Joined Jun 2019 · 8,412 posts</span></div>
      <article class="min-w-0 px-6 py-5 max-sm:px-4 max-sm:pt-2.5 max-sm:pb-4" aria-label="Post 2 by ferro_print">
        <p class="flex flex-wrap items-center gap-2.5 text-[.8rem] text-df-muted"><a class="rounded-sm font-semibold text-df-muted no-underline hover:text-df-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" href="#df-post-2">#2</a><time datetime="2026-10-15T21:02">4 days ago</time><span class="inline-flex items-center gap-1.5 font-bold text-df-ok"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 5 5 9-10"/></svg>Accepted solution</span></p>
        <div class="mt-2 [&_code]:rounded-[5px] [&_code]:bg-df-panel2 [&_code]:px-1.5 [&_code]:py-px [&_code]:text-[.86em] [&_code]:text-[#f0abfc] [&_p]:text-pretty [&_p+p]:mt-2.5"><p>Isopropyl removes grease, but not the film that builds up over time. Wash the sheet with hot water and a drop of washing-up liquid, dry it with a clean paper towel and do not touch the surface afterwards.</p><p>Every 200 to 300 prints is normal.</p></div>
        <div class="mt-3 flex flex-wrap gap-1"><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" aria-pressed="false" data-like><svg class="group-aria-pressed/a:fill-current" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="sr-only">Like post 2,</span> <span data-n>27</span></button><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" data-quote="Every 200 to 300 prints is normal." data-user="ferro_print"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="sr-only"> post 2</span></button></div>
      </article>
    </li>
    <li class="grid grid-cols-[150px_minmax(0,1fr)] border-b border-df-line max-sm:grid-cols-1" id="df-post-3" data-post>
      <div class="flex flex-col items-center gap-1 border-r border-df-line px-3 py-5 text-center text-[.78rem] text-df-muted max-sm:flex-row max-sm:flex-wrap max-sm:items-center max-sm:gap-x-2.5 max-sm:border-r-0 max-sm:px-4 max-sm:pt-4 max-sm:pb-0 max-sm:text-left"><span class="mb-1 grid size-12 place-items-center rounded-[14px] text-[.9rem] font-bold max-sm:mb-0 max-sm:size-9 max-sm:rounded-[10px] max-sm:text-[.74rem] bg-[#123b2b] text-[#86efac]" aria-hidden="true">LP</span><b class="text-[.9rem] font-bold text-df-ink">linnea.p</b><span class="leading-[1.45]">Joined Mar 2024 · 310 posts</span></div>
      <article class="min-w-0 px-6 py-5 max-sm:px-4 max-sm:pt-2.5 max-sm:pb-4" aria-label="Post 3 by linnea.p">
        <p class="flex flex-wrap items-center gap-2.5 text-[.8rem] text-df-muted"><a class="rounded-sm font-semibold text-df-muted no-underline hover:text-df-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" href="#df-post-3">#3</a><time datetime="2026-10-16T07:45">3 days ago</time></p>
        <div class="mt-2 [&_code]:rounded-[5px] [&_code]:bg-df-panel2 [&_code]:px-1.5 [&_code]:py-px [&_code]:text-[.86em] [&_code]:text-[#f0abfc] [&_p]:text-pretty [&_p+p]:mt-2.5"><blockquote class="my-2.5 rounded-r-[10px] border-l-[3px] border-df-accent bg-df-panel2 px-3.5 py-2.5 text-[.9rem] text-[#c9cfdd] [&_cite]:mb-0.5 [&_cite]:block [&_cite]:text-[.78rem] [&_cite]:font-bold [&_cite]:text-df-accent [&_cite]:not-italic"><cite>ferro_print wrote:</cite>Every 200 to 300 prints is normal.</blockquote><p>Can confirm. I also raised the first layer to <code>0.24 mm</code> and it has stuck perfectly since.</p></div>
        <div class="mt-3 flex flex-wrap gap-1"><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" aria-pressed="false" data-like><svg class="group-aria-pressed/a:fill-current" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="sr-only">Like post 3,</span> <span data-n>5</span></button><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" data-quote="I also raised the first layer to 0.24 mm." data-user="linnea.p"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="sr-only"> post 3</span></button></div>
      </article>
    </li>
    <li class="grid grid-cols-[150px_minmax(0,1fr)] border-b border-df-line max-sm:grid-cols-1" id="df-post-4" data-post>
      <div class="flex flex-col items-center gap-1 border-r border-df-line px-3 py-5 text-center text-[.78rem] text-df-muted max-sm:flex-row max-sm:flex-wrap max-sm:items-center max-sm:gap-x-2.5 max-sm:border-r-0 max-sm:px-4 max-sm:pt-4 max-sm:pb-0 max-sm:text-left"><span class="mb-1 grid size-12 place-items-center rounded-[14px] text-[.9rem] font-bold max-sm:mb-0 max-sm:size-9 max-sm:rounded-[10px] max-sm:text-[.74rem] bg-[#4a2318] text-[#fdba74]" aria-hidden="true">KM</span><b class="text-[.9rem] font-bold text-df-ink">kai.makes</b><span class="rounded-md bg-[#1d2a44] px-1.5 py-0.5 text-[.68rem] font-bold tracking-[.06em] text-[#93c5fd] uppercase">Original poster</span><span class="leading-[1.45]">Joined Jan 2025 · 42 posts</span></div>
      <article class="min-w-0 px-6 py-5 max-sm:px-4 max-sm:pt-2.5 max-sm:pb-4" aria-label="Post 4 by kai.makes">
        <p class="flex flex-wrap items-center gap-2.5 text-[.8rem] text-df-muted"><a class="rounded-sm font-semibold text-df-muted no-underline hover:text-df-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" href="#df-post-4">#4</a><time datetime="2026-10-16T18:30">3 days ago</time></p>
        <div class="mt-2 [&_code]:rounded-[5px] [&_code]:bg-df-panel2 [&_code]:px-1.5 [&_code]:py-px [&_code]:text-[.86em] [&_code]:text-[#f0abfc] [&_p]:text-pretty [&_p+p]:mt-2.5"><p>Washing-up liquid did it. Twenty prints since, not one lifted corner. Thanks both, marking this as solved.</p></div>
        <div class="mt-3 flex flex-wrap gap-1"><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" aria-pressed="false" data-like><svg class="group-aria-pressed/a:fill-current" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7-4.4-9.2-8.6C1.3 8.4 3.2 5 6.6 5c2.1 0 3.6 1.2 5.4 3 1.8-1.8 3.3-3 5.4-3 3.4 0 5.3 3.4 3.8 6.4C19 15.6 12 20 12 20Z"/></svg><span class="sr-only">Like post 4,</span> <span data-n>2</span></button><button class="group/a inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-lg px-2.5 text-[.82rem] font-semibold text-df-muted transition-colors duration-150 hover:bg-df-panel2 hover:text-df-ink aria-pressed:bg-[#2a1f45] aria-pressed:text-df-accent motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="button" data-quote="Washing-up liquid did it." data-user="kai.makes"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7h4v4c0 3-2 5-4 6M14 7h4v4c0 3-2 5-4 6"/></svg>Quote<span class="sr-only"> post 4</span></button></div>
      </article>
    </li>
  </ol>
  <form class="grid gap-2.5 bg-df-panel2/60 px-6 py-5 max-sm:px-4" data-composer>
    <label class="text-[.86rem] font-bold" for="df-reply">Your reply</label>
    <textarea class="min-h-24 w-full resize-y rounded-xl border-[1.5px] border-[#5b6680] bg-df-bg px-3.5 py-3 text-df-ink placeholder:text-df-muted focus:border-df-accent focus:shadow-[0_0_0_3px_rgba(167,139,250,.3)] focus:outline-2 focus:outline-transparent" id="df-reply" rows="3" placeholder="Share what worked for you" aria-describedby="df-reply-hint"></textarea>
    <div class="flex flex-wrap items-center justify-between gap-3"><p class="text-[.8rem] text-df-muted" id="df-reply-hint">Markdown works. Use Quote on any post to reply to it.</p><button class="inline-flex min-h-10 cursor-pointer items-center rounded-full bg-[#7c3aed] px-5 font-bold text-white hover:bg-[#6d28d9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" type="submit">Post reply</button></div>
    <p class="text-[.86rem] font-semibold text-df-ok empty:hidden" role="status" data-status></p>
  </form>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa3b8] uppercase">Pinned and locked notices</span>
<div class="overflow-hidden rounded-2xl border border-df-line bg-df-panel text-[.9rem] leading-normal text-df-ink">
  <p class="flex items-start gap-3 border-b border-df-line px-5 py-4 last:border-b-0"><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#3a2c0b] text-[#fcd34d]"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 4h6l-1 6 4 4H6l4-4zM12 14v7"/></svg></span><span class="text-df-muted [&_b]:block [&_b]:text-df-ink"><b>Pinned by moderators</b>Add your printer model and slicer settings to every help topic. <a class="rounded-sm font-semibold text-df-accent underline underline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-df-accent" href="#">Posting guide</a></span></p>
  <p class="flex items-start gap-3 border-b border-df-line px-5 py-4 last:border-b-0"><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-df-panel2 text-df-muted"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span><span class="text-df-muted [&_b]:block [&_b]:text-df-ink"><b>This topic is locked</b>It closed automatically 30 days after the last reply. Start a new topic and link back to this one.</span></p>
</div></div>
CSS
/* Palette as theme colours; pressed likes use aria-pressed: and group-aria-pressed/a:. */
@theme {
  --color-df-bg:#0b0e14; --color-df-panel:#131823; --color-df-panel2:#1c2333; --color-df-line:#2a3245;
  --color-df-ink:#e6e9f2; --color-df-muted:#9aa3b8; --color-df-accent:#a78bfa; --color-df-ok:#4ade80;
}
JavaScript
/* Forum: like toggles (aria-pressed with a count), Quote puts the post into the reply box as a markdown quote,
   and Post reply confirms in a status line. Frontend only: send the reply to your backend in the submit handler. */
document.querySelectorAll('[data-forum]').forEach(function (root) {
  var form = root.querySelector('[data-composer]'), field = form.querySelector('textarea'), status = form.querySelector('[data-status]');
  root.addEventListener('click', function (e) {
    var like = e.target.closest('[data-like]'), quote = e.target.closest('[data-quote]');
    if (like) {
      var on = like.getAttribute('aria-pressed') !== 'true', n = like.querySelector('[data-n]');
      like.setAttribute('aria-pressed', on); n.textContent = +n.textContent + (on ? 1 : -1);
    }
    if (quote) {
      field.value = '> ' + quote.dataset.user + ' wrote: ' + quote.dataset.quote + '\n\n' + field.value;
      field.focus(); field.setSelectionRange(field.value.length, field.value.length);
    }
  });
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!field.value.trim()) { field.focus(); return; }
    status.textContent = 'Reply posted. Thanks for helping out.';
    field.value = '';
  });
});

About this set

A forum topic in a dark theme, plus two notices that forums need sooner or later. The topic header shows a category tag, a Solved tag, the title and reply, view and last-reply counts. Each post has a user column with an initials avatar, the username, a Moderator or Original poster badge, the join date and post count, and a content column with a numbered permalink, the time, the post text with inline code, Like and Quote buttons. The moderator’s answer is marked Accepted solution with a green rail and a written label, and a later post quotes it in a styled blockquote with a cite line. Like toggles a heart and its count. Quote copies a short excerpt of the post into the reply box as a markdown quote and focuses it, and Post reply confirms in a status line. On phones the user column turns into a compact row above each post. The second block shows a pinned moderator note with a link and a locked-topic notice. All text colours were chosen for at least 4.5:1 contrast on the near-black panels.

What’s included

  • User column with role badges, join date and post count
  • Accepted solution marked with a rail and a written label
  • Like toggles with counts and aria-pressed
  • Quote button that fills the reply box with a markdown quote
  • Pinned and locked notices, and a stacked layout on phones

Accessibility

Each post is an article named by its number and author, roles and the solved state are written as text, and quotes use blockquote with a cite line. Like buttons use aria-pressed and include the post number in hidden text, Quote buttons say which post they quote, the reply field has a visible label and hint, and the confirmation is a status message. Focus outlines are violet on the dark panels, and the Bootstrap version runs in data-bs-theme="dark".

Customise it

Change --df-panel, --df-panel2, --df-accent and --df-ok on .df. Bootstrap uses the same variables on the .card in data-bs-theme="dark"; in Tailwind edit the --color-df-* theme colours.