primitives
Split Like Button
A split-panel like button with two visually distinct zones: a filled primary-colored left section containing a heart icon and label, and a lighter badge-style right section showing the like count. The two halves are bridged by a rotated-square diamond connector cut from the background token, creating a smooth visual seam. Hover and press states darken the left panel via color-mix in oklch. An aria-pressed attribute tracks liked state for accessibility, and a composed aria-label announces both the action and the count to screen readers. Pure CSS transitions — no JS animation hooks, no client boundary needed.
Ported from vinodjangid07 (MIT)
Install
1. Register the namespace (once per project):
json
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
bash
npx shadcn add @bottega/split-like-buttonUsage
Usagetsx
<SplitLikeButton />Props
Standard props — see source.
Source
split-like-button.tsxtsx
import * as React from "react";
import styles from "./split-like-button.module.css";
export type SplitLikeButtonProps = {
/** Count to display in the badge section. */
count?: number | string;
/** Label for the like action (default "Like"). */
label?: string;
/** Whether the button is in a liked/active state. */
liked?: boolean;
/** Called when the button is clicked. */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
/** Whether the button is disabled. */
disabled?: boolean;
/** Button type (default "button"). */
type?: "button" | "submit" | "reset";
/** Additional class names merged onto the root element. */
className?: string;
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type" | "onClick">;
export const SplitLikeButton = React.forwardRef<
HTMLButtonElement,
SplitLikeButtonProps
>(function SplitLikeButton(
{
count = "2,050",
label = "Like",
liked = false,
onClick,
disabled = false,
type = "button",
className = "",
...props
},
ref,
) {
const rootClass = [
styles.btn,
liked ? styles.liked : "",
className,
]
.filter(Boolean)
.join(" ");
return (
<button
ref={ref}
type={type}
disabled={disabled}
aria-pressed={liked}
aria-label={`${label}${typeof count !== "undefined" ? `, ${count} likes` : ""}`}
onClick={onClick}
className={rootClass}
{...props}
>
<span className={styles.leftContainer} aria-hidden="true">
{/* Heart icon */}
<svg
fill="currentColor"
viewBox="0 0 512 512"
height="1em"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
>
<path d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z" />
</svg>
<span className={styles.likeLabel}>{label}</span>
</span>
<span className={styles.likeCount} aria-hidden="true">
{count}
</span>
</button>
);
});
Dependencies
- @bottega/tokens