カラーピッカー(Color Picker)— HEX/RGB変換・パレット選択

フォーム入力 初級

このコンポーネントについて

ユーザーが色を選択・入力できるカラーピッカーコンポーネントです。 ブラウザ標準の input[type="color"] をベースに、HEX/RGB変換やプリセットパレットなどの実用的なUIを追加しています。

テーマカラー設定・ラベルカラー選択・デザインツールの入力欄など、色を扱うあらゆるフォームに対応できます。 選んだ色はプレビューにリアルタイム反映されるため、実際の見え方を確認しながら選択できます。

  • HEX/RGB変換 — 選んだ色をHEXコードとRGB形式の両方でリアルタイム表示する
  • HEX直接入力 — テキストフィールドに6桁のHEXコードを直接打ち込んで色を指定できる
  • コピーボタン — HEX・RGBそれぞれをワンクリックでクリップボードにコピーできる
  • 背景色プレビュー(Pattern 1) — 選んだ色がプレビューカードの背景色にリアルタイム反映される
  • プリセットパレット(Pattern 2) — よく使われる18色をグリッドで表示し、クリックで即座に選択できる
  • ラベルプレビュー(Pattern 2) — 選んだ色がラベルの背景色に反映され、文字色はコントラストに応じて白/黒を自動切替する

実装のポイント・注意点

input[type="color"] はブラウザがネイティブのカラーピッカーUIを提供します。 ただし、デフォルトの見た目はブラウザごとに異なりデザインのカスタマイズが難しいため、このサンプルでは要素を opacity: 0; width: 0; height: 0; で非表示にし、 カスタムボタンのクリックイベントから colorInput.click() を呼び出してピッカーを起動する方式を採用しています。 なお display: none にするとブラウザによって .click() が無視されるため、必ず上記のスタイルで非表示にしてください。

Pattern 2 のラベルプレビューでは、選んだ色の明るさに応じて文字色(白/黒)を自動で切り替えています。 これはWCAGの相対輝度計算式を使って背景色の輝度を求め、0.179 を閾値に白・黒文字を選ぶ方式です。 閾値 0.179 は白文字(輝度1.0)との対比比率が最低限4.5:1を確保できる境界値です。

HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。

デモ

Pattern 1 — HEX/RGB変換ピッカー

HEX
RGB rgb(43, 127, 232)
#2B7FE8

プレビュー

選んだ色がこのカードの背景に反映されます。

Pattern 2 — プリセットパレット

プレビュー: ラベル
#2B7FE8

サンプルソース

3つのファイルを同じフォルダに保存し、index.html をブラウザで開くとすぐに動作確認できます。
ファイル名:index.html / style.css / script.js — 保存時の文字コードは UTF-8 を指定してください(Shift-JISだと日本語が文字化けします)。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>カラーピッカー サンプル</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>

<!-- Pattern 1: HEX/RGB変換ピッカー -->
<div class="cp-wrapper">
  <div class="cp-trigger">
    <div class="cp-swatch" id="colorSwatch" style="background: #2B7FE8;"></div>
    <button type="button" class="cp-open-btn" id="colorOpenBtn">色を選ぶ</button>
    <input type="color" class="cp-hidden-input" id="colorInput" value="#2b7fe8" aria-hidden="true" tabindex="-1">
  </div>
  <div class="cp-formats">
    <div class="cp-format-row">
      <span class="cp-format-label">HEX</span>
      <input type="text" class="cp-hex-input" id="hexInput" value="#2B7FE8" maxlength="7" aria-label="HEXコード">
      <button type="button" class="cp-copy-btn" id="hexCopyBtn">コピー</button>
    </div>
    <div class="cp-format-row">
      <span class="cp-format-label">RGB</span>
      <span class="cp-rgb-value" id="rgbValue">rgb(43, 127, 232)</span>
      <button type="button" class="cp-copy-btn" id="rgbCopyBtn">コピー</button>
    </div>
  </div>
  <div class="cp-preview-card">
    <div class="cp-preview-header" id="previewHeader" style="background: #2B7FE8;">
      <span class="cp-preview-hex" id="previewHex">#2B7FE8</span>
    </div>
    <div class="cp-preview-body">
      <p class="cp-preview-title">プレビュー</p>
      <p class="cp-preview-text">選んだ色がこのカードの背景に反映されます。</p>
    </div>
  </div>
</div>

