<div id="root"></div>
body {
  display: grid;
  place-content: center;
  height: 100vh;
}

#root {
  > * + * {
    margin-top: 0.5rem;
  }
}
View Compiled
import React, {
  useState,
  ReactNode,
  useCallback,
  KeyboardEvent,
  MouseEvent,
  HTMLAttributes,
  HTMLButtonElement
} from "https://esm.sh/react@18";
import { createRoot } from "https://esm.sh/react-dom@18";

type ButtonProps = {
  onClick?: (e: MouseEvent) => void;
  children?: ReactNode;
  className?: string;
  disabled?: boolean;
} & HTMLAttributes<HTMLButtonElement>;

const Button = ({
  onClick,
  children,
  className = "",
  disabled = false,
  ...props
}: ButtonProps) => {
  const handleKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (disabled) return;

      if (e.key === " " || e.key === "Enter") {
        e.preventDefault();
        onClick?.((e as unknown) as MouseEvent);
      }
    },
    [onClick, disabled]
  );

  const handleClick = (e: MouseEvent) => {
    if (disabled) return;
    onClick?.(e);
  };

  return (
    <div
      {...props}
      tabIndex={disabled ? -1 : 0}
      onClick={handleClick}
      onKeyDown={handleKeyDown}
      aria-disabled={disabled}
      className={`
        inline-block px-4 py-2
        text-sm
        bg-blue-500 text-white
        rounded cursor-pointer select-none
        transition-all duration-200

        /* ホバー時のスタイル */
        hover:bg-blue-600

        /* フォーカス時のスタイル */
        focus:outline-none
        focus:ring-2
        focus:ring-blue-400
        focus:ring-offset-2

        /* アクティブ時のスタイル */
        active:bg-blue-700

        /* 非活性時のスタイル */
        ${
          disabled &&
          `
          bg-gray-200
          text-gray-400
          cursor-not-allowed
          hover:bg-gray-200
          hover:shadow-none
          focus:ring-0
          focus:ring-offset-0
          pointer-events-none
          opacity-80
        `
        }
      `}
    >
      {children}
    </div>
  );
};

const App = () => {
  return (
    <>
      <div>
        <Button onClick={() => alert("Clicked!")}>Pure Div Button</Button>
      </div>
      <div>
        <Button disabled onClick={() => alert("Clicked!")}>
          Pure Div Button (disabled)
        </Button>
      </div>
    </>
  );
};

const rootElement = document.getElementById("root");
if (rootElement) {
  const root = createRoot(rootElement);
  root.render(<App />);
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.tailwindcss.com