メッセージ入力欄
入力内容に応じて高さが伸びるテキストエリアと、文字が入ると活性化する送信ボタン、絵文字と添付のアイコンを備えた入力欄です。
ライブデモ
コード
HTML
<!-- メッセージ入力欄: 内容に応じて伸びるテキストエリアと、入力があると活性化する送信ボタン、絵文字/添付アイコン -->
<div class="stage">
<section class="cc-panel" aria-label="メッセージ入力デモ">
<ul class="cc-history" id="ccHistory">
<li class="cc-msg cc-msg--in">今日の打ち合わせ、資料はドライブに置いておいたよ</li>
<li class="cc-msg cc-msg--out">ありがとう、確認するね!</li>
</ul>
<div class="cc-emoji" id="ccEmoji" hidden>
<button type="button" class="cc-emoji__item">😀</button>
<button type="button" class="cc-emoji__item">👍</button>
<button type="button" class="cc-emoji__item">❤️</button>
<button type="button" class="cc-emoji__item">😂</button>
<button type="button" class="cc-emoji__item">🎉</button>
</div>
<form class="cc-composer" id="ccForm">
<button type="button" class="cc-icon" id="ccAttach" aria-label="ファイルを添付">📎</button>
<button type="button" class="cc-icon" id="ccEmojiToggle" aria-label="絵文字を選択" aria-expanded="false">🙂</button>
<textarea class="cc-textarea" id="ccInput" rows="1" placeholder="メッセージを入力…" aria-label="メッセージ入力"></textarea>
<button type="submit" class="cc-send" id="ccSend" disabled aria-label="送信"><span aria-hidden="true">➤</span></button>
</form>
</section>
</div>
CSS
/* メッセージ入力欄 */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px;
font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 90% 8%, rgba(167, 139, 250, .15) 0%, transparent 42%),
radial-gradient(circle at 6% 92%, rgba(236, 72, 153, .1) 0%, transparent 45%),
#120e1c;
color: #ede9f7;
}
.cc-panel {
width: min(380px, 92vw);
border-radius: 22px;
background: rgba(255, 255, 255, .045);
border: 1px solid rgba(255, 255, 255, .09);
box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
overflow: hidden;
display: grid;
grid-template-rows: auto auto auto;
}
.cc-history {
list-style: none;
margin: 0;
padding: 18px 16px 8px;
display: grid;
gap: 8px;
max-height: 220px;
overflow-y: auto;
}
.cc-msg {
width: fit-content;
max-width: 82%;
padding: 10px 14px;
font-size: 13px;
line-height: 1.55;
border-radius: 15px;
animation: cc-pop .35s cubic-bezier(.22, 1, .36, 1);
}
.cc-msg--in {
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
border-bottom-left-radius: 5px;
}
.cc-msg--out {
margin-left: auto;
background: linear-gradient(135deg, #a78bfa, #7c3aed);
border-bottom-right-radius: 5px;
color: #fff;
box-shadow: 0 10px 22px -12px rgba(124, 58, 237, .7);
}
@keyframes cc-pop {
from { opacity: 0; transform: translateY(8px) scale(.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* 絵文字ピッカー */
.cc-emoji {
display: flex;
gap: 6px;
padding: 0 16px 10px;
animation: cc-pop .25s cubic-bezier(.22, 1, .36, 1);
}
.cc-emoji[hidden] { display: none; }
.cc-emoji__item {
font: inherit;
font-size: 17px;
line-height: 1;
padding: 7px 9px;
border: none;
border-radius: 10px;
background: rgba(255, 255, 255, .06);
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.cc-emoji__item:hover { background: rgba(255, 255, 255, .14); transform: translateY(-2px); }
/* 入力バー */
.cc-composer {
display: flex;
align-items: flex-end;
gap: 6px;
padding: 12px 12px 14px;
border-top: 1px solid rgba(255, 255, 255, .08);
}
.cc-icon {
flex: none;
width: 34px;
height: 34px;
display: grid;
place-items: center;
font-size: 16px;
border: none;
border-radius: 50%;
background: transparent;
color: rgba(237, 233, 247, .75);
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.cc-icon:hover { background: rgba(255, 255, 255, .08); }
.cc-icon.is-active { transform: scale(.9); background: rgba(255, 255, 255, .14); }
.cc-textarea {
flex: 1;
resize: none;
min-height: 34px;
max-height: 120px;
overflow-y: auto;
padding: 8px 12px;
font: inherit;
font-size: 13.5px;
line-height: 1.5;
color: #ede9f7;
background: rgba(255, 255, 255, .07);
border: 1px solid rgba(255, 255, 255, .1);
border-radius: 16px;
transition: border-color .2s ease, background .2s ease;
}
.cc-textarea:focus {
outline: none;
border-color: rgba(167, 139, 250, .55);
background: rgba(255, 255, 255, .1);
}
.cc-textarea::placeholder { color: rgba(237, 233, 247, .38); }
.cc-send {
flex: none;
width: 36px;
height: 36px;
display: grid;
place-items: center;
font-size: 15px;
border: none;
border-radius: 50%;
color: rgba(237, 233, 247, .4);
background: rgba(255, 255, 255, .07);
cursor: not-allowed;
transition: background .2s ease, color .2s ease, transform .15s cubic-bezier(.22, 1, .36, 1);
}
.cc-send:not(:disabled) {
color: #fff;
background: linear-gradient(135deg, #a78bfa, #7c3aed);
cursor: pointer;
box-shadow: 0 8px 18px -8px rgba(124, 58, 237, .75);
}
.cc-send:not(:disabled):hover { transform: translateY(-2px) scale(1.05); }
.cc-send:not(:disabled):active { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) {
.cc-msg { animation: none; }
.cc-send:not(:disabled):hover { transform: none; }
}
@media (max-width: 380px) {
.cc-panel { border-radius: 18px; }
.cc-history { padding: 14px 12px 6px; }
}
JavaScript
// メッセージ入力欄: オートリサイズ・送信ボタンの活性化・絵文字挿入・送信で履歴に追記
(() => {
const form = document.getElementById('ccForm');
const input = document.getElementById('ccInput');
const sendBtn = document.getElementById('ccSend');
const history = document.getElementById('ccHistory');
const attachBtn = document.getElementById('ccAttach');
const emojiToggle = document.getElementById('ccEmojiToggle');
const emojiPanel = document.getElementById('ccEmoji');
if (!form || !input || !sendBtn || !history) return; // null安全
const MAX_HEIGHT = 120;
const autoResize = () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, MAX_HEIGHT) + 'px';
};
const syncSendState = () => {
const hasText = input.value.trim().length > 0;
sendBtn.disabled = !hasText;
};
input.addEventListener('input', () => {
autoResize();
syncSendState();
});
// 添付ボタンは押した感触だけ返す装飾的な操作
if (attachBtn) {
attachBtn.addEventListener('click', () => {
attachBtn.classList.add('is-active');
setTimeout(() => attachBtn.classList.remove('is-active'), 160);
});
}
// 絵文字パネルの開閉
if (emojiToggle && emojiPanel) {
emojiToggle.addEventListener('click', () => {
const willShow = emojiPanel.hidden;
emojiPanel.hidden = !willShow;
emojiToggle.setAttribute('aria-expanded', String(willShow));
if (willShow) input.focus();
});
emojiPanel.addEventListener('click', (e) => {
const btn = e.target.closest('.cc-emoji__item');
if (!btn) return;
input.value += btn.textContent;
autoResize();
syncSendState();
input.focus();
emojiPanel.hidden = true;
emojiToggle.setAttribute('aria-expanded', 'false');
});
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
const li = document.createElement('li');
li.className = 'cc-msg cc-msg--out';
li.textContent = text;
history.appendChild(li);
history.scrollTop = history.scrollHeight;
input.value = '';
autoResize();
syncSendState();
if (emojiPanel && !emojiPanel.hidden) {
emojiPanel.hidden = true;
if (emojiToggle) emojiToggle.setAttribute('aria-expanded', 'false');
}
});
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「メッセージ入力欄」の効果を追加してください。
# 追加してほしい効果
メッセージ入力欄(チャット & メディア)
入力内容に応じて高さが伸びるテキストエリアと、文字が入ると活性化する送信ボタン、絵文字と添付のアイコンを備えた入力欄です。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- メッセージ入力欄: 内容に応じて伸びるテキストエリアと、入力があると活性化する送信ボタン、絵文字/添付アイコン -->
<div class="stage">
<section class="cc-panel" aria-label="メッセージ入力デモ">
<ul class="cc-history" id="ccHistory">
<li class="cc-msg cc-msg--in">今日の打ち合わせ、資料はドライブに置いておいたよ</li>
<li class="cc-msg cc-msg--out">ありがとう、確認するね!</li>
</ul>
<div class="cc-emoji" id="ccEmoji" hidden>
<button type="button" class="cc-emoji__item">😀</button>
<button type="button" class="cc-emoji__item">👍</button>
<button type="button" class="cc-emoji__item">❤️</button>
<button type="button" class="cc-emoji__item">😂</button>
<button type="button" class="cc-emoji__item">🎉</button>
</div>
<form class="cc-composer" id="ccForm">
<button type="button" class="cc-icon" id="ccAttach" aria-label="ファイルを添付">📎</button>
<button type="button" class="cc-icon" id="ccEmojiToggle" aria-label="絵文字を選択" aria-expanded="false">🙂</button>
<textarea class="cc-textarea" id="ccInput" rows="1" placeholder="メッセージを入力…" aria-label="メッセージ入力"></textarea>
<button type="submit" class="cc-send" id="ccSend" disabled aria-label="送信"><span aria-hidden="true">➤</span></button>
</form>
</section>
</div>
【CSS】
/* メッセージ入力欄 */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px;
font-family: "Hiragino Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 90% 8%, rgba(167, 139, 250, .15) 0%, transparent 42%),
radial-gradient(circle at 6% 92%, rgba(236, 72, 153, .1) 0%, transparent 45%),
#120e1c;
color: #ede9f7;
}
.cc-panel {
width: min(380px, 92vw);
border-radius: 22px;
background: rgba(255, 255, 255, .045);
border: 1px solid rgba(255, 255, 255, .09);
box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .75);
overflow: hidden;
display: grid;
grid-template-rows: auto auto auto;
}
.cc-history {
list-style: none;
margin: 0;
padding: 18px 16px 8px;
display: grid;
gap: 8px;
max-height: 220px;
overflow-y: auto;
}
.cc-msg {
width: fit-content;
max-width: 82%;
padding: 10px 14px;
font-size: 13px;
line-height: 1.55;
border-radius: 15px;
animation: cc-pop .35s cubic-bezier(.22, 1, .36, 1);
}
.cc-msg--in {
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .07);
border-bottom-left-radius: 5px;
}
.cc-msg--out {
margin-left: auto;
background: linear-gradient(135deg, #a78bfa, #7c3aed);
border-bottom-right-radius: 5px;
color: #fff;
box-shadow: 0 10px 22px -12px rgba(124, 58, 237, .7);
}
@keyframes cc-pop {
from { opacity: 0; transform: translateY(8px) scale(.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* 絵文字ピッカー */
.cc-emoji {
display: flex;
gap: 6px;
padding: 0 16px 10px;
animation: cc-pop .25s cubic-bezier(.22, 1, .36, 1);
}
.cc-emoji[hidden] { display: none; }
.cc-emoji__item {
font: inherit;
font-size: 17px;
line-height: 1;
padding: 7px 9px;
border: none;
border-radius: 10px;
background: rgba(255, 255, 255, .06);
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.cc-emoji__item:hover { background: rgba(255, 255, 255, .14); transform: translateY(-2px); }
/* 入力バー */
.cc-composer {
display: flex;
align-items: flex-end;
gap: 6px;
padding: 12px 12px 14px;
border-top: 1px solid rgba(255, 255, 255, .08);
}
.cc-icon {
flex: none;
width: 34px;
height: 34px;
display: grid;
place-items: center;
font-size: 16px;
border: none;
border-radius: 50%;
background: transparent;
color: rgba(237, 233, 247, .75);
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.cc-icon:hover { background: rgba(255, 255, 255, .08); }
.cc-icon.is-active { transform: scale(.9); background: rgba(255, 255, 255, .14); }
.cc-textarea {
flex: 1;
resize: none;
min-height: 34px;
max-height: 120px;
overflow-y: auto;
padding: 8px 12px;
font: inherit;
font-size: 13.5px;
line-height: 1.5;
color: #ede9f7;
background: rgba(255, 255, 255, .07);
border: 1px solid rgba(255, 255, 255, .1);
border-radius: 16px;
transition: border-color .2s ease, background .2s ease;
}
.cc-textarea:focus {
outline: none;
border-color: rgba(167, 139, 250, .55);
background: rgba(255, 255, 255, .1);
}
.cc-textarea::placeholder { color: rgba(237, 233, 247, .38); }
.cc-send {
flex: none;
width: 36px;
height: 36px;
display: grid;
place-items: center;
font-size: 15px;
border: none;
border-radius: 50%;
color: rgba(237, 233, 247, .4);
background: rgba(255, 255, 255, .07);
cursor: not-allowed;
transition: background .2s ease, color .2s ease, transform .15s cubic-bezier(.22, 1, .36, 1);
}
.cc-send:not(:disabled) {
color: #fff;
background: linear-gradient(135deg, #a78bfa, #7c3aed);
cursor: pointer;
box-shadow: 0 8px 18px -8px rgba(124, 58, 237, .75);
}
.cc-send:not(:disabled):hover { transform: translateY(-2px) scale(1.05); }
.cc-send:not(:disabled):active { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) {
.cc-msg { animation: none; }
.cc-send:not(:disabled):hover { transform: none; }
}
@media (max-width: 380px) {
.cc-panel { border-radius: 18px; }
.cc-history { padding: 14px 12px 6px; }
}
【JavaScript】
// メッセージ入力欄: オートリサイズ・送信ボタンの活性化・絵文字挿入・送信で履歴に追記
(() => {
const form = document.getElementById('ccForm');
const input = document.getElementById('ccInput');
const sendBtn = document.getElementById('ccSend');
const history = document.getElementById('ccHistory');
const attachBtn = document.getElementById('ccAttach');
const emojiToggle = document.getElementById('ccEmojiToggle');
const emojiPanel = document.getElementById('ccEmoji');
if (!form || !input || !sendBtn || !history) return; // null安全
const MAX_HEIGHT = 120;
const autoResize = () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, MAX_HEIGHT) + 'px';
};
const syncSendState = () => {
const hasText = input.value.trim().length > 0;
sendBtn.disabled = !hasText;
};
input.addEventListener('input', () => {
autoResize();
syncSendState();
});
// 添付ボタンは押した感触だけ返す装飾的な操作
if (attachBtn) {
attachBtn.addEventListener('click', () => {
attachBtn.classList.add('is-active');
setTimeout(() => attachBtn.classList.remove('is-active'), 160);
});
}
// 絵文字パネルの開閉
if (emojiToggle && emojiPanel) {
emojiToggle.addEventListener('click', () => {
const willShow = emojiPanel.hidden;
emojiPanel.hidden = !willShow;
emojiToggle.setAttribute('aria-expanded', String(willShow));
if (willShow) input.focus();
});
emojiPanel.addEventListener('click', (e) => {
const btn = e.target.closest('.cc-emoji__item');
if (!btn) return;
input.value += btn.textContent;
autoResize();
syncSendState();
input.focus();
emojiPanel.hidden = true;
emojiToggle.setAttribute('aria-expanded', 'false');
});
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
const li = document.createElement('li');
li.className = 'cc-msg cc-msg--out';
li.textContent = text;
history.appendChild(li);
history.scrollTop = history.scrollHeight;
input.value = '';
autoResize();
syncSendState();
if (emojiPanel && !emojiPanel.hidden) {
emojiPanel.hidden = true;
if (emojiToggle) emojiToggle.setAttribute('aria-expanded', 'false');
}
});
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。