diff --git a/index.html b/index.html
index a5734ba4..9e1fc93b 100644
--- a/index.html
+++ b/index.html
@@ -40,6 +40,30 @@
/>
+
+
+
diff --git a/src/components/common/AddToCompareButton.tsx b/src/components/common/AddToCompareButton.tsx
new file mode 100644
index 00000000..d0ab6f43
--- /dev/null
+++ b/src/components/common/AddToCompareButton.tsx
@@ -0,0 +1,91 @@
+import { Check, GitCompareArrows } from 'lucide-react';
+import { Tooltip } from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
+import {
+ COMPARE_LIMIT_REASON,
+ MAX_COMPARE_KEYS,
+ useCompareStore,
+ type CompareKey,
+} from '@/hooks/useCompareStore';
+
+interface AddToCompareButtonProps {
+ compareKey: CompareKey;
+ className?: string;
+}
+
+const BASE_CLASS =
+ 'inline-flex items-center gap-1.5 rounded-sm border px-2.5 py-1.5 font-mono text-[9px] uppercase tracking-wider transition-colors outline-none focus-visible:ring-2 focus-visible:ring-gray-400 dark:focus-visible:ring-white/50';
+
+export default function AddToCompareButton({
+ compareKey,
+ className,
+}: AddToCompareButtonProps) {
+ const isSelected = useCompareStore(state =>
+ state.keys.some(key => key.id === compareKey.id)
+ );
+ const isFull = useCompareStore(
+ state => state.keys.length >= MAX_COMPARE_KEYS
+ );
+ const add = useCompareStore(state => state.add);
+ const remove = useCompareStore(state => state.remove);
+
+ if (isSelected) {
+ return (
+
+ );
+ }
+
+ if (isFull) {
+ // aria-disabled (not `disabled`) keeps the button focusable so
+ // keyboard users can still reach the tooltip explaining why.
+ return (
+