diff --git a/src/components/controls/ColorKeyboard.tsx b/src/components/controls/ColorKeyboard.tsx
new file mode 100644
index 0000000..8e25918
--- /dev/null
+++ b/src/components/controls/ColorKeyboard.tsx
@@ -0,0 +1,101 @@
+import { Toggle } from "@/components/ui/toggle";
+import { colorScale } from "@/constants/colorScale";
+import { cn } from "@/lib/utils";
+
+const WHITE_KEYS = ["C", "D", "E", "F", "G", "A", "B"];
+const BLACK_KEYS = [
+ { note: "C#", position: 0 },
+ { note: "D#", position: 1 },
+ { note: "F#", position: 3 },
+ { note: "G#", position: 4 },
+ { note: "A#", position: 5 },
+];
+
+export default function ColorKeyboard({
+ value,
+ onChange,
+}: {
+ value: string;
+ onChange: (color: string) => void;
+}) {
+ const colorByNote = Object.fromEntries(colorScale.map((entry) => [entry.note, entry.color]));
+
+ return (
+
+ {WHITE_KEYS.map((note) => {
+ const color = colorByNote[note];
+ const isActive = color.toLowerCase() === value.toLowerCase();
+
+ return (
+ onChange(color)}
+ className={cn(
+ "relative flex flex-1 items-end justify-center rounded-none border border-foreground dark:border-background cursor-pointer",
+ "h-36 min-w-0 px-0",
+ note === "C" ? "rounded-l-md" : "",
+ note === "B" ? "rounded-r-md" : "",
+ isActive ? "z-10" : "",
+ )}
+ style={{ backgroundColor: color }}
+ aria-label={`Select ${note}`}
+ >
+
+ {note}
+
+
+ );
+ })}
+
+ {BLACK_KEYS.map((key) => {
+ const color = colorByNote[key.note];
+ const isActive = color.toLowerCase() === value.toLowerCase();
+
+ return (
+ onChange(color)}
+ className={cn(
+ "absolute top-0 z-20 h-24 rounded-none border border-black min-w-0 px-0 cursor-pointer",
+ isActive ? "" : "",
+ )}
+ style={{
+ width: "8%",
+ left: `${(key.position + 1) * (100 / WHITE_KEYS.length) - 4}%`,
+ backgroundColor: color,
+ }}
+ aria-label={`Select ${key.note}`}
+ >
+
+
+ {key.note}
+
+
+
+ );
+ })}
+
+ );
+}