Back
Learn how to build beautiful, animated toggle buttons with icons for liking, bookmarking, and more.
<div class="toggle-buttons-container">
<!-- Like Toggle Button -->
<div class="toggle-btn-wrapper">
<button class="icon-toggle-btn" id="like-button" aria-label="Like" aria-pressed="false">
<svg class="icon icon-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<svg class="icon icon-active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<span class="toggle-label">Like</span>
</button>
</div>
<!-- Additional toggle buttons follow the same pattern -->
</div>
Our HTML structure includes these key features:
- Each toggle button is wrapped in a container for proper spacing
- We use two SVG icons for each button: an outline version for the default state and a filled version for the active state
- We include proper accessibility attributes (`aria-label` and `aria-pressed`)
- Each button has a descriptive text label under the icon
- We use unique IDs for each button to allow specific styling and behavior
document.addEventListener('DOMContentLoaded', () => {
// Select all toggle buttons
const toggleButtons = document.querySelectorAll('.icon-toggle-btn');
// Function to toggle the state of a button
function toggleButtonState(button) {
const isPressed = button.getAttribute('aria-pressed') === 'true';
const newState = !isPressed;
// Update aria-pressed state
button.setAttribute('aria-pressed', newState);
// Add or remove active class for styling
if (newState) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
// Get button ID to determine which icon is being toggled
const buttonId = button.id;
// You could trigger different actions based on the button id
switch (buttonId) {
case 'like-button':
console.log(`User ${newState ? 'liked' : 'unliked'} the item`);
break;
case 'bookmark-button':
console.log(`User ${newState ? 'saved' : 'removed'} the bookmark`);
break;
// Additional cases for other buttons
}
// Create and play animation
if (newState) {
button.classList.add('animate');
// Remove animation class after it completes
setTimeout(() => {
button.classList.remove('animate');
}, 700); // Match this to your CSS animation duration
}
}
// Add click event listeners to all toggle buttons
toggleButtons.forEach(button => {
button.addEventListener('click', () => {
toggleButtonState(button);
});
// For accessibility: support keyboard activation
button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleButtonState(button);
}
});
});
});
Our JavaScript implementation provides:
- A centralized function to handle the state toggle for all buttons
- Proper updating of accessibility attributes when the state changes
- Custom actions based on which button was toggled
- Animation triggering when toggling to the active state
- Support for keyboard navigation and activation
- Clean handling of multiple toggle buttons on the same page
/* Icon toggle button base styles */
.icon-toggle-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 8px;
cursor: pointer;
padding: 12px;
transition: all 0.2s ease;
position: relative;
color: #64748b; /* Default icon color */
font-family: inherit;
}
/* Hide icons that shouldn't be visible */
.icon-toggle-btn .icon {
opacity: 1;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.icon-toggle-btn .icon-active {
position: absolute;
top: 12px;
opacity: 0;
color: transparent;
}
/* Button hover and focus states */
.icon-toggle-btn:hover {
background-color: #f1f5f9;
color: #334155;
}
.icon-toggle-btn:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
Now let's style the active states of our buttons with different colors:
/* Active state */
.icon-toggle-btn.active {
color: #3b82f6; /* Active color */
}
.icon-toggle-btn.active .icon-default {
opacity: 0;
}
.icon-toggle-btn.active .icon-active {
opacity: 1;
color: #3b82f6;
}
/* Button specific colors */
#like-button.active,
#like-button.active .icon-active {
color: #ef4444; /* Red for like */
}
#bookmark-button.active,
#bookmark-button.active .icon-active {
color: #3b82f6; /* Blue for bookmark */
}
#star-button.active,
#star-button.active .icon-active {
color: #eab308; /* Yellow for star */
}
#notify-button.active,
#notify-button.active .icon-active {
color: #8b5cf6; /* Purple for notifications */
}
Finally, let's add the animation for toggling and responsive styles:
/* Animation for toggling on */
@keyframes pulse {
0% { transform: scale(1); }
30% { transform: scale(0.8); }
60% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.icon-toggle-btn.animate .icon-active {
animation: pulse 0.5s ease-in-out;
}
/* Responsive styles */
@media (max-width: 480px) {
.toggle-buttons-container {
gap: 8px;
}
.toggle-btn-wrapper {
min-width: 70px;
}
.icon-toggle-btn {
padding: 8px;
}
.toggle-label {
font-size: 12px;
}
}
These icon toggle buttons provide several advantages:
- Visual clarity: The combination of icon changes and color shifts clearly indicates state
- Intuitive feedback: The animation provides immediate confirmation of the user's action
- Compact design: The vertical layout with icons and labels uses space efficiently
- Consistent system: The uniform design can be extended to any binary action
- Accessibility: Proper ARIA attributes and keyboard support ensure all users can interact
- Visual differentiation: Different colors for different actions help users understand the meaning
These toggle buttons are perfect for social interactions (like/favorite), content management (save/bookmark), preference settings, or any feature that requires a binary state toggle with clear visual feedback.<div class="toggle-buttons-container">
<!-- Like Toggle Button -->
<div class="toggle-btn-wrapper">
<button class="icon-toggle-btn" id="like-button" aria-label="Like" aria-pressed="false">
<svg class="icon icon-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<svg class="icon icon-active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
<span class="toggle-label">Like</span>
</button>
</div>
<!-- Bookmark Toggle Button -->
<div class="toggle-btn-wrapper">
<button class="icon-toggle-btn" id="bookmark-button" aria-label="Bookmark" aria-pressed="false">
<svg class="icon icon-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
<svg class="icon icon-active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
<span class="toggle-label">Save</span>
</button>
</div>
<!-- Star Toggle Button -->
<div class="toggle-btn-wrapper">
<button class="icon-toggle-btn" id="star-button" aria-label="Star" aria-pressed="false">
<svg class="icon icon-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
<svg class="icon icon-active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
<span class="toggle-label">Star</span>
</button>
</div>
<!-- Bell Toggle Button -->
<div class="toggle-btn-wrapper">
<button class="icon-toggle-btn" id="notify-button" aria-label="Enable notifications" aria-pressed="false">
<svg class="icon icon-default" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
<svg class="icon icon-active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
<span class="toggle-label">Notify</span>
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Select all toggle buttons
const toggleButtons = document.querySelectorAll('.icon-toggle-btn');
// Function to toggle the state of a button
function toggleButtonState(button) {
const isPressed = button.getAttribute('aria-pressed') === 'true';
const newState = !isPressed;
// Update aria-pressed state
button.setAttribute('aria-pressed', newState);
// Add or remove active class for styling
if (newState) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
// Get button ID to determine which icon is being toggled
const buttonId = button.id;
// You could trigger different actions based on the button id
switch (buttonId) {
case 'like-button':
console.log(`User ${newState ? 'liked' : 'unliked'} the item`);
break;
case 'bookmark-button':
console.log(`User ${newState ? 'saved' : 'removed'} the bookmark`);
break;
case 'star-button':
console.log(`User ${newState ? 'starred' : 'unstarred'} the item`);
break;
case 'notify-button':
console.log(`User ${newState ? 'enabled' : 'disabled'} notifications`);
break;
}
// Create and play animation
if (newState) {
button.classList.add('animate');
// Remove animation class after it completes
setTimeout(() => {
button.classList.remove('animate');
}, 700); // Match this to your CSS animation duration
}
}
// Add click event listeners to all toggle buttons
toggleButtons.forEach(button => {
button.addEventListener('click', () => {
toggleButtonState(button);
});
// For accessibility: support keyboard activation
button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleButtonState(button);
}
});
});
});
</script>
<style>
/* Container for toggle buttons */
.toggle-buttons-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
max-width: 800px;
margin: 0 auto;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Wrapper for each toggle button */
.toggle-btn-wrapper {
display: flex;
flex-direction: column;
align-items: center;
min-width: 90px;
}
/* Icon toggle button base styles */
.icon-toggle-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 8px;
cursor: pointer;
padding: 12px;
transition: all 0.2s ease;
position: relative;
color: #64748b; /* Default icon color */
font-family: inherit;
}
/* Hide icons that shouldn't be visible */
.icon-toggle-btn .icon {
opacity: 1;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.icon-toggle-btn .icon-active {
position: absolute;
top: 12px;
opacity: 0;
color: transparent;
}
/* Button hover and focus states */
.icon-toggle-btn:hover {
background-color: #f1f5f9;
color: #334155;
}
.icon-toggle-btn:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Toggle label */
.toggle-label {
margin-top: 4px;
font-size: 14px;
font-weight: 500;
transition: color 0.2s ease;
}
/* Active state */
.icon-toggle-btn.active {
color: #3b82f6; /* Active color */
}
.icon-toggle-btn.active .icon-default {
opacity: 0;
}
.icon-toggle-btn.active .icon-active {
opacity: 1;
color: #3b82f6;
}
/* Button specific colors */
#like-button.active,
#like-button.active .icon-active {
color: #ef4444; /* Red for like */
}
#bookmark-button.active,
#bookmark-button.active .icon-active {
color: #3b82f6; /* Blue for bookmark */
}
#star-button.active,
#star-button.active .icon-active {
color: #eab308; /* Yellow for star */
}
#notify-button.active,
#notify-button.active .icon-active {
color: #8b5cf6; /* Purple for notifications */
}
/* Animation for toggling on */
@keyframes pulse {
0% { transform: scale(1); }
30% { transform: scale(0.8); }
60% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.icon-toggle-btn.animate .icon-active {
animation: pulse 0.5s ease-in-out;
}
/* Responsive styles */
@media (max-width: 480px) {
.toggle-buttons-container {
gap: 8px;
}
.toggle-btn-wrapper {
min-width: 70px;
}
.icon-toggle-btn {
padding: 8px;
}
.toggle-label {
font-size: 12px;
}
}
</style>
Thank you for reading this article.
Comments