<!-- Pattern 2: プリセットパレット + カスタム色 -->
<div class="cp-palette-wrapper">
  <div class="cp-palette-grid" id="paletteGrid" role="radiogroup" aria-label="カラーパレット"></div>
  <div class="cp-palette-custom">
    <button type="button" class="cp-custom-btn" id="customBtn">
      <span>+</span>カスタム色
    </button>
    <input type="color" class="cp-hidden-input" id="customColorInput" value="#2b7fe8" aria-hidden="true" tabindex="-1">
  </div>
  <div class="cp-palette-preview-row">
    <span class="cp-palette-preview-label">プレビュー:</span>
    <span class="cp-label-tag" id="labelTag" style="background: #2B7FE8; color: #fff;">ラベル</span>
  </div>
  <div class="cp-palette-hex-row">
    <span class="cp-palette-hex" id="paletteHex">#2B7FE8</span>
    <button type="button" class="cp-copy-btn" id="paletteCopyBtn">コピー</button>
  </div>
</div>

<script src="./script.js"></script>
</body>
</html>
/* === カラーピッカー スタイル
   色を変えたいときは :root の変数を書き換えるだけでOKです === */
:root {
  --color-primary: #2B7FE8;
  --color-text:    #1E2A3A;
  --color-muted:   #5A6A7A;
  --color-border:  #D0D7E0;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  font-family: sans-serif;
  padding: 24px;
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

/* === Pattern 1: HEX/RGB変換ピッカー === */

.cp-wrapper { width: 100%; }

.cp-trigger {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  position: relative;
}

.cp-swatch {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  border: 1.5px solid var(--color-border);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.15s;
}

.cp-swatch:hover { border-color: #9AA5B4; }

/* ネイティブ color input は非表示にしてJSで.click()する
   display:none だとブラウザによって.click()が無視されるためこの方法を使う */
.cp-hidden-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.cp-open-btn {
  padding: 8px 16px;
  font-size: 14px;
  font-family: sans-serif;
  color: #fff;
  background: var(--color-primary);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s;
}

.cp-open-btn:hover { background: #1a6fd4; }

.cp-formats { display: flex; flex-direction: column; gap: 10px; }

.cp-format-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.cp-format-label {
  width: 36px;
  font-size: 11px;
  font-weight: 700;
  color: var(--color-muted);
  flex-shrink: 0;
}

.cp-hex-input {
  flex: 1;
  padding: 7px 10px;
  border: 1.5px solid var(--color-border);
  border-radius: 6px;
  font-size: 14px;
  font-family: monospace;
  color: var(--color-text);
  outline: none;
  transition: border-color 0.15s;
}

.cp-hex-input:focus { border-color: var(--color-primary); }

.cp-rgb-value {
  flex: 1;
  font-size: 13px;
  font-family: monospace;
  color: var(--color-text);
  padding: 7px 0;
}

.cp-copy-btn {
  padding: 6px 12px;
  font-size: 12px;
  font-family: sans-serif;
  border: 1.5px solid var(--color-border);
  border-radius: 6px;
  background: #fff;
  color: var(--color-text);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.cp-copy-btn.copied {
  background: #e6f0fd;
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.cp-preview-card {
  margin-top: 20px;
  border-radius: 10px;
  overflow: hidden;
  border: 1.5px solid var(--color-border);
}

.cp-preview-header {
  padding: 20px 16px;
  transition: background 0.2s;
}

.cp-preview-hex {
  font-size: 13px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.9);
  font-family: monospace;
  letter-spacing: 0.04em;
}

.cp-preview-body {
  padding: 14px 16px;
  background: #fff;
}

.cp-preview-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--color-text);
  margin: 0 0 4px;
}

.cp-preview-text {
  font-size: 13px;
  color: var(--color-muted);
  margin: 0;
}

/* === Pattern 2: プリセットパレット === */

.cp-palette-wrapper { width: 100%; }

.cp-palette-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 32px);
  gap: 6px;
  margin-bottom: 12px;
}

.cp-swatch-btn {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  border: 2.5px solid transparent;
  cursor: pointer;
  position: relative;
  transition: transform 0.1s, border-color 0.1s;
  padding: 0;
}

.cp-swatch-btn:hover { transform: scale(1.1); }
.cp-swatch-btn.is-selected { border-color: var(--color-text); }

.cp-palette-custom {
  margin-bottom: 14px;
  position: relative;
}

.cp-custom-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1.5px dashed var(--color-border);
  border-radius: 6px;
  background: #fff;
  font-size: 13px;
  font-family: sans-serif;
  color: var(--color-muted);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}

.cp-custom-btn:hover {
  border-color: #9AA5B4;
  color: var(--color-text);
}

.cp-palette-preview-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.cp-palette-preview-label {
  font-size: 13px;
  color: var(--color-muted);
}

