← Component Library
Social Components

Social Patterns

The pieces that make a site social — sharing, following, comments, feeds, forums, and chat. Each with a live demo and the HTML & CSS.

01 · Share Buttons

Let people spread the word

A row of branded share links. Each is just an anchor to the platform's share URL with your page link — no SDK required.

HTML

<div class="share">
  <a class="x" href="https://twitter.com/intent/tweet?url=PAGE_URL">Share</a>
  <a class="fb" href="https://www.facebook.com/sharer/sharer.php?u=PAGE_URL">Share</a>
  <a class="li" href="https://www.linkedin.com/sharing/share-offsite/?url=PAGE_URL">Share</a>
  <a class="wa" href="https://wa.me/?text=PAGE_URL">Share</a>
</div>

CSS

.share { display: flex; gap: 10px; flex-wrap: wrap; }
.share a { display: inline-flex; align-items: center; gap: 8px;
           padding: 9px 14px; border-radius: 8px; color: #fff;
           text-decoration: none; font-weight: 600; }
.share .x  { background: #111; }
.share .fb { background: #1877f2; }
.share .li { background: #0a66c2; }
.share .wa { background: #25d366; }

JavaScript

// Open the right share URL when a button is clicked.
// data-share="twitter|facebook|linkedin|whatsapp" picks the target.
document.querySelectorAll('.share a').forEach(function (a) {
  a.addEventListener('click', function (e) {
    e.preventDefault();
    var url = encodeURIComponent(window.location.href);
    var title = encodeURIComponent(document.title);
    var map = {
      twitter:  'https://twitter.com/intent/tweet?url=' + url + '&text=' + title,
      facebook: 'https://www.facebook.com/sharer/sharer.php?u=' + url,
      linkedin: 'https://www.linkedin.com/sharing/share-offsite/?url=' + url,
      whatsapp: 'https://wa.me/?text=' + title + '%20' + url
    };
    var kind = a.dataset.share || (a.classList.contains('x') ? 'twitter'
                                : a.classList.contains('fb') ? 'facebook'
                                : a.classList.contains('li') ? 'linkedin' : 'whatsapp');
    // Try the native share sheet first when available (mobile)
    if (navigator.share && a.dataset.share === 'native') {
      navigator.share({ title: document.title, url: window.location.href });
      return;
    }
    window.open(map[kind], '_blank', 'noopener,width=600,height=520');
  });
});

// "Copy link" variant: copy current URL and show "Copied!" for 1.5s
var copyBtn = document.querySelector('.share .copy');
if (copyBtn) {
  copyBtn.addEventListener('click', function () {
    navigator.clipboard.writeText(window.location.href);
    var original = copyBtn.textContent;
    copyBtn.textContent = 'Copied!';
    setTimeout(function () { copyBtn.textContent = original; }, 1500);
  });
}
02 · Follow Buttons

Grow an audience

A follow button toggles between “Follow” and “Following,” usually paired with a follower count. Show the two states clearly.

2,481 followers

HTML

<button class="follow" aria-pressed="false">Follow</button>
<button class="follow following" aria-pressed="true">Following</button>
<span class="count"><b>2,481</b> followers</span>

CSS

.follow {
  border-radius: 999px; padding: 9px 18px; font-weight: 700;
  background: #38bdf8; color: #fff; border: none;
}
.follow.following {           /* toggled state */
  background: #fff; color: #111; border: 1px solid #cbd5e1;
}

JavaScript

// Toggle Follow / Following with a live count, persisted per user.
// HTML expected:
//   <button class="follow" data-user="ada">Follow</button>
//   <span class="count"><b id="followers">2,481</b> followers</span>
document.querySelectorAll('.follow').forEach(function (btn) {
  var user = btn.dataset.user || 'default';
  var key = 'following:' + user;
  var saved = localStorage.getItem(key) === '1';

  // restore saved state on load
  if (saved) {
    btn.classList.add('following');
    btn.setAttribute('aria-pressed', 'true');
    btn.textContent = 'Following';
  }

  btn.addEventListener('click', function () {
    var nowFollowing = !btn.classList.contains('following');
    btn.classList.toggle('following', nowFollowing);
    btn.setAttribute('aria-pressed', String(nowFollowing));
    btn.textContent = nowFollowing ? 'Following' : 'Follow';
    localStorage.setItem(key, nowFollowing ? '1' : '0');

    // bump follower count up/down
    var counter = document.getElementById('followers');
    if (counter) {
      var n = parseInt(counter.textContent.replace(/,/g, ''), 10) || 0;
      counter.textContent = (n + (nowFollowing ? 1 : -1)).toLocaleString();
    }
  });
});
03 · Comments Section

Let visitors talk back

Each comment shows an avatar, name, timestamp, the message, and actions (like, reply). A simple flex row per comment.

Sam R. · 2h ago

This tutorial finally made flexbox click for me. Thank you!

12 Reply
Dana K. · 1h ago

Agreed — the live demos make all the difference.

4 Reply

HTML

<article class="comment">
  <img class="avatar" src="sam.jpg" alt="">
  <div>
    <p><b>Sam R.</b> <time>· 2h ago</time></p>
    <p>This tutorial finally made flexbox click for me. Thank you!</p>
    <div class="actions"><button>♥ 12</button><button>Reply</button></div>
  </div>
</article>
<article class="comment">
  <img class="avatar" src="dana.jpg" alt="">
  <div>
    <p><b>Dana K.</b> <time>· 1h ago</time></p>
    <p>Agreed — the live demos make all the difference.</p>
    <div class="actions"><button>♥ 4</button><button>Reply</button></div>
  </div>
</article>

CSS

.comment { display: flex; gap: 12px; }
.avatar  { width: 38px; height: 38px; border-radius: 50%; }
.actions { display: flex; gap: 14px; font-size: .8rem; }

JavaScript

// Comments: post new, toggle like, reveal reply form.
// HTML expected: a <form id="commentForm"> with <input name="body">
// and a container <div class="comments">...</div>
var commentForm = document.getElementById('commentForm');
var commentsList = document.querySelector('.comments');

if (commentForm && commentsList) {
  commentForm.addEventListener('submit', function (e) {
    e.preventDefault();
    var body = commentForm.body.value.trim();
    if (!body) return;
    var html =
      '<article class="comment">' +
        '<img class="avatar" src="you.jpg" alt="">' +
        '<div>' +
          '<p><b>You</b> <time>· just now</time></p>' +
          '<p>' + body.replace(/</g,'&lt;') + '</p>' +
          '<div class="actions">' +
            '<button class="like">♥ 0</button>' +
            '<button class="reply">Reply</button>' +
          '</div>' +
        '</div>' +
      '</article>';
    commentsList.insertAdjacentHTML('afterbegin', html);
    commentForm.reset();
  });
}

// Like button: toggle .liked + increment/decrement count
commentsList && commentsList.addEventListener('click', function (e) {
  var like = e.target.closest('.like');
  if (like) {
    var liked = like.classList.toggle('liked');
    var n = parseInt(like.textContent.replace(/\D/g, ''), 10) || 0;
    like.innerHTML = '♥ ' + (n + (liked ? 1 : -1));
    return;
  }
  // Reply button: reveal an inline reply form
  var reply = e.target.closest('.reply');
  if (reply) {
    var comment = reply.closest('.comment');
    var existing = comment.querySelector('.reply-form');
    if (existing) { existing.remove(); return; }
    var form = document.createElement('form');
    form.className = 'reply-form';
    form.innerHTML = '<input name="r" placeholder="Write a reply…"><button>Post</button>';
    comment.appendChild(form);
    form.r.focus();
  }
});
04 · User Settings

Toggle preferences

A settings list pairs each option with a switch. Stack rows with a label, a short description, and the toggle on the right.

Email notifications
Get updates about your account
Public profile
Anyone can view your profile

HTML

<div class="setting">
  <div>
    <p class="label">Email notifications</p>
    <p class="sub">Get updates about your account</p>
  </div>
  <button class="switch on" role="switch" aria-checked="true"></button>
</div>
<div class="setting">
  <div>
    <p class="label">Public profile</p>
    <p class="sub">Anyone can view your profile</p>
  </div>
  <button class="switch" role="switch" aria-checked="false"></button>
</div>

CSS

.setting { display: flex; justify-content: space-between; align-items: center; }
.switch  { width: 44px; height: 26px; border-radius: 999px; background: #cbd5e1; position: relative; }
.switch::after { content: ""; position: absolute; top: 3px; left: 3px;
                 width: 20px; height: 20px; border-radius: 50%; background: #fff; }
.switch.on { background: #22c55e; } .switch.on::after { left: 21px; }

JavaScript

// Toggle switches save to localStorage; "Save" shows a "Settings saved" toast.
document.querySelectorAll('.setting .switch').forEach(function (sw) {
  var key = 'pref:' + (sw.dataset.key || sw.previousElementSibling?.querySelector('.label')?.textContent || Math.random());
  // restore
  if (localStorage.getItem(key) === '1') {
    sw.classList.add('on');
    sw.setAttribute('aria-checked', 'true');
  }
  sw.addEventListener('click', function () {
    var on = sw.classList.toggle('on');
    sw.setAttribute('aria-checked', String(on));
    localStorage.setItem(key, on ? '1' : '0');
  });
});

// Simple toast for the Save button
function showToast(msg) {
  var t = document.createElement('div');
  t.textContent = msg;
  t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);' +
    'background:#111;color:#fff;padding:10px 18px;border-radius:8px;font-size:.9rem;z-index:9999;';
  document.body.appendChild(t);
  setTimeout(function () { t.remove(); }, 1800);
}
var settingsForm = document.getElementById('settingsForm');
settingsForm && settingsForm.addEventListener('submit', function (e) {
  e.preventDefault();
  showToast('Settings saved');
});
05 · Activity Feed

A timeline of what happened

A vertical feed of events, each with an icon, a short description, and a timestamp — connected by a line to read as a stream.

Sam liked your post
2h ago
Dana started following you
5h ago
Lee commented on your photo
1d ago

HTML

<ul class="feed">
  <li><span class="icon">♥</span>
      <div><p><b>Sam</b> liked your post</p><time>2h ago</time></div></li>
  <li><span class="icon">+</span>
      <div><p><b>Dana</b> started following you</p><time>5h ago</time></div></li>
  <li><span class="icon">💬</span>
      <div><p><b>Lee</b> commented on your photo</p><time>1d ago</time></div></li>
</ul>

CSS

.feed li { display: flex; gap: 12px; padding: 12px 0; position: relative; }
.feed li::before {            /* the connecting line */
  content: ""; position: absolute; left: 18px; top: 40px; bottom: -12px;
  width: 2px; background: #e5e7eb;
}
.icon { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; }

JavaScript

// "Load more" appends ~3 simulated feed items; time-ago strings auto-refresh.
var feed = document.querySelector('.feed');
var loadMore = document.getElementById('loadMore');
var fakeItems = [
  { icon: '♥',   text: '<b>Mia</b> liked your post',         ts: Date.now() - 60 * 60 * 3 * 1000 },
  { icon: '+',     text: '<b>Jordan</b> started following you', ts: Date.now() - 60 * 60 * 8 * 1000 },
  { icon: '💬', text: '<b>Ada</b> commented on your photo',  ts: Date.now() - 60 * 60 * 26 * 1000 }
];

function timeAgo(ms) {
  var s = Math.max(1, Math.floor((Date.now() - ms) / 1000));
  if (s < 60)      return s + 's ago';
  if (s < 3600)    return Math.floor(s / 60) + 'm ago';
  if (s < 86400)   return Math.floor(s / 3600) + 'h ago';
  return Math.floor(s / 86400) + 'd ago';
}

loadMore && loadMore.addEventListener('click', function () {
  fakeItems.forEach(function (it) {
    var li = document.createElement('li');
    li.dataset.ts = it.ts;
    li.innerHTML = '<span class="icon">' + it.icon + '</span>' +
                   '<div><p>' + it.text + '</p>' +
                   '<time>' + timeAgo(it.ts) + '</time></div>';
    feed.appendChild(li);
  });
});

// auto-refresh every minute
setInterval(function () {
  feed.querySelectorAll('li[data-ts] time').forEach(function (t) {
    t.textContent = timeAgo(+t.closest('li').dataset.ts);
  });
}, 60000);
06 · Community Forum

A list of discussion threads

Each thread row shows a title, who started it, and a reply count. Keep the title prominent and the reply count aligned right.

How do I center a div in 2026?
by Ada · 3d ago
24
Best free fonts for body text?
by Lee · 1d ago
9
Show your portfolio — feedback welcome
by Mia · 6h ago
41

HTML

<ul class="forum">
  <li class="thread">
    <div>
      <p class="title">How do I center a div in 2026?</p>
      <p class="by">by Ada · 3d ago</p>
    </div>
    <span class="replies">24</span>
  </li>
  <li class="thread">
    <div>
      <p class="title">Best free fonts for body text?</p>
      <p class="by">by Lee · 1d ago</p>
    </div>
    <span class="replies">9</span>
  </li>
  <li class="thread">
    <div>
      <p class="title">Show your portfolio — feedback welcome</p>
      <p class="by">by Mia · 6h ago</p>
    </div>
    <span class="replies">41</span>
  </li>
</ul>

CSS

.thread { display: flex; align-items: center; gap: 12px;
          padding: 12px 14px; border-bottom: 1px solid #f1f5f9; }
.replies { margin-left: auto; font-weight: 700; }  /* push count right */

JavaScript

// Forum voting: .vote-up / .vote-down update a .score; .mark-solved toggles a ribbon.
document.querySelectorAll('.thread').forEach(function (thread) {
  var score = thread.querySelector('.score');
  thread.addEventListener('click', function (e) {
    if (e.target.closest('.vote-up') && score) {
      score.textContent = (parseInt(score.textContent, 10) || 0) + 1;
    } else if (e.target.closest('.vote-down') && score) {
      score.textContent = (parseInt(score.textContent, 10) || 0) - 1;
    } else if (e.target.closest('.mark-solved')) {
      thread.classList.toggle('solved');
      var ribbon = thread.querySelector('.solved-ribbon');
      if (thread.classList.contains('solved') && !ribbon) {
        var r = document.createElement('span');
        r.className = 'solved-ribbon';
        r.textContent = 'Solved';
        r.style.cssText = 'background:#22c55e;color:#fff;padding:2px 8px;border-radius:6px;font-size:.7rem;margin-left:8px;';
        thread.querySelector('.title').appendChild(r);
      } else if (ribbon) {
        ribbon.remove();
      }
    }
  });
});
07 · Chat Widget

A messaging bubble

Incoming and outgoing messages sit on opposite sides with different colors. An input row sits pinned at the bottom.

Support
Hi! How can we help?
My order hasn't shipped yet.
Let me check that for you…

HTML

<div class="chat">
  <div class="head">Support</div>
  <div class="messages">
    <p class="bubble them">Hi! How can we help?</p>
    <p class="bubble me">My order hasn't shipped yet.</p>
    <p class="bubble them">Let me check that for you…</p>
  </div>
  <form class="input"><input placeholder="Type a message…"><button>Send</button></form>
</div>

CSS

.chat { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; }
.head { background: #0b1a33; color: #fff; padding: 10px 14px; font-weight: 700; }
.messages { padding: 14px; display: grid; gap: 8px; }
.bubble { max-width: 75%; padding: 8px 12px; border-radius: 14px; }
.bubble.them { background: #eef1f5; }                 /* left */
.bubble.me   { background: #38bdf8; color: #fff; margin-left: auto; }  /* right */

JavaScript

// Chat: open panel on bubble click, post message + simulated bot reply, close panel.
var chatBubble = document.querySelector('.chat-bubble');
var chatPanel  = document.querySelector('.chat');
var chatClose  = document.querySelector('.chat .close');
var chatForm   = document.querySelector('.chat .input');
var chatInput  = chatForm && chatForm.querySelector('input');
var chatBody   = document.querySelector('.chat .messages');

chatBubble && chatBubble.addEventListener('click', function () {
  chatPanel.classList.add('open');
});
chatClose && chatClose.addEventListener('click', function () {
  chatPanel.classList.remove('open');
});

chatForm && chatForm.addEventListener('submit', function (e) {
  e.preventDefault();
  var text = chatInput.value.trim();
  if (!text) return;
  var mine = document.createElement('p');
  mine.className = 'bubble me';
  mine.textContent = text;
  chatBody.appendChild(mine);
  chatInput.value = '';
  chatBody.scrollTop = chatBody.scrollHeight;

  // Simulated bot reply after 600ms
  setTimeout(function () {
    var reply = document.createElement('p');
    reply.className = 'bubble them';
    reply.textContent = 'Let me check that for you…';
    chatBody.appendChild(reply);
    chatBody.scrollTop = chatBody.scrollHeight;
  }, 600);
});
08 · Full Page

The whole thing as one file

Every pattern above — share, follow, comments, settings, activity feed, forum, and chat — combined into one complete HTML file: a small community hub. Copy it into a new file called community.html, open it in a browser, and it just works — no libraries, no build step. Here's the live result, then the full code.

Live preview

Complete HTML — copy this whole file

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Community Hub</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
           background: #f6f7fb; color: #1f2433; line-height: 1.6; padding: 24px; }
    .wrap { max-width: 760px; margin: 0 auto; display: grid; gap: 20px; }
    .card { background: #fff; border: 1px solid #e5e7eb; border-radius: 14px; padding: 22px; }
    .card h2 { font-size: 1.05rem; margin-bottom: 14px; }
    .page-head { text-align: center; padding: 8px 0 4px; }
    .page-head h1 { font-size: 1.8rem; letter-spacing: -.02em; }
    .page-head p { color: #6b7280; font-size: .92rem; }

    /* ===== Share buttons ===== */
    .share { display: flex; gap: 10px; flex-wrap: wrap; }
    .share a { display: inline-flex; align-items: center; gap: 8px; padding: 9px 14px;
               border-radius: 8px; color: #fff; text-decoration: none; font-size: .85rem; font-weight: 600; }
    .share .x  { background: #111; }
    .share .fb { background: #1877f2; }
    .share .li { background: #0a66c2; }
    .share .wa { background: #25d366; }

    /* ===== Follow ===== */
    .follow-row { display: inline-flex; align-items: center; gap: 12px; }
    .follow { display: inline-flex; align-items: center; gap: 7px; background: #38bdf8; color: #fff;
              border: none; border-radius: 999px; padding: 9px 18px; font-weight: 700; cursor: pointer; font-size: .88rem; }
    .follow.following { background: #fff; color: #111; border: 1px solid #cbd5e1; }
    .count { font-size: .85rem; color: #6b7280; }
    .count b { color: #111; }

    /* ===== Comments ===== */
    .comments { display: grid; gap: 14px; }
    .comment { display: flex; gap: 12px; }
    .avatar { width: 38px; height: 38px; border-radius: 50%; flex: none;
              background: linear-gradient(135deg,#38bdf8,#9b7bff); }
    .comment .nm { font-weight: 700; color: #111; font-size: .85rem; }
    .comment .meta { color: #9aa3b2; font-size: .72rem; }
    .comment p { color: #374151; font-size: .88rem; margin: 3px 0; }
    .comment .act { display: flex; gap: 14px; font-size: .75rem; color: #6b7280; }
    .comment .act button { background: none; border: none; color: #6b7280; cursor: pointer; font-size: .75rem; }

    /* ===== Settings ===== */
    .setting { display: flex; align-items: center; justify-content: space-between;
               padding: 12px 0; border-bottom: 1px solid #eef1f5; }
    .setting:last-child { border-bottom: none; }
    .setting .lbl { font-size: .9rem; color: #111; font-weight: 600; }
    .setting .sub { font-size: .78rem; color: #6b7280; }
    .switch { width: 44px; height: 26px; border-radius: 999px; background: #cbd5e1; border: none;
              position: relative; flex: none; cursor: pointer; transition: background .2s; }
    .switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
                     border-radius: 50%; background: #fff; transition: left .2s; }
    .switch.on { background: #22c55e; }
    .switch.on::after { left: 21px; }

    /* ===== Activity feed ===== */
    .feed { list-style: none; display: grid; gap: 0; }
    .feed li { display: flex; gap: 12px; padding: 12px 0; position: relative; }
    .feed li::before { content: ""; position: absolute; left: 18px; top: 40px; bottom: -12px;
                       width: 2px; background: #e5e7eb; }
    .feed li:last-child::before { display: none; }
    .feed .icon { width: 38px; height: 38px; border-radius: 50%; background: #e0f2fe; color: #0369a1;
                  display: grid; place-items: center; flex: none; font-size: 1rem; }
    .feed .tx { font-size: .88rem; color: #374151; }
    .feed .tx b { color: #111; }
    .feed time { font-size: .72rem; color: #9aa3b2; }

    /* ===== Forum ===== */
    .forum { list-style: none; }
    .thread { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid #f1f5f9; }
    .thread:last-child { border-bottom: none; }
    .thread .title { font-weight: 600; color: #111; font-size: .9rem; }
    .thread .by { font-size: .75rem; color: #9aa3b2; }
    .replies { margin-left: auto; font-weight: 700; color: #0369a1; font-size: .85rem; }

    /* ===== Chat ===== */
    .chat { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; max-width: 360px; }
    .chat .head { background: #0b1a33; color: #fff; padding: 10px 14px; font-weight: 700; font-size: .88rem; }
    .messages { padding: 14px; display: grid; gap: 8px; min-height: 90px; }
    .bubble { max-width: 75%; padding: 8px 12px; border-radius: 14px; font-size: .85rem; }
    .bubble.them { background: #eef1f5; color: #111; border-bottom-left-radius: 4px; }
    .bubble.me { background: #38bdf8; color: #fff; margin-left: auto; border-bottom-right-radius: 4px; }
    .chat .input { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid #eef1f5; }
    .chat .input input { flex: 1; border: 1px solid #cbd5e1; border-radius: 999px; padding: 8px 12px; font-size: .85rem; }
    .chat .input button { background: #38bdf8; color: #fff; border: none; border-radius: 999px; padding: 0 16px; cursor: pointer; font-weight: 600; }
  </style>
</head>
<body>

  <div class="wrap">
    <div class="page-head">
      <h1>DevTown Community</h1>
      <p>Share, follow, chat, and join the conversation.</p>
    </div>

    <section class="card">
      <h2>Spread the word</h2>
      <div class="share">
        <a class="x"  href="https://twitter.com/intent/tweet?url=PAGE_URL">Share</a>
        <a class="fb" href="https://www.facebook.com/sharer/sharer.php?u=PAGE_URL">Share</a>
        <a class="li" href="https://www.linkedin.com/sharing/share-offsite/?url=PAGE_URL">Share</a>
        <a class="wa" href="https://wa.me/?text=PAGE_URL">Share</a>
      </div>
      <div class="follow-row" style="margin-top:16px;">
        <button class="follow" aria-pressed="false">+ Follow</button>
        <span class="count"><b id="followers">2,481</b> followers</span>
      </div>
    </section>

    <section class="card">
      <h2>Comments</h2>
      <div class="comments">
        <article class="comment">
          <span class="avatar"></span>
          <div>
            <span class="nm">Sam R.</span> <span class="meta">· 2h ago</span>
            <p>This community finally made flexbox click for me. Thank you!</p>
            <div class="act"><button>♥ 12</button><button>Reply</button></div>
          </div>
        </article>
        <article class="comment">
          <span class="avatar"></span>
          <div>
            <span class="nm">Dana K.</span> <span class="meta">· 1h ago</span>
            <p>Agreed — the live demos make all the difference.</p>
            <div class="act"><button>♥ 4</button><button>Reply</button></div>
          </div>
        </article>
      </div>
    </section>

    <section class="card">
      <h2>Settings</h2>
      <div class="setting">
        <div>
          <p class="lbl">Email notifications</p>
          <p class="sub">Get updates about your account</p>
        </div>
        <button class="switch on" role="switch" aria-checked="true"></button>
      </div>
      <div class="setting">
        <div>
          <p class="lbl">Public profile</p>
          <p class="sub">Anyone can view your profile</p>
        </div>
        <button class="switch" role="switch" aria-checked="false"></button>
      </div>
    </section>

    <section class="card">
      <h2>Recent activity</h2>
      <ul class="feed">
        <li><span class="icon">♥</span>
            <div><p class="tx"><b>Sam</b> liked your post</p><time>2h ago</time></div></li>
        <li><span class="icon">+</span>
            <div><p class="tx"><b>Dana</b> started following you</p><time>5h ago</time></div></li>
        <li><span class="icon">💬</span>
            <div><p class="tx"><b>Lee</b> commented on your photo</p><time>1d ago</time></div></li>
      </ul>
    </section>

    <section class="card">
      <h2>Discussion threads</h2>
      <ul class="forum">
        <li class="thread">
          <div><p class="title">How do I center a div in 2026?</p><p class="by">by Ada · 3d ago</p></div>
          <span class="replies">24 replies</span>
        </li>
        <li class="thread">
          <div><p class="title">Best free fonts for body text?</p><p class="by">by Lee · 1d ago</p></div>
          <span class="replies">9 replies</span>
        </li>
        <li class="thread">
          <div><p class="title">Show your portfolio — feedback welcome</p><p class="by">by Mia · 6h ago</p></div>
          <span class="replies">41 replies</span>
        </li>
      </ul>
    </section>

    <section class="card">
      <h2>Live chat</h2>
      <div class="chat">
        <div class="head">Support</div>
        <div class="messages" id="messages">
          <p class="bubble them">Hi! How can we help?</p>
          <p class="bubble me">My order hasn't shipped yet.</p>
          <p class="bubble them">Let me check that for you…</p>
        </div>
        <form class="input" id="chatForm">
          <input id="chatInput" placeholder="Type a message…" aria-label="Message">
          <button type="submit">Send</button>
        </form>
      </div>
    </section>
  </div>

  <script>
    // Follow toggle with live count
    var followBtn = document.querySelector('.follow');
    var followers = document.getElementById('followers');
    var base = 2481, following = false;
    followBtn.addEventListener('click', function () {
      following = !following;
      followBtn.classList.toggle('following', following);
      followBtn.setAttribute('aria-pressed', following);
      followBtn.textContent = following ? '✓ Following' : '+ Follow';
      followers.textContent = (base + (following ? 1 : 0)).toLocaleString();
    });

    // Settings switches
    document.querySelectorAll('.switch').forEach(function (sw) {
      sw.addEventListener('click', function () {
        var on = sw.classList.toggle('on');
        sw.setAttribute('aria-checked', on);
      });
    });

    // Chat: send a message
    var form = document.getElementById('chatForm');
    var input = document.getElementById('chatInput');
    var messages = document.getElementById('messages');
    form.addEventListener('submit', function (e) {
      e.preventDefault();
      var text = input.value.trim();
      if (!text) return;
      var bubble = document.createElement('p');
      bubble.className = 'bubble me';
      bubble.textContent = text;
      messages.appendChild(bubble);
      input.value = '';
      messages.scrollTop = messages.scrollHeight;
      setTimeout(function () {
        var reply = document.createElement('p');
        reply.className = 'bubble them';
        reply.textContent = 'Thanks! A team member will reply shortly.';
        messages.appendChild(reply);
        messages.scrollTop = messages.scrollHeight;
      }, 700);
    });
  </script>
</body>
</html>
That's a real, standalone page. The live preview above is rendered from the exact code in the box — so what you copy is precisely what you see.
09 · Related

Keep building

See Testimonials, Profile Picture, and Icons — or browse the full Component Library.