bottega

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-button

Usage

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