UI Components Easy
Follow/Subscribe Toggle
A sleek, state-aware button for follow or subscribe actions, featuring smooth label transitions and color shifting.
Open in Lab
MCP
css vanilla-js react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
body {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
:root {
--follow-default-bg: #f8fafc;
--follow-default-text: #0f172a;
--follow-following-bg: rgba(255, 255, 255, 0.06);
--follow-following-text: #f8fafc;
--follow-following-border: rgba(255, 255, 255, 0.2);
--follow-unfollow-bg: rgba(239, 68, 68, 0.12);
--follow-unfollow-text: #f87171;
--follow-unfollow-border: rgba(239, 68, 68, 0.3);
}
.follow-btn {
padding: 0.6rem 1.625rem;
border-radius: 9999px;
font-weight: 700;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.22s ease;
min-width: 110px;
font-family: "Inter", system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
letter-spacing: 0.01em;
}
.follow-btn {
background: var(--follow-default-bg);
color: var(--follow-default-text);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.follow-btn:hover:not(.active) {
opacity: 0.88;
transform: translateY(-1px);
}
.state-following,
.state-unfollow {
display: none;
}
.follow-btn.active {
background: var(--follow-following-bg);
color: var(--follow-following-text);
border-color: var(--follow-following-border);
box-shadow: none;
backdrop-filter: blur(8px);
}
.follow-btn.active .state-follow {
display: none;
}
.follow-btn.active .state-following {
display: inline;
}
.follow-btn.active:hover {
background: var(--follow-unfollow-bg);
color: var(--follow-unfollow-text);
border-color: var(--follow-unfollow-border);
}
.follow-btn.active:hover .state-following {
display: none;
}
.follow-btn.active:hover .state-unfollow {
display: inline;
}const followBtn = document.getElementById("follow-btn");
followBtn.addEventListener("click", () => {
followBtn.classList.toggle("active");
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Follow Button</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="follow-container">
<button id="follow-btn" class="follow-btn">
<span class="state-follow">Follow</span>
<span class="state-following">Following</span>
<span class="state-unfollow">Unfollow</span>
</button>
</div>
<script src="script.js"></script>
</body>
</html>import { useState } from "react";
const PROFILES = [
{ name: "Sarah Chen", handle: "@sarahchen", avatar: "SC", color: "#bc8cff", followers: "12.4K" },
{ name: "Alex Rivera", handle: "@arivera", avatar: "AR", color: "#58a6ff", followers: "8.1K" },
{ name: "Jordan Kim", handle: "@jordankim", avatar: "JK", color: "#7ee787", followers: "3.2K" },
];
function ProfileCard({ name, handle, avatar, color, followers }: (typeof PROFILES)[0]) {
const [following, setFollowing] = useState(false);
const [hover, setHover] = useState(false);
return (
<div className="bg-[#161b22] border border-[#30363d] rounded-xl p-4 flex items-center gap-4">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold text-[#0d1117] flex-shrink-0"
style={{ background: color }}
>
{avatar}
</div>
<div className="flex-1 min-w-0">
<p className="text-[#e6edf3] font-semibold text-sm truncate">{name}</p>
<p className="text-[#8b949e] text-xs">
{handle} · {followers} followers
</p>
</div>
<button
onClick={() => setFollowing((f) => !f)}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
className={`px-4 py-1.5 rounded-full text-xs font-semibold transition-all duration-200 whitespace-nowrap ${
following
? hover
? "bg-[#f85149]/10 border border-[#f85149]/40 text-[#f85149]"
: "bg-[#21262d] border border-[#30363d] text-[#e6edf3]"
: "bg-[#238636] border border-[#2ea043] text-white hover:bg-[#2ea043]"
}`}
>
{following ? (hover ? "Unfollow" : "Following") : "Follow"}
</button>
</div>
);
}
export default function FollowButtonRC() {
return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center p-6">
<div className="w-full max-w-sm space-y-3">
<h2 className="text-[#e6edf3] font-bold text-lg mb-4">Who to follow</h2>
{PROFILES.map((p) => (
<ProfileCard key={p.handle} {...p} />
))}
</div>
</div>
);
}<script setup>
import { ref, reactive } from "vue";
const PROFILES = [
{ name: "Sarah Chen", handle: "@sarahchen", avatar: "SC", color: "#bc8cff", followers: "12.4K" },
{ name: "Alex Rivera", handle: "@arivera", avatar: "AR", color: "#58a6ff", followers: "8.1K" },
{ name: "Jordan Kim", handle: "@jordankim", avatar: "JK", color: "#7ee787", followers: "3.2K" },
];
const followingState = reactive({});
const hoverState = reactive({});
function toggleFollow(handle) {
followingState[handle] = !followingState[handle];
}
function btnClass(handle) {
const following = followingState[handle];
const hover = hoverState[handle];
if (following) {
return hover ? "btn btn-unfollow" : "btn btn-following";
}
return "btn btn-follow";
}
function btnLabel(handle) {
const following = followingState[handle];
const hover = hoverState[handle];
if (following) return hover ? "Unfollow" : "Following";
return "Follow";
}
</script>
<template>
<div class="follow-wrapper">
<div class="follow-panel">
<h2 class="follow-title">Who to follow</h2>
<div v-for="p in PROFILES" :key="p.handle" class="profile-card">
<div class="avatar" :style="{ background: p.color }">{{ p.avatar }}</div>
<div class="info">
<p class="name">{{ p.name }}</p>
<p class="handle">{{ p.handle }} · {{ p.followers }} followers</p>
</div>
<button
:class="btnClass(p.handle)"
@click="toggleFollow(p.handle)"
@mouseenter="hoverState[p.handle] = true"
@mouseleave="hoverState[p.handle] = false"
>
{{ btnLabel(p.handle) }}
</button>
</div>
</div>
</div>
</template>
<style scoped>
.follow-wrapper {
min-height: 100vh;
background: #0d1117;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.follow-panel { width: 100%; max-width: 24rem; display: flex; flex-direction: column; gap: 0.75rem; }
.follow-title { color: #e6edf3; font-weight: 700; font-size: 1.125rem; margin-bottom: 1rem; }
.profile-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 0.75rem;
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
font-weight: 700;
color: #0d1117;
flex-shrink: 0;
}
.info { flex: 1; min-width: 0; }
.name { color: #e6edf3; font-weight: 600; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.handle { color: #8b949e; font-size: 0.75rem; }
.btn {
padding: 0.375rem 1rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
transition: all 0.2s;
cursor: pointer;
}
.btn-follow {
background: #238636;
border: 1px solid #2ea043;
color: white;
}
.btn-follow:hover { background: #2ea043; }
.btn-following {
background: #21262d;
border: 1px solid #30363d;
color: #e6edf3;
}
.btn-unfollow {
background: rgba(248, 81, 73, 0.1);
border: 1px solid rgba(248, 81, 73, 0.4);
color: #f85149;
}
</style><script>
const PROFILES = [
{ name: "Sarah Chen", handle: "@sarahchen", avatar: "SC", color: "#bc8cff", followers: "12.4K" },
{ name: "Alex Rivera", handle: "@arivera", avatar: "AR", color: "#58a6ff", followers: "8.1K" },
{ name: "Jordan Kim", handle: "@jordankim", avatar: "JK", color: "#7ee787", followers: "3.2K" },
];
let following = {};
let hovering = {};
function toggleFollow(handle) {
following = { ...following, [handle]: !following[handle] };
}
function setHover(handle, val) {
hovering = { ...hovering, [handle]: val };
}
</script>
<div class="follow-wrapper">
<div class="follow-panel">
<h2 class="follow-title">Who to follow</h2>
{#each PROFILES as p}
<div class="profile-card">
<div class="avatar" style="background: {p.color};">{p.avatar}</div>
<div class="info">
<p class="name">{p.name}</p>
<p class="handle">{p.handle} · {p.followers} followers</p>
</div>
<button
class="btn {following[p.handle] ? (hovering[p.handle] ? 'btn-unfollow' : 'btn-following') : 'btn-follow'}"
on:click={() => toggleFollow(p.handle)}
on:mouseenter={() => setHover(p.handle, true)}
on:mouseleave={() => setHover(p.handle, false)}
>
{following[p.handle] ? (hovering[p.handle] ? 'Unfollow' : 'Following') : 'Follow'}
</button>
</div>
{/each}
</div>
</div>
<style>
.follow-wrapper {
min-height: 100vh;
background: #0d1117;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.follow-panel { width: 100%; max-width: 24rem; display: flex; flex-direction: column; gap: 0.75rem; }
.follow-title { color: #e6edf3; font-weight: 700; font-size: 1.125rem; margin-bottom: 1rem; }
.profile-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 0.75rem;
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
font-weight: 700;
color: #0d1117;
flex-shrink: 0;
}
.info { flex: 1; min-width: 0; }
.name { color: #e6edf3; font-weight: 600; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.handle { color: #8b949e; font-size: 0.75rem; }
.btn {
padding: 0.375rem 1rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
transition: all 0.2s;
cursor: pointer;
}
.btn-follow {
background: #238636;
border: 1px solid #2ea043;
color: white;
}
.btn-follow:hover { background: #2ea043; }
.btn-following {
background: #21262d;
border: 1px solid #30363d;
color: #e6edf3;
}
.btn-unfollow {
background: rgba(248, 81, 73, 0.1);
border: 1px solid rgba(248, 81, 73, 0.4);
color: #f85149;
}
</style>Follow/Subscribe Toggle
A refined UI element for relationship management. This button handles the transition between ‘Follow’ and ‘Following’ states with minimal code and maximum aesthetic appeal.
Features
- State-driven styling (Follow vs Following)
- Hover effects for “Unfollow” action
- Simple, lightweight implementation
- Accessible button states
- Support for icons and count badges