.cp-label-tag {
  display: inline-block;
  padding: 4px 14px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  transition: background 0.2s, color 0.2s;
}

.cp-palette-hex-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.cp-palette-hex {
  font-size: 14px;
  font-family: monospace;
  font-weight: 700;
  color: var(--color-text);
}

@media (max-width: 480px) {
  body { padding: 16px; }
}
// ===== Pattern 1: HEX/RGB変換ピッカー =====

var colorInput    = document.getElementById('colorInput');
var colorOpenBtn  = document.getElementById('colorOpenBtn');
var colorSwatch   = document.getElementById('colorSwatch');
var hexInput      = document.getElementById('hexInput');
var rgbValue      = document.getElementById('rgbValue');
var previewHeader = document.getElementById('previewHeader');
var previewHex    = document.getElementById('previewHex');
var hexCopyBtn    = document.getElementById('hexCopyBtn');
var rgbCopyBtn    = document.getElementById('rgbCopyBtn');

// ボタン・スウォッチのクリックでブラウザのカラーピッカーを開く
colorOpenBtn.addEventListener('click', function () { colorInput.click(); });
colorSwatch.addEventListener('click', function () { colorInput.click(); });

// ネイティブピッカーで色が変わるたびに各UIを更新する
colorInput.addEventListener('input', function () {
  updateColor(colorInput.value);
});

// HEXを各UIに反映する
function updateColor(hex) {
  var rgb = hexToRgb(hex);
  if (!rgb) return;
  colorSwatch.style.background = hex;
  hexInput.value = hex.toUpperCase();
  rgbValue.textContent = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
  previewHeader.style.background = hex;
  previewHex.textContent = hex.toUpperCase();
}

// HEX → RGB に変換する
function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  if (!result) return null;
  return {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  };
}

// HEXフィールドへの直接入力: blur または Enter で反映する
hexInput.addEventListener('change', applyHexInput);
hexInput.addEventListener('keydown', function (e) {
  if (e.key === 'Enter') applyHexInput();
});

function applyHexInput() {
  var val = hexInput.value.trim();
  // # がなければ補完する
  if (!val.startsWith('#')) val = '#' + val;
  if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
    colorInput.value = val.toLowerCase();
    updateColor(val);
  } else {
    // 不正な値はフィールドを元に戻す
    hexInput.value = colorInput.value.toUpperCase();
  }
}

// クリップボードへコピーし、ボタンテキストでフィードバックを表示する
function copyToClipboard(text, btn) {
  navigator.clipboard.writeText(text).then(function () {
    btn.classList.add('copied');
    btn.textContent = 'コピー済み';
    setTimeout(function () {
      btn.classList.remove('copied');
      btn.textContent = 'コピー';
    }, 1500);
  });
}

hexCopyBtn.addEventListener('click', function () {
  copyToClipboard(hexInput.value, hexCopyBtn);
});

rgbCopyBtn.addEventListener('click', function () {
  copyToClipboard(rgbValue.textContent, rgbCopyBtn);
});

// ===== Pattern 2: プリセットパレット + カスタム色 =====

var PRESET_COLORS = [
  '#1E2A3A', '#5A6A7A', '#9AA5B4', '#D0D7E0',
  '#1a4fa0', '#2B7FE8', '#60a5fa', '#bfdbfe',
  '#166534', '#22c55e', '#86efac', '#dcfce7',
  '#991b1b', '#ef4444', '#fca5a5', '#fee2e2',
  '#7c3aed', '#f59e0b'
];

var INITIAL_COLOR = '#2B7FE8';

var paletteGrid      = document.getElementById('paletteGrid');
var customBtn        = document.getElementById('customBtn');
var customColorInput = document.getElementById('customColorInput');
var labelTag         = document.getElementById('labelTag');
var paletteHex       = document.getElementById('paletteHex');
var paletteCopyBtn   = document.getElementById('paletteCopyBtn');

// パレットのスウォッチをJSで生成する
PRESET_COLORS.forEach(function (color) {
  var btn = document.createElement('button');
  btn.type = 'button';
  btn.className = 'cp-swatch-btn';
  btn.style.background = color;
  btn.setAttribute('aria-label', color);
  btn.dataset.color = color;
  btn.addEventListener('click', function () {
    selectPaletteColor(color, btn);
  });
  paletteGrid.appendChild(btn);
});

// 色を選んでラベルとHEX表示を更新する
function selectPaletteColor(hex, activeBtn) {
  paletteGrid.querySelectorAll('.cp-swatch-btn').forEach(function (b) {
    b.classList.remove('is-selected');
  });
  if (activeBtn) activeBtn.classList.add('is-selected');
  applyPaletteColor(hex);
}

