アウトライン→塗り
枠線だけのボタンが、ホバーで下から色がせり上がり塗りつぶされ、文字色も同時に白へ反転します。控えめな副次アクションのボタンに向きます。
ライブデモ
コード
HTML
<!-- アウトライン→塗りボタン: hoverで下から塗りがせり上がり文字色が反転する -->
<div class="stage">
<button class="btn-outline-fill" type="button" aria-label="資料をダウンロード">
<span class="fill" aria-hidden="true"></span>
<span class="label label--base">資料をダウンロード</span>
<span class="label label--fill" aria-hidden="true">資料をダウンロード</span>
</button>
<p class="hint">ホバーで下から塗りつぶされ、文字色も白へ反転します</p>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
font-family: "Segoe UI", system-ui, sans-serif;
background: linear-gradient(160deg, #f6faf8 0%, #eaf5f0 55%, #dff0e8 100%);
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
:root {
--accent: #0f9d76;
}
/* ボタン本体: 枠線のみ。fillレイヤーとlabel--fillを内側に重ねる */
.btn-outline-fill {
position: relative;
overflow: hidden;
isolation: isolate;
display: inline-grid;
place-items: center;
background: #fff;
border: 2px solid var(--accent);
border-radius: 14px;
padding: 19px 54px;
font-size: 17px;
font-weight: 700;
letter-spacing: .02em;
color: var(--accent);
cursor: pointer;
transition: transform .2s ease, box-shadow .3s ease;
box-shadow: 0 6px 16px rgba(15, 157, 118, .12);
/* 待機時のアイドル: 枠の外側にごく淡いリングが呼吸する */
animation: outline-breathe 3.2s ease-in-out infinite;
}
@keyframes outline-breathe {
0%, 100% { box-shadow: 0 6px 16px rgba(15, 157, 118, .12), 0 0 0 0 rgba(15, 157, 118, 0); }
50% { box-shadow: 0 6px 16px rgba(15, 157, 118, .12), 0 0 0 7px rgba(15, 157, 118, .1); }
}
/* 塗りレイヤー: 下から高さで伸びる(scaleでなくheightで、上端をイージングで見せる) */
.fill {
position: absolute;
inset: auto 0 0 0;
height: 0%;
background: var(--accent);
z-index: 0;
transition: height .5s cubic-bezier(.65, 0, .35, 1);
}
.label {
position: relative;
z-index: 1;
grid-area: 1 / 1;
white-space: nowrap;
}
.label--base { color: var(--accent); }
/* 反転後の白文字: clip-pathで塗りの高さと同じ速度で下から現れる */
.label--fill {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #fff;
clip-path: inset(100% 0 0 0);
transition: clip-path .5s cubic-bezier(.65, 0, .35, 1);
}
.btn-outline-fill:hover,
.btn-outline-fill:focus-visible {
transform: translateY(-2px);
box-shadow: 0 12px 26px rgba(15, 157, 118, .28);
animation-play-state: paused;
}
.btn-outline-fill:hover .fill,
.btn-outline-fill:focus-visible .fill {
height: 100%;
}
.btn-outline-fill:hover .label--fill,
.btn-outline-fill:focus-visible .label--fill {
clip-path: inset(0 0 0 0);
}
.btn-outline-fill:active {
transform: translateY(0) scale(.98);
}
.btn-outline-fill:focus-visible {
outline: 3px solid rgba(15, 157, 118, .45);
outline-offset: 4px;
}
.hint {
margin: 0;
font-size: 13px;
color: #52685f;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.btn-outline-fill { animation: none; }
.fill,
.label--fill { transition-duration: .01ms; }
}
JavaScript
/* アウトライン→塗りボタン: 演出はCSSのみで完結(height + clip-pathの同期アニメーション)。JS不要。 */
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「アウトライン→塗り」の効果を追加してください。
# 追加してほしい効果
アウトライン→塗り(ボタン)
枠線だけのボタンが、ホバーで下から色がせり上がり塗りつぶされ、文字色も同時に白へ反転します。控えめな副次アクションのボタンに向きます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- アウトライン→塗りボタン: hoverで下から塗りがせり上がり文字色が反転する -->
<div class="stage">
<button class="btn-outline-fill" type="button" aria-label="資料をダウンロード">
<span class="fill" aria-hidden="true"></span>
<span class="label label--base">資料をダウンロード</span>
<span class="label label--fill" aria-hidden="true">資料をダウンロード</span>
</button>
<p class="hint">ホバーで下から塗りつぶされ、文字色も白へ反転します</p>
</div>
【CSS】
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
font-family: "Segoe UI", system-ui, sans-serif;
background: linear-gradient(160deg, #f6faf8 0%, #eaf5f0 55%, #dff0e8 100%);
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
:root {
--accent: #0f9d76;
}
/* ボタン本体: 枠線のみ。fillレイヤーとlabel--fillを内側に重ねる */
.btn-outline-fill {
position: relative;
overflow: hidden;
isolation: isolate;
display: inline-grid;
place-items: center;
background: #fff;
border: 2px solid var(--accent);
border-radius: 14px;
padding: 19px 54px;
font-size: 17px;
font-weight: 700;
letter-spacing: .02em;
color: var(--accent);
cursor: pointer;
transition: transform .2s ease, box-shadow .3s ease;
box-shadow: 0 6px 16px rgba(15, 157, 118, .12);
/* 待機時のアイドル: 枠の外側にごく淡いリングが呼吸する */
animation: outline-breathe 3.2s ease-in-out infinite;
}
@keyframes outline-breathe {
0%, 100% { box-shadow: 0 6px 16px rgba(15, 157, 118, .12), 0 0 0 0 rgba(15, 157, 118, 0); }
50% { box-shadow: 0 6px 16px rgba(15, 157, 118, .12), 0 0 0 7px rgba(15, 157, 118, .1); }
}
/* 塗りレイヤー: 下から高さで伸びる(scaleでなくheightで、上端をイージングで見せる) */
.fill {
position: absolute;
inset: auto 0 0 0;
height: 0%;
background: var(--accent);
z-index: 0;
transition: height .5s cubic-bezier(.65, 0, .35, 1);
}
.label {
position: relative;
z-index: 1;
grid-area: 1 / 1;
white-space: nowrap;
}
.label--base { color: var(--accent); }
/* 反転後の白文字: clip-pathで塗りの高さと同じ速度で下から現れる */
.label--fill {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #fff;
clip-path: inset(100% 0 0 0);
transition: clip-path .5s cubic-bezier(.65, 0, .35, 1);
}
.btn-outline-fill:hover,
.btn-outline-fill:focus-visible {
transform: translateY(-2px);
box-shadow: 0 12px 26px rgba(15, 157, 118, .28);
animation-play-state: paused;
}
.btn-outline-fill:hover .fill,
.btn-outline-fill:focus-visible .fill {
height: 100%;
}
.btn-outline-fill:hover .label--fill,
.btn-outline-fill:focus-visible .label--fill {
clip-path: inset(0 0 0 0);
}
.btn-outline-fill:active {
transform: translateY(0) scale(.98);
}
.btn-outline-fill:focus-visible {
outline: 3px solid rgba(15, 157, 118, .45);
outline-offset: 4px;
}
.hint {
margin: 0;
font-size: 13px;
color: #52685f;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.btn-outline-fill { animation: none; }
.fill,
.label--fill { transition-duration: .01ms; }
}
【JavaScript】
/* アウトライン→塗りボタン: 演出はCSSのみで完結(height + clip-pathの同期アニメーション)。JS不要。 */
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。