mark-favourite
v1.0.7
Published
A beautifully animated, gesture-driven favourite/like/bookmark toggle component for React & Next.js
Maintainers
Readme
⭐ mark-favourite
A beautifully animated, gesture-driven favourite / like / bookmark toggle component for React & Next.js built with TypeScript and optimized for performance.
✅ CSS-based animations (no dependencies)
✅ Tap, hover & long-press gestures
✅ Controlled & uncontrolled mode
✅ SSR-safe for Next.js
✅ Zero CSS setup required
✅ Haptic feedback on mobile
✅ Performance optimized for low-end devices
✅ Accessible (respects prefers-reduced-motion)
✅ TypeScript support with full type definitions
✅ Lightweight & production-ready
✨ Demo
Features:
- 🎨 3 Built-in Icons - Heart, Star, and Bookmark
- ✨ Stunning Animations - Particle burst and confetti effects
- 📳 Haptic Feedback - Vibration on mobile devices
- ⚡ Auto-adapts to device capabilities
- 🎨 Fully Customizable - Size, color, and behavior
Try it live: Open
test.htmlin your browser after installation
📦 Installation
npm install mark-favouriteyarn add mark-favouritepnpm add mark-favouriteNote: No additional dependencies required! All animations are CSS-based.
🚀 Quick Start
import { MarkFavourite } from "mark-favourite";
function App() {
return <MarkFavourite icon="heart" color="#ff2d55" size={32} />;
}That's it! The component works out of the box with beautiful animations, haptic feedback, and performance optimizations.
📖 Usage Examples
Gestures Supported
- Click/Tap - Toggle favorite state
- Hover - Scale up animation (desktop)
- Long Press - Toggle after 420ms hold (mobile)
- Active Press - Scale down feedback
Basic Usage
import { MarkFavourite } from 'mark-favourite';
// Heart icon
<MarkFavourite icon="heart" color="#ff2d55" />
// Star icon
<MarkFavourite icon="star" color="#ffc700" />
// Bookmark icon
<MarkFavourite icon="bookmark" color="#5856d6" />Controlled Component
import { useState } from "react";
import { MarkFavourite } from "mark-favourite";
function FavoriteButton() {
const [isFavorite, setIsFavorite] = useState(false);
return (
<MarkFavourite
icon="heart"
color="#ff2d55"
active={isFavorite}
onToggle={(value) => {
setIsFavorite(value);
console.log("Favorite:", value);
}}
/>
);
}Uncontrolled Component
<MarkFavourite
icon="star"
color="#ffc700"
defaultActive={true}
onToggle={(value) => console.log("Toggled:", value)}
/>Custom Size and Color
<MarkFavourite icon="heart" size={48} color="#e91e63" />With Haptic Feedback
// Haptics enabled by default on mobile
<MarkFavourite icon="heart" color="#ff2d55" />
// Explicitly disable haptics
<MarkFavourite icon="heart" color="#ff2d55" enableHaptics={false} />Reduced Motion Mode
// Auto-detects user preference
<MarkFavourite icon="star" color="#ffc700" />
// Force reduced motion
<MarkFavourite icon="star" color="#ffc700" reduceMotion={true} />Disable Long Press
<MarkFavourite icon="bookmark" color="#5856d6" enableLongPress={false} />🎛️ API Reference
Props
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------- | ----------- | ------------------------------------------------------- |
| icon | "heart" | "star" | "bookmark" | "heart" | Icon type to display |
| size | number | 32 | Size of the button in pixels |
| color | string | "#FFC107" | Color of the icon and effects |
| active | boolean | undefined | Controlled active state |
| defaultActive | boolean | false | Default active state (uncontrolled) |
| enableLongPress | boolean | true | Enable long press gesture on mobile |
| enableHaptics | boolean | true | Enable haptic feedback on mobile |
| reduceMotion | boolean | undefined | Override motion preferences (auto-detects if undefined) |
| onToggle | (value: boolean) => void | undefined | Callback when toggled |
TypeScript
import { MarkFavourite } from "mark-favourite";
type IconType = "heart" | "star" | "bookmark";
interface MarkFavouriteProps {
active?: boolean;
defaultActive?: boolean;
size?: number;
color?: string;
icon?: IconType;
enableLongPress?: boolean;
enableHaptics?: boolean;
reduceMotion?: boolean;
onToggle?: (value: boolean) => void;
}⚡ Performance
The component automatically optimizes for device performance:
Adaptive Rendering
| Device Type | Particles | Confetti | Performance Impact | | ------------------ | --------- | -------- | ------------------ | | Desktop (8+ cores) | 12 | 8 | Full effects | | Desktop (≤4 cores) | 6 | 4 | 50% reduction | | Mobile | 6 | 4 | 50% reduction | | Reduced Motion | 4 | 0 | 80% reduction |
Optimizations
- ✅ React.memo - Prevents unnecessary re-renders
- ✅ useMemo/useCallback - Memoized computations
- ✅ CSS Containment - Isolated rendering
- ✅ GPU Acceleration - Hardware-accelerated animations
- ✅ Passive Event Listeners - Better scroll performance
- ✅ Conditional Rendering - Only renders needed elements
♿ Accessibility
- ✅ Respects
prefers-reduced-motionmedia query - ✅ Proper ARIA labels
- ✅ Full keyboard navigation support
- ✅ Touch-optimized for mobile devices
- ✅ Screen reader friendly
🎨 Customization
Custom Colors
// Gradient-like effect with bright colors
<MarkFavourite icon="heart" color="#ff006e" />
<MarkFavourite icon="star" color="#ffbe0b" />
<MarkFavourite icon="bookmark" color="#8338ec" />Different Sizes
// Small
<MarkFavourite icon="heart" size={24} color="#ff2d55" />
// Medium (default)
<MarkFavourite icon="heart" size={32} color="#ff2d55" />
// Large
<MarkFavourite icon="heart" size={48} color="#ff2d55" />
// Extra Large
<MarkFavourite icon="heart" size={64} color="#ff2d55" />🌐 Framework Support
Next.js
"use client"; // Required for Next.js 13+ App Router
import { MarkFavourite } from "mark-favourite";
export default function Page() {
return <MarkFavourite icon="heart" color="#ff2d55" />;
}Remix
import { MarkFavourite } from "mark-favourite";
export default function Route() {
return <MarkFavourite icon="star" color="#ffc700" />;
}Gatsby
import { MarkFavourite } from "mark-favourite";
const IndexPage = () => {
return <MarkFavourite icon="bookmark" color="#5856d6" />;
};
export default IndexPage;📱 Mobile Features
Haptic Feedback
The component provides subtle haptic feedback on mobile devices when activated:
// Vibration pattern: [10ms, 5ms, 10ms]
<MarkFavourite icon="heart" color="#ff2d55" enableHaptics={true} />Long Press Gesture
Long press (420ms) triggers the toggle on touch devices:
// Enabled by default
<MarkFavourite icon="heart" color="#ff2d55" />
// Disable if needed
<MarkFavourite icon="heart" color="#ff2d55" enableLongPress={false} />🎯 Real-World Examples
Like Button
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(0);
const handleToggle = async (value) => {
setLiked(value);
setLikeCount((prev) => (value ? prev + 1 : prev - 1));
// API call
await fetch(`/api/posts/${postId}/like`, {
method: value ? "POST" : "DELETE",
});
};
return (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<MarkFavourite
icon="heart"
color="#ff2d55"
active={liked}
onToggle={handleToggle}
/>
<span>{likeCount} likes</span>
</div>
);
}Favorite Product
function ProductCard({ product }) {
const [isFavorite, setIsFavorite] = useState(product.isFavorite);
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<div className="favorite-button">
<MarkFavourite
icon="star"
color="#ffc700"
size={28}
active={isFavorite}
onToggle={setIsFavorite}
/>
</div>
</div>
);
}Bookmark Article
function ArticleHeader({ article }) {
const [bookmarked, setBookmarked] = useState(false);
return (
<header>
<h1>{article.title}</h1>
<MarkFavourite
icon="bookmark"
color="#5856d6"
active={bookmarked}
onToggle={(value) => {
setBookmarked(value);
localStorage.setItem(`bookmark-${article.id}`, value);
}}
/>
</header>
);
}🧪 Testing
The component is fully testable with React Testing Library:
import { render, screen, fireEvent } from "@testing-library/react";
import { MarkFavourite } from "mark-favourite";
test("toggles on click", () => {
const handleToggle = jest.fn();
render(<MarkFavourite icon="heart" onToggle={handleToggle} />);
const button = screen.getByRole("button");
fireEvent.click(button);
expect(handleToggle).toHaveBeenCalledWith(true);
});🎨 Animation Details
Particle Burst
- 12 particles (or fewer on low-end devices)
- Radial burst pattern
- 0.6s duration
- Cubic bezier easing
Confetti Effect
- 8 confetti pieces (or fewer on low-end devices)
- Random rotation and offset
- 1.2s duration with staggered delays
- Colorful variety (gold, red, teal, orange)
Scale Animation
- Button scales to 1.25x on activation
- 0.5s duration with bounce easing
- Smooth return to normal size
🔧 Browser Support
- ✅ Chrome/Edge (latest)
- ✅ Firefox (latest)
- ✅ Safari (latest)
- ✅ Safari iOS (latest)
- ✅ Chrome Android (latest)
📄 License
MIT © Ashish Gupta
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
🐛 Issues
Found a bug? Please open an issue.
📚 Related
Made with ❤️ by Sonic-sabers