function applyPaletteColor(hex) {
  labelTag.style.background = hex;
  labelTag.style.color = getContrastColor(hex);
  paletteHex.textContent = hex.toUpperCase();
}

// 輝度から文字色(白 or 黒)を決める(WCAG相対輝度ベース)
function getContrastColor(hex) {
  var r = parseInt(hex.slice(1, 3), 16) / 255;
  var g = parseInt(hex.slice(3, 5), 16) / 255;
  var b = parseInt(hex.slice(5, 7), 16) / 255;
  var toLinear = function (c) {
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  };
  var lum = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
  return lum > 0.179 ? '#1E2A3A' : '#ffffff';
}

// カスタム色ボタンでブラウザのピッカーを開く
customBtn.addEventListener('click', function () { customColorInput.click(); });

customColorInput.addEventListener('input', function () {
  // カスタム色選択時はパレットの選択状態を解除する
  paletteGrid.querySelectorAll('.cp-swatch-btn').forEach(function (b) {
    b.classList.remove('is-selected');
  });
  applyPaletteColor(customColorInput.value);
});

// 選択中のHEXをコピーする
paletteCopyBtn.addEventListener('click', function () {
  copyToClipboard(paletteHex.textContent, paletteCopyBtn);
});

// 初期色を選択状態にする
var initialBtn = paletteGrid.querySelector('[data-color="' + INITIAL_COLOR + '"]');
selectPaletteColor(INITIAL_COLOR, initialBtn);

AI用プロンプト

各パターンのプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。

ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。色の追加や初期色の変更など、要件を追記して使うのがおすすめです。

※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

Pattern 1 — HEX/RGB変換ピッカー

# カラーピッカー(HEX/RGB変換)作成依頼

## 概要
ブラウザ標準のカラーピッカーで選んだ色をHEXとRGBの両形式で表示・コピーできるUIを作成してください。選んだ色はプレビューカードの背景にリアルタイム反映されます。

## 要件
- 「色を選ぶ」ボタンクリックでブラウザのカラーピッカーを開く(input[type="color"]を非表示にしてJSで.click()する)
- 選んだ色のHEXコードとRGB値をリアルタイム表示する
- HEXフィールドは直接テキスト入力もできる(6桁入力後にblurまたはEnterで色を反映)
- HEX・RGBそれぞれにコピーボタンを設置し、クリックでクリップボードにコピーする
- プレビューカードのヘッダー部の背景色が選択色にリアルタイム反映される

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要

## 動作詳細
input[type="color"]はposition:absolute;opacity:0;width:0;height:0で非表示にし(display:noneは不可)、ボタンのclickイベントでcolorInput.click()を呼び出します。colorInputのinputイベントでHEX値を取得し、parseInt(hex.slice(1,3),16)等でRGBに変換します。HEXフィールドへの直接入力はchangeイベントとEnterキーで検知し、正規表現/^#[0-9A-Fa-f]{6}$/でバリデーションします。テキスト更新はtextContentを使用し、innerHTML+変数結合は禁止します。コピーはnavigator.clipboard.writeText()を使い、ボタンのtextContentでフィードバックを表示します。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。

Pattern 2 — プリセットパレット

# カラーピッカー(プリセットパレット)作成依頼

## 概要
よく使われる18色をグリッドで選択できるカラーパレットUIを作成してください。選んだ色はラベル(タグ)のプレビューに即時反映され、文字色は背景色の輝度に応じて白/黒を自動切替します。

## 要件
- 18色のカラースウォッチをグリッドで表示する(JSで動的生成する)
- クリックで選択状態(濃い枠線)を切り替える
- 「カスタム色」ボタンでブラウザのカラーピッカーを開き、任意の色も選べる
- 選んだ色がラベル(タグ形状のバッジ)の背景色にリアルタイム反映される
- ラベルの文字色はWCAG相対輝度の計算結果をもとに白/黒を自動で選ぶ
- 選択中のHEXコードをコピーボタンでクリップボードにコピーできる

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要

## 動作詳細
スウォッチはJSでdocument.createElementを使って動的生成してください。輝度計算はWCAG相対輝度の式(sRGB値をリニア化後に0.2126r+0.7152g+0.0722bで加重平均)で求め、0.179を閾値に白/黒文字を切り替えます。テキスト更新はtextContentを使用し、innerHTML+変数結合は禁止します。コピーはnavigator.clipboard.writeText()を使い、ボタンのtextContentでフィードバックを表示します。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。