Copy Button — コピーボタン(コードブロック・テキストエリア・インライン)
このコンポーネントについて
コピーボタン(copy button)は、クリック1回でテキストをクリップボードへコピーするUIです。コードブロックのコピー・URLや認証キーのコピー・メールテンプレートの一括コピーなど、「文字を手動で選択して Ctrl+C」という手間を省く場面で活躍します。
このページでは、コードブロック・テキストエリア・インラインテキストの3パターンを紹介します。いずれもモダンブラウザ標準の navigator.clipboard.writeText()(Clipboard API)を使い、古いブラウザ向けのフォールバックも実装しています。
- コードブロックコピー — コードの右上にコピーボタンを重ねるGitHub風レイアウト
- テキストエリアコピー — 編集可能なテキストエリアの内容を一括コピー
- インラインコピー — APIキー・URLなど短いテキストをラベル付きで表示し、隣にコピーボタンを配置
- 「コピーしました!」フィードバック — コピー後2秒間ボタンのテキストが変化して成功を伝える
実装のポイント・注意点
Clipboard APIは navigator.clipboard.writeText(text) でテキストをコピーします。これは Promise を返すため .then() でコピー成功時の処理を書きます。ただし、このAPIは HTTPS環境またはlocalhost でしか動作しません。ローカルで file:// で開いた場合は動作しないため、Live Serverなどを使って確認してください。
Clipboard APIが使えない環境(古いブラウザ)向けに、textarea 要素を一時生成して document.execCommand('copy') を呼ぶフォールバックを実装しています。execCommand は非推奨ですが後方互換として有用です。一時的な textarea は position: fixed; opacity: 0 でページのレイアウトに影響させず、コピー後すぐに削除します。
コピー対象テキストの取得は、<pre><code> の場合は .textContent、<textarea> の場合は .value で取得します。innerHTML ではなく textContent や value を使うことで、HTMLタグが含まれていても純粋なテキストとしてコピーできます。
HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。
デモ
Pattern 1 — コードブロックコピー
const fetchData = async (url) => {
const res = await fetch(url);
if (!res.ok) throw new Error('fetch failed');
return res.json();
};
Pattern 2 — テキストエリアコピー
Pattern 3 — インラインコピー
サンプルソース
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: コードブロックコピー -->
<div class="code-wrapper">
<button class="copy-btn" data-copy-target="code1" onclick="copyCode(this)">コピー</button>
<pre><code id="code1">const fetchData = async (url) => {
const res = await fetch(url);
return res.json();
};</code></pre>
</div>
<!-- Pattern 2: テキストエリアコピー -->
<div class="textarea-wrapper">
<textarea id="text1" class="copy-textarea">お世話になっております。
〇〇の件についてご確認をお願いします。
よろしくお願いいたします。</textarea>
<button class="textarea-copy-btn" data-copy-target="text1" onclick="copyTextarea(this)">全文コピー</button>
</div>
<!-- Pattern 3: インラインコピー -->
<div class="inline-list">
<div class="inline-item">
<span class="inline-label">APIキー</span>
<span class="inline-text" id="api-key">sk-xxxx-xxxxxxxx-xxxx-xxxx</span>
<button class="inline-copy-btn" data-copy-target="api-key" onclick="copyInline(this)">コピー</button>
</div>
<div class="inline-item">
<span class="inline-label">インストール</span>
<span class="inline-text" id="npm-cmd">npm install @example/ui-kit</span>
<button class="inline-copy-btn" data-copy-target="npm-cmd" onclick="copyInline(this)">コピー</button>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
:root {
--cp-primary: #2B7FE8;
--cp-border: #E5E9EF;
--cp-radius: 8px;
}
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: sans-serif;
padding: 24px;
color: #1A2332;
display: flex;
flex-direction: column;
gap: 24px;
max-width: 600px;
}
/* ===== コードブロックコピー ===== */
.code-wrapper {
position: relative;
background: #1E293B;
border-radius: var(--cp-radius);
overflow: hidden;
}
.code-wrapper pre {
margin: 0;
padding: 18px 20px;
overflow-x: auto;
}
.code-wrapper code {
font-family: 'Courier New', Courier, monospace;
font-size: 13px;
color: #E2E8F0;
line-height: 1.8;
white-space: pre;
}
.copy-btn {
position: absolute;
top: 10px; right: 10px;
padding: 5px 12px;
background: rgba(255,255,255,.12);
color: #94A3B8;
border: 1px solid rgba(255,255,255,.18);
border-radius: 5px;
font-size: 12px;
font-family: sans-serif;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.copy-btn:hover { background: rgba(255,255,255,.22); color: #E2E8F0; }
.copy-btn.copied { background: rgba(34,197,94,.2); color: #86EFAC; }
/* ===== テキストエリアコピー ===== */
.textarea-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
}
.copy-textarea {
width: 100%;
padding: 12px 14px;
font-size: 13px;
font-family: sans-serif;
color: #1A2332;
background: #F8FAFC;
border: 1.5px solid var(--cp-border);
border-radius: var(--cp-radius);
resize: vertical;
min-height: 90px;
line-height: 1.7;
transition: border-color 0.15s;
}
.copy-textarea:focus {
outline: none;
border-color: var(--cp-primary);
background: #fff;
}
.textarea-copy-btn {
align-self: flex-end;
padding: 8px 18px;
background: var(--cp-primary);
color: #fff;
border: none;
border-radius: 7px;
font-size: 13px;
font-family: sans-serif;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.textarea-copy-btn:hover { background: #1A6DD0; }
.textarea-copy-btn.copied { background: #22C55E; }
/* ===== インラインコピー ===== */
.inline-list { display: flex; flex-direction: column; gap: 10px; }
.inline-item {
display: flex;
align-items: stretch;
border: 1.5px solid var(--cp-border);
border-radius: var(--cp-radius);
overflow: hidden;
}
.inline-label {
padding: 0 12px;
font-size: 11px;
font-weight: 700;
color: #5A6A7A;
background: #F8FAFC;
border-right: 1.5px solid var(--cp-border);
display: flex; align-items: center;
white-space: nowrap;
min-height: 42px;
}
.inline-text {
flex: 1;
padding: 10px 12px;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
color: #1A2332;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex; align-items: center;
}
.inline-copy-btn {
padding: 0 14px;
background: none;
border: none;
border-left: 1.5px solid var(--cp-border);
font-size: 12px;
font-family: sans-serif;
color: #5A6A7A;
cursor: pointer;
min-width: 68px;
min-height: 42px;
transition: background 0.15s, color 0.15s;
white-space: nowrap;
}
.inline-copy-btn:hover { background: #F8FAFC; color: var(--cp-primary); }
.inline-copy-btn.copied { color: #22C55E; background: #F0FDF4; }
// コードブロックをコピー
function copyCode(btn) {
var targetId = btn.dataset.copyTarget;
var text = document.getElementById(targetId).textContent;
copyToClipboard(text, btn, 'コピー');
}
// テキストエリアをコピー
function copyTextarea(btn) {
var targetId = btn.dataset.copyTarget;
var text = document.getElementById(targetId).value;
copyToClipboard(text, btn, '全文コピー');
}
// インラインテキストをコピー
function copyInline(btn) {
var targetId = btn.dataset.copyTarget;
var text = document.getElementById(targetId).textContent;
copyToClipboard(text, btn, 'コピー');
}
// クリップボードにコピーしてフィードバックを表示
function copyToClipboard(text, btn, originalLabel) {
navigator.clipboard.writeText(text).then(function() {
feedback(btn, originalLabel);
}).catch(function() {
// フォールバック(Clipboard API 非対応環境向け)
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
feedback(btn, originalLabel);
});
}
// 2秒間「コピーしました!」と表示して元に戻す
function feedback(btn, originalLabel) {
btn.textContent = 'コピーしました!';
btn.classList.add('copied');
setTimeout(function() {
btn.textContent = originalLabel;
btn.classList.remove('copied');
}, 2000);
}
AI用プロンプト
以下のプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。
ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。パターン数の変更やフィードバックの秒数変更など、要件を追記して使うのがおすすめです。
※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。
💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。
# コピーボタン 作成依頼
## 概要
クリックでテキストをクリップボードにコピーするコピーボタンを、3パターン実装してください。
## 要件
### Pattern 1: コードブロックコピー
- コードを暗色背景の <pre><code> で表示する
- 右上にコピーボタンを絶対配置する(GitHubのコードコピー風)
### Pattern 2: テキストエリアコピー
- 編集可能な textarea を表示する
- 「全文コピー」ボタンで textarea の .value をコピーする
### Pattern 3: インラインコピー
- ラベル + テキスト + コピーボタン を横並びにした行を複数表示する
- APIキー・コマンド・URLなど短いテキストのコピーを想定
### 共通
- Clipboard API(navigator.clipboard.writeText)を使用する
- 非対応ブラウザ向けに execCommand フォールバックを実装する
- コピー成功後2秒間、ボタンのテキストを「コピーしました!」に変えてフィードバックを表示する
## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要
## 動作詳細
コピー対象は data-copy-target 属性でIDを指定して取得する。
<code> / <span> の場合は .textContent、textarea の場合は .value を取得する。
テキストの取得に innerHTML は使わない。
コピー後は classList に 'copied' を追加してスタイルを変化させ、2秒後に元に戻す。
## